Skip to content

Commit 6091136

Browse files
fix(mcp, email, knowledge, queue, sms, storage, trigger, i18n): runtime strings state each decision in words instead of a tracker number (stage 2) (#21311)
Part of #20751 Clause-②: no **Stage 2 of the `domain:services` lane under the maintainer's A / A ruling (5902360492): the `connector-mcp`, `plugin-email`, `service-storage`, `service-knowledge`, `service-queue`, `service-sms`, `trigger-record-change` and `service-i18n` strings.** The card stays open for the later stages, so this PR carries no closing keyword. Text only: no status, error `code`, field, route, export or control flow moves (AST-skeleton proof below, 14 of 14 files SAME). ## What this does These eight packages' refusals, warnings, log lines, route-ledger notes and the built-in change-email notice template description (in all four locales) sent the reader to a tracker number for the reason behind them. In form D, as stage 1 applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. All 22 ledgered occurrences in the eight packages (claim `5944824425`), re-derived from the ledger on `origin/main` at `383a00c7`, not from the card's table: `plugin-email` 6, `service-storage` 4, `service-knowledge` 3, `connector-mcp` 2, `service-queue` 2, `service-sms` 2, `service-i18n` 2, `trigger-record-change` 1. The card's table reads the same 22. They sit in 21 string sites (one storage note carried two ids). ### Rewritten in words Author- and administrator-visible text first, log lines last. Every cited card was read through REST (body and every comment) before its string was rewritten. | Where (head line) | Cited | The text now says | Decision read from | |---|---|---|---| | `connector-mcp` `mcp-provider.ts:142`, allowlist-miss refusal (fatal at boot, skipped and logged on a runtime publish) | 3055 | "a stdio transport launches a local process, so stack metadata may only name a command the host's own code allows" | card body: declarative stdio is default-deny and allowlisted by host code, because metadata (a runtime Studio publish included) could otherwise launch commands on the server | | `connector-mcp` `mcp-provider.ts:149`, default-deny refusal | 3055 | "or use an http transport, which this policy does not gate (ADR-0024 §4)" | card body: http transports stay unaffected | | `plugin-email` `templates/auth-templates.ts:573`, `:601`, `:630`, `:660`, the `auth.email_change_notice` description seeded into `sys_email_template` (en-US, zh-CN, ja-JP, es-ES) | 8019 | "so a hijacked session cannot move the account identity unannounced", and the same decision in each locale | maintainer ruling 5271034556: notify the old address, do not gate | | `plugin-email` `internal-header-readback.ts:128`, thrown when the engine cannot dereference `headers_json` | 8149 | "a missing header does not announce itself, so the send would succeed while silently deviating from what was authored" | the card adopts its blocker's landed remedy; unlock comment 5277987462, point 4 (fail closed, because a missing header is not self-announcing) | | `service-queue` `db-queue-adapter.ts:93`, thrown when `sys_job_queue` loses its retention declaration | 5179 | "the one platform reaper sweeps completed rows by that declaration, and a sweeper here would be a second copy of the window, free to drift from the first" | ACCEPT 5176594402: declarative retention swept by the one platform reaper (ADR-0057 §3.3), read from the declaration rather than copied | | `trigger-record-change` `record-change-trigger.ts:240`, array-trigger warning to the flow author | 3457 | "multi-event arrays are deferred until two independent projects need a combination other than created-or-updated" | closing comment 5076318442 (not planned; its restart clause is two independent real projects) | | `service-knowledge` `knowledge-service.ts:321`, no-identity retrieval warning | 2981 | "a missing identity is not a grant of authority, so retrieval fails closed rather than searching the whole corpus unscoped" | card body: fail closed on a missing identity; pass-through only for an explicit system context (ADR-0096) | | `service-knowledge` `knowledge-service-plugin.ts:221`, predicate-write warning | 4672 | "the lifecycle reap guard de-indexes those rows before they are deleted" | maintainer ruling 5194621834 (plan C) | | `service-queue` `queue-service-plugin.ts:160`, rejected-floor error | 5195 | "that floor is what makes it refuse an override below the idempotency window" | ACCEPT 5177937633: an override below a consumer's registered floor is refused | | `service-sms` `sms-daily-quota.ts:327`, unreadable-counter warning | 2814 | "a quota the platform cannot count must not refuse the one-time codes users sign in with" | card body, ask 4: a counter-store failure fails open with a warning, and the quota gate must not take sign-in down | | `service-storage` `attachment-lifecycle.ts:529`, reclamation-gate line | 4797 | "deleting bytes cannot be undone, so it waits for a verified migration with no deviation on record, while reversible work carries on" | maintainer ruling 5202265919 / 5203575604 (conditional B: withdraw the irreversible authority, keep the reversible) | | `plugin-email` `email-service.ts:925`, over-limit attachments line | 5172 | "queueable through the storage capability, which holds it outside the row while the row keeps a reference and the attachment's audit metadata" | maintainer ruling in the card body (content through storage; the row keeps the reference and permanent audit metadata) | | `service-storage` `storage-route-ledger.ts:113`, download-URL row note (guard data, not shipped) | 3584 | "The dispatcher ledger points here because this protocol, not a dispatcher route, is the canonical storage surface the SDK speaks" | landing commit `0bab8bb45` (the service-storage protocol stays canonical), and the later retirement of the dispatcher bridge recorded in `packages/runtime/src/route-ledger.ts` | ### Citation only (the sentence already stated the decision) - `service-knowledge` `knowledge-service-plugin.ts:220` (4639, "A bulk event carries a count, not records, ... cannot be repaired from the event stream": the honest aggregate event). - `service-sms` `sms-plugin.ts:170` (2814): the counter store's `subject` is now "daily SMS send quota". It is prose only, interpolated into the store's bind and fallback log lines, never a key. - `service-i18n` `i18n-route-ledger.ts:98`, `:100` (3636, "The client now sends the path form the spec declares"; "it now sends both the object and the locale in the path", checked against `i18n.getTranslations` / `i18n.getFieldLabels` in `packages/client/src/index.ts`). - `service-storage` `storage-route-ledger.ts:113` (second half) and `:119` (3689, "moved into the declared envelope"; "retired when every storage route moved to the declared envelope, `ok` being a private second word for `success`"). ### Translations The change-email notice description is the only string here with locale variants. Each of the zh-CN, ja-JP and es-ES rows carries the same decision as the en-US row ("使被劫持的会话无法在原邮箱不知情的情况下转移账号身份", "乗っ取られたセッションが元のアドレスに知られないままアカウントの識別情報を移せないようにします", "para que una sesión secuestrada no pueda trasladar la identidad de la cuenta sin aviso") and no number. They are template rows, not i18n bundle keys, so no bundle or digest moves. `check:i18n` reads "all bundles in sync". The built-in seeder upserts by name and locale on every boot, so a deployment's existing rows take the new description. ## Ledger (`scripts/doc-authoring-prose-id.baseline.json`) Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger` (exit 0, no growth refusal) into a scratch file, then copied into place. The diff deletes 44 lines and adds none: exactly the 14 file blocks of the eight packages. A scripted comparison of every other key: 0 moved, 0 added. | | before (`383a00c7`) | after | |---|---|---| | `plugin-email` | 6 occurrences, 3 pairs, 3 files | 0 | | `service-storage` | 4 occurrences, 3 pairs, 2 files | 0 | | `service-knowledge` | 3 occurrences, 3 pairs, 2 files | 0 | | `connector-mcp` | 2 occurrences, 1 pair, 1 file | 0 | | `service-queue` | 2 occurrences, 2 pairs, 2 files | 0 | | `service-sms` | 2 occurrences, 2 pairs, 2 files | 0 | | `service-i18n` | 2 occurrences, 1 pair, 1 file | 0 | | `trigger-record-change` | 1 occurrence, 1 pair, 1 file | 0 | | whole ledger | 283 occurrences, 205 pairs, 86 files | 261 occurrences, 189 pairs, 72 files | `pnpm check:doc-authoring` at the head: "236 pinned site(s) across 72 file(s), 86149 string(s) read in 1248 parsed source(s), no growth, no burn-down unrecorded" (the census before the change read 257 sites). No gate is added or loosened; `scripts/check-doc-authoring.mjs` is untouched. ## Changeset `.changeset/20751-services-strings-stage2-state-the-decision.md`: `patch` for `@objectstack/connector-mcp`, `@objectstack/plugin-email`, `@objectstack/service-knowledge`, `@objectstack/service-queue`, `@objectstack/service-sms`, `@objectstack/service-storage` and `@objectstack/trigger-record-change`. Measured after building at the head: - Each new sentence named above is in its package's built output (2 files each; `plugin-email`'s four descriptions read back from the built module's exported `AUTH_EMAIL_CHANGE_NOTICE_TEMPLATES`, all four id-free). - A TypeScript scan of every string literal and template text in the eight packages' built `.js`/`.mjs`/`.cjs` finds 0 tracker ids. Control: the same scan reads 86 in `plugin-security`'s built output and 66 in `service-automation`'s. - `@objectstack/service-i18n` is deliberately NOT in the changeset. Its only change is the route ledger, which no entry imports: `I18N_ROUTE_LEDGER` and the new note are in 0 files of its `dist/` (positive control: `registerRoutes` in 4). The same holds for `service-storage`'s ledger (`STORAGE_ROUTE_LEDGER` in 0, control `reap guard: kept` in 2), so that package's patch is for `attachment-lifecycle.ts` alone. ## Text-only proof A TypeScript-AST skeleton of each changed non-test `.ts` file: every string literal and template text is a placeholder, a run of adjacent string operands of a `+` chain is one string (embedded expressions kept in order), identifiers and numbers are kept, and comments are never read. The walk visits every child (no early exit). `383a00c7` against the head: 14 of 14 SAME, node counts identical per file. Control on scratch copies of `mcp-provider.ts`: renaming one identifier reads DIFF; changing only a string's text reads SAME; re-splitting one template into two `+` operands reads SAME. ## Pins Four assertions named a tracker id and now name the sentence that replaced it: - `service-knowledge` `__tests__/event-sync-data-events.test.ts:166`, `:167`: `toContain('cannot be repaired from the event stream')` and `toContain('the lifecycle reap guard de-indexes those rows before they are deleted')` instead of the two ids. The third half-pin (`application-level predicate writes are not`) is unchanged, so both halves of the honest line stay pinned. - `trigger-record-change` `array-form-refusal-end-to-end.test.ts:116` and `record-change-trigger.test.ts:183`: `toMatch(/multi-event arrays are deferred until/)` instead of the id. The label now reads "states the standing decision". No `code` or `status` assertion was touched; none of these strings is a coded refusal. Every other old fragment was searched repo-wide: no other test, doc or fixture quotes them. ## Tests All at head `7bcf2f613`, through `scripts/pm/os-verify-lock.sh`, each `VERDICT command-exit 0`: - Build: `turbo run build` over the eight packages and their dependency closure (37/37), then `turbo run build --filter=./packages/* --filter=./packages/*/*` (71/71) for the dist-reading gates. - `vitest run --maxWorkers=2` per package: `plugin-email` 31 files, 510 tests; `service-storage` 41 files, 629 tests; `trigger-record-change` 10 files, 101 tests; `service-queue` 5 files, 77 tests; `service-i18n` 5 files, 74 tests; `service-sms` 5 files, 74 tests; `service-knowledge` 4 files, 44 tests; `connector-mcp` 3 files, 23 tests. All passed. The three re-pinned files were also run with the verbose reporter, which names the three cases holding the four re-pinned assertions as passed. - `typecheck` for all eight, exit 0, including `check:test-typecheck: OK` where the package wires it. ## Gates - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths) at `7bcf2f613`: 72 commands over 19 paths, run one at a time from the worktree, each exit code recorded before any pipe. `--ran`: "72 derived famil(ies) accounted for — 72 run, 0 NOT-MEASURED (a DERIVED zero — all 72 recorded an exit code and none of them is 3)". - `check:dual-build-cjs-loads` and `check:i18n` first answered `PREREQUISITE NOT MET` (exit 3, only the closure was built). After the full package build they and the other dist-reading gates were re-run, all exit 0: 105 require entry points across 66 packages load; "all bundles in sync"; `check:dts-closure` 71 built packages, 167/167 declaration files; `check:sourcemap-no-sources-content` 68 packages, 522 maps; `check:lean-entry-closure` and `check:published-files` green. - `check-issue-citations`: "no issue citations added against 383a00c (14 file(s) read)"; `check:nul-bytes`: OK, 9609 files, no raw ASCII control bytes; `check:i18n-stale-fill`: "no new stale fills"; `check:type-check-debt`: "none above its recorded number"; `check:engine-double-contract`: OK. - Outside the derived set, all exit 0: the eleven declared wide-population families (`check:init-service-contract`, `check:live-db-isolation`, `check:meta-type-normalized`, `check:optional-error-sink`, `check:resume-authority-declared`, `check:route-envelope`, `check:runner-env-posture`, `check:settings-bind-window`, `check:startup-registry-verdict`, `check:verify-stand-in`, `check:wildcard-fallthrough`) and the artifact-roster families whose roster sits under one of this PR's directories (`check-changeset-fixed`, `check-published-list-mirrors` and its self-test, `check:authz-resolver`, `check:console-injection`, `check:error-code-casing`, `check:filter-alias-parity`, `check:published-readme-exports`, `check-dts-references --self-test`). The path-scheduled CI jobs and the type-check lanes are CI's. - `pnpm lint` (`eslint . --no-inline-config`, repo-wide, not narrowed) at `7bcf2f613`: exit 0, 128 s under the lock. - No gate run appended anything to `AGENTS.md`; the tree stayed clean throughout. ## Acceptance notes - `origin/main` moved 8 commits to `393ae878` after the branch point. None touches any of this PR's 19 paths or any of the eight packages, and the ledger is not among them, so the recomputed ledger stands on the merged tree. The branch was not merged with `main`; the queue rebuilds it there. - Comments and test titles in these packages still cite numbers: a comment is the sanctioned home for an internal anchor, and the ledger does not read test files. Two conformance tests carry an id in their failure text (`storage-route-ledger.conformance.test.ts:74`, `i18n-route-ledger.conformance.test.ts:91`); they are test files, outside the ledger and this stage. - `packages/lint/src/validate-flow-trigger-readiness.ts:557` carries the same 3457 deferral in the lint hint, and its test pins the id (`validate-flow-trigger-readiness.test.ts:738`). `packages/lint` is not in this stage. The trigger warning now uses the lint hint's own phrase ("multi-event arrays are deferred"), so the two read alike once that stage lands. - The remaining packages of this lane's share are later stages. --- _Generated by [Claude Code](https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1c52a5e commit 6091136

19 files changed

Lines changed: 66 additions & 73 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/connector-mcp': patch
3+
'@objectstack/plugin-email': patch
4+
'@objectstack/service-knowledge': patch
5+
'@objectstack/service-queue': patch
6+
'@objectstack/service-sms': patch
7+
'@objectstack/service-storage': patch
8+
'@objectstack/trigger-record-change': patch
9+
---
10+
11+
MCP stdio, email, knowledge, queue, SMS, storage and record-trigger refusals, warnings and template descriptions no longer cite tracker numbers; each one states the decision behind it in words
12+
13+
Clause-②: no
14+
15+
Some strings these seven packages show to operators, administrators and flow authors pointed at an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does.
16+
17+
- `@objectstack/connector-mcp`: the declarative stdio refusals say a stdio transport launches a local process, so stack metadata may only name a command the host's own code allows, and that an http transport is not gated by this policy.
18+
- `@objectstack/plugin-email`: the built-in change-email notice template's description, in all four locales, says the notice goes to the previous address so a hijacked session cannot move the account identity unannounced; the internal-headers refusal says a missing header does not announce itself, so the send would succeed while silently deviating from what was authored; the over-limit attachments line says the storage capability holds large content outside the row while the row keeps a reference and the attachment's audit metadata.
19+
- `@objectstack/service-knowledge`: the no-identity retrieval warning says a missing identity is not a grant of authority, so retrieval fails closed rather than searching the whole corpus unscoped; the predicate-write warning says the lifecycle reap guard de-indexes retention-swept rows before they are deleted.
20+
- `@objectstack/service-queue`: the missing-retention refusal says the one platform reaper sweeps completed rows by that declaration, so the adapter does not sweep the table itself; the rejected-floor error says the floor is what makes the lifecycle service refuse an override below the idempotency window.
21+
- `@objectstack/service-sms`: the unreadable-counter warning says a quota the platform cannot count must not refuse the one-time codes users sign in with; the counter store's lines name the daily SMS send quota without a number.
22+
- `@objectstack/service-storage`: the reclamation-gate line says deleting bytes cannot be undone, so it waits for a verified migration with no deviation on record, while reversible work carries on.
23+
- `@objectstack/trigger-record-change`: the array-trigger warning says multi-event arrays are deferred until two independent projects need a combination other than created-or-updated.
24+
25+
Text only: no status, error code, field, route or control flow moves. A client or log filter that matches the old text (for example a tracker-number suffix) needs the new spelling.

‎packages/connectors/connector-mcp/src/mcp-provider.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,16 @@ function assertDeclarativeStdioAllowed(
138138
throw new Error(
139139
`connector-mcp provider: connector '${connectorName}' declares a stdio transport with command '${command}', ` +
140140
`which is not in the host's declarativeStdio allowlist [${policy.join(', ')}]. ` +
141-
`Add the command to new ConnectorMcpPlugin({ declarativeStdio: [...] }) if this server is trusted (#3055).`,
141+
`Add the command to new ConnectorMcpPlugin({ declarativeStdio: [...] }) if this server is trusted: a stdio ` +
142+
`transport launches a local process, so stack metadata may only name a command the host's own code allows.`,
142143
);
143144
}
144145
throw new Error(
145146
`connector-mcp provider: connector '${connectorName}' declares a stdio transport (command '${command}'), ` +
146147
`but declarative stdio transports are disabled by default — a stdio transport launches a local process ` +
147148
`from stack metadata (including runtime Studio publishes). If this server is trusted, opt in deliberately: ` +
148-
`new ConnectorMcpPlugin({ declarativeStdio: ['${command}'] }) — or use an http transport (#3055, ADR-0024 §4).`,
149+
`new ConnectorMcpPlugin({ declarativeStdio: ['${command}'] }) — or use an http transport, which this policy ` +
150+
`does not gate (ADR-0024 §4).`,
149151
);
150152
}
151153

‎packages/plugins/plugin-email/src/email-service.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,8 @@ export class EmailService implements IEmailService {
922922
`EmailService: queue delivery skipped for one message — its attachments total `
923923
+ `${encodedAttachments.totalBytes} bytes, over the ${SYS_EMAIL_ATTACHMENT_LIMIT_BYTES}-byte limit a `
924924
+ 'sys_email row carries, so the message was delivered inline (in-process retries only) rather than '
925-
+ 'queued without them. Content that large is queueable through the storage capability (#5172), '
925+
+ 'queued without them. Content that large is queueable through the storage capability, which holds '
926+
+ 'it outside the row while the row keeps a reference and the attachment\'s audit metadata, '
926927
+ `but ${encodedAttachments.storageDetail ?? 'that path was not attempted for this message'}. `
927928
+ 'Fix: mount the storage capability (@objectstack/service-storage) so large attachments are '
928929
+ 'stored out of the row and the message can be delivered durably.',

‎packages/plugins/plugin-email/src/internal-header-readback.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ export async function readInternalHeadersJson(
124124
throw new Error(
125125
`EmailService: ${SYS_EMAIL_OBJECT}.${HEADERS_COLUMN} is declared \`internal: true\`, but this `
126126
+ 'data engine does not implement resolveInternalField() — the custom headers this message was '
127-
+ 'authored with are stored but cannot be recovered, and a message must not be sent missing them '
128-
+ '(#8149). The row stays `queued`: the queue retry or the next boot outbox sweep delivers it '
127+
+ 'authored with are stored but cannot be recovered, and a message must not be sent missing them: '
128+
+ 'a missing header does not announce itself, so the send would succeed while silently deviating '
129+
+ 'from what was authored. The row stays `queued`: the queue retry or the next boot outbox sweep delivers it '
129130
+ 'intact once an engine that implements the privileged accessor is mounted.',
130131
);
131132
}

‎packages/plugins/plugin-email/src/templates/auth-templates.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ to end other sessions.`,
570570
variables: EMAIL_CHANGE_NOTICE_VARIABLES,
571571
active: true,
572572
isSystem: true,
573-
description: 'Sent to the PREVIOUS address when a change-email request is accepted (#8019). Notification only — never gates the change.',
573+
description: 'Sent to the PREVIOUS address when a change-email request is accepted, so a hijacked session cannot move the account identity unannounced. Notification only — never gates the change.',
574574
};
575575

576576
export const AUTH_EMAIL_CHANGE_NOTICE_TEMPLATE_ZH_CN: EmailTemplate = {
@@ -598,7 +598,7 @@ export const AUTH_EMAIL_CHANGE_NOTICE_TEMPLATE_ZH_CN: EmailTemplate = {
598598
599599
如果这不是您本人的操作,您的账号可能已被入侵。请立即联系您的 {{appName}} 管理员
600600
或支持团队,并修改密码以结束其他会话。`,
601-
description: '在接受变更邮箱请求时发送至原邮箱地址(#8019)。仅为通知,绝不阻断变更流程。',
601+
description: '在接受变更邮箱请求时发送至原邮箱地址,使被劫持的会话无法在原邮箱不知情的情况下转移账号身份。仅为通知,绝不阻断变更流程。',
602602
};
603603

604604
export const AUTH_EMAIL_CHANGE_NOTICE_TEMPLATE_JA_JP: EmailTemplate = {
@@ -627,7 +627,7 @@ export const AUTH_EMAIL_CHANGE_NOTICE_TEMPLATE_JA_JP: EmailTemplate = {
627627
心当たりがない場合、アカウントが不正利用されている可能性があります。直ちに
628628
{{appName}} の管理者またはサポートへご連絡のうえ、パスワードを変更して他の
629629
セッションを終了してください。`,
630-
description: '変更メールの要求が受理された際に変更前のアドレスへ送信されます(#8019)。通知のみで、変更を妨げることはありません。',
630+
description: '変更メールの要求が受理された際に変更前のアドレスへ送信され、乗っ取られたセッションが元のアドレスに知られないままアカウントの識別情報を移せないようにします。通知のみで、変更を妨げることはありません。',
631631
};
632632

633633
export const AUTH_EMAIL_CHANGE_NOTICE_TEMPLATE_ES_ES: EmailTemplate = {
@@ -657,7 +657,7 @@ aprobación.
657657
Si no has solicitado este cambio, tu cuenta podría estar comprometida. Ponte en
658658
contacto de inmediato con el administrador o el equipo de soporte de {{appName}}
659659
y cambia tu contraseña para cerrar las demás sesiones.`,
660-
description: 'Se envía a la dirección ANTERIOR cuando se acepta una solicitud de cambio de correo (#8019). Solo notificación; nunca bloquea el cambio.',
660+
description: 'Se envía a la dirección ANTERIOR cuando se acepta una solicitud de cambio de correo, para que una sesión secuestrada no pueda trasladar la identidad de la cuenta sin aviso. Solo notificación; nunca bloquea el cambio.',
661661
};
662662

663663
/**

‎packages/services/service-i18n/src/i18n-route-ledger.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export interface I18nRouteLedgerEntry {
9595
export const I18N_ROUTE_LEDGER: readonly I18nRouteLedgerEntry[] = [
9696
{ route: 'GET /api/v1/i18n/locales', family: 'i18n', disposition: 'sdk', client: 'i18n.getLocales' },
9797
{ route: 'GET /api/v1/i18n/translations/:locale', family: 'i18n', disposition: 'sdk', client: 'i18n.getTranslations',
98-
note: 'was a wire-level 404 — the client sent /translations?locale=xx, a shape no server mounts (the dispatcher domain body accepts it, but nothing routes a bare /translations to that body). Since #3636 the client sends the path form the spec declares.' },
98+
note: 'was a wire-level 404 — the client sent /translations?locale=xx, a shape no server mounts (the dispatcher domain body accepts it, but nothing routes a bare /translations to that body). The client now sends the path form the spec declares.' },
9999
{ route: 'GET /api/v1/i18n/labels/:object/:locale', family: 'i18n', disposition: 'sdk', client: 'i18n.getFieldLabels',
100-
note: 'ditto — the client sent /labels/:object?locale=xx against a two-path-param mount (#3636).' },
100+
note: 'ditto — the client sent /labels/:object?locale=xx against a two-path-param mount; it now sends both the object and the locale in the path.' },
101101
];

‎packages/services/service-knowledge/src/__tests__/event-sync-data-events.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ describe('#4626 — KnowledgeServicePlugin event sync on data.record.*', () => {
163163
await deliver(BULK_DELETED);
164164

165165
const [message] = harness.ctx.logger.warn.mock.calls.at(-1) as [string];
166-
expect(message).toContain('#4639');
167-
expect(message).toContain('lifecycle reap guard (#4672)');
166+
expect(message).toContain('cannot be repaired from the event stream');
167+
expect(message).toContain('the lifecycle reap guard de-indexes those rows before they are deleted');
168168
expect(message).toContain('application-level predicate writes are not');
169169
});
170170
});

‎packages/services/service-knowledge/src/knowledge-service-plugin.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,9 @@ export class KnowledgeServicePlugin implements Plugin {
217217
`KnowledgeServicePlugin: '${object}' had a predicate write (${type}) affecting ` +
218218
`${typeof matched === 'number' ? matched : 'an unreported number of'} record(s). ` +
219219
'A bulk event carries a count, not records, so the knowledge index for this object ' +
220-
'may now be stale and cannot be repaired from the event stream (#4639). ' +
221-
'Retention-sweep deletes are covered separately by the lifecycle reap guard (#4672); ' +
220+
'may now be stale and cannot be repaired from the event stream. ' +
221+
'Retention-sweep deletes are covered separately: the lifecycle reap guard de-indexes those rows ' +
222+
'before they are deleted; ' +
222223
'application-level predicate writes are not, and need an explicit reindexSource.',
223224
{ object, type, matched },
224225
);

‎packages/services/service-knowledge/src/knowledge-service.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ export class KnowledgeService implements IKnowledgeService {
318318
// No identity → fail closed on object-backed hits (keep file/http hits).
319319
if (!ctx) {
320320
this.options.logger?.warn?.(
321-
'[knowledge] retrieval with no ExecutionContext — dropping object-source hits to stay safe (#2981). ' +
321+
'[knowledge] retrieval with no ExecutionContext — dropping object-source hits to stay safe: a missing ' +
322+
'identity is not a grant of authority, so retrieval fails closed rather than searching the whole corpus unscoped. ' +
322323
'Pass the caller identity (or an explicit system context) to retrieve object-backed knowledge.',
323324
);
324325
return hits.filter((h) => !h.sourceRecordId);

‎packages/services/service-queue/src/db-queue-adapter.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ export function completedRetentionWindowMs(): number {
8989
if (!maxAge) {
9090
throw new Error(
9191
'[service-queue] sys_job_queue no longer declares lifecycle.retention — DbQueueAdapter dedups against '
92-
+ 'terminal rows by `created_at` window and relies on that declared retention to keep them (ADR-0057, #5179). '
93-
+ 'Restore the declaration in @objectstack/platform-objects rather than sweeping the table from here.',
92+
+ 'terminal rows by `created_at` window and relies on that declared retention to keep them (ADR-0057). '
93+
+ 'Restore the declaration in @objectstack/platform-objects rather than sweeping the table from here: '
94+
+ 'the one platform reaper sweeps completed rows by that declaration, and a sweeper here would be a '
95+
+ 'second copy of the window, free to drift from the first.',
9496
);
9597
}
9698
return lifecycleDurationMs(maxAge);

0 commit comments

Comments
 (0)