fix(mcp, email, knowledge, queue, sms, storage, trigger, i18n): runtime strings state each decision in words instead of a tracker number (stage 2) - #21311
Conversation
…me strings state each decision in words instead of a tracker number Stage 2 of the services lane: every ledgered tracker-number occurrence in connector-mcp, plugin-email, service-storage, service-knowledge, service-queue, service-sms, trigger-record-change and service-i18n (22 occurrences in 21 string sites) is gone. 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. The change-email notice template description changes in all four locales. The doc-authoring ledger is recomputed with --census-ledger: the 14 file blocks of these packages are deleted and no other row moves. Four test assertions that pinned an id now pin the sentence that replaced it. Text only: no status, error code, field, route or control flow moves. Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 8 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 17 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2c096c8029f9572d8185b8ec5bf821bd1042f5c4 && git checkout 2c096c8029f9572d8185b8ec5bf821bd1042f5c4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 393ae878d3d52fe843c56b4621c004b934dcf853 7bcf2f613f595e00d9ede5742fffa52263842abf && git checkout -B drift-repro 393ae878d3d52fe843c56b4621c004b934dcf853 && git merge --no-ff 7bcf2f613f595e00d9ede5742fffa52263842abf
node scripts/docs-audit/affected-docs.mjs --json 393ae878d3d52fe843c56b4621c004b934dcf853
|
Part of #20751
Clause-②: no
Stage 2 of the
domain:serviceslane under the maintainer's A / A ruling (5902360492): theconnector-mcp,plugin-email,service-storage,service-knowledge,service-queue,service-sms,trigger-record-changeandservice-i18nstrings. The card stays open for the later stages, so this PR carries no closing keyword. Text only: no status, errorcode, 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 onorigin/mainat383a00c7, not from the card's table:plugin-email6,service-storage4,service-knowledge3,connector-mcp2,service-queue2,service-sms2,service-i18n2,trigger-record-change1. 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.
connector-mcpmcp-provider.ts:142, allowlist-miss refusal (fatal at boot, skipped and logged on a runtime publish)connector-mcpmcp-provider.ts:149, default-deny refusalplugin-emailtemplates/auth-templates.ts:573,:601,:630,:660, theauth.email_change_noticedescription seeded intosys_email_template(en-US, zh-CN, ja-JP, es-ES)plugin-emailinternal-header-readback.ts:128, thrown when the engine cannot dereferenceheaders_jsonservice-queuedb-queue-adapter.ts:93, thrown whensys_job_queueloses its retention declarationtrigger-record-changerecord-change-trigger.ts:240, array-trigger warning to the flow authorservice-knowledgeknowledge-service.ts:321, no-identity retrieval warningservice-knowledgeknowledge-service-plugin.ts:221, predicate-write warningservice-queuequeue-service-plugin.ts:160, rejected-floor errorservice-smssms-daily-quota.ts:327, unreadable-counter warningservice-storageattachment-lifecycle.ts:529, reclamation-gate lineplugin-emailemail-service.ts:925, over-limit attachments lineservice-storagestorage-route-ledger.ts:113, download-URL row note (guard data, not shipped)0bab8bb45(the service-storage protocol stays canonical), and the later retirement of the dispatcher bridge recorded inpackages/runtime/src/route-ledger.tsCitation only (the sentence already stated the decision)
service-knowledgeknowledge-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-smssms-plugin.ts:170(2814): the counter store'ssubjectis now "daily SMS send quota". It is prose only, interpolated into the store's bind and fallback log lines, never a key.service-i18ni18n-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 againsti18n.getTranslations/i18n.getFieldLabelsinpackages/client/src/index.ts).service-storagestorage-route-ledger.ts:113(second half) and:119(3689, "moved into the declared envelope"; "retired when every storage route moved to the declared envelope,okbeing a private second word forsuccess").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:i18nreads "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.383a00c7)plugin-emailservice-storageservice-knowledgeconnector-mcpservice-queueservice-smsservice-i18ntrigger-record-changepnpm check:doc-authoringat 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.mjsis untouched.Changeset
.changeset/20751-services-strings-stage2-state-the-decision.md:patchfor@objectstack/connector-mcp,@objectstack/plugin-email,@objectstack/service-knowledge,@objectstack/service-queue,@objectstack/service-sms,@objectstack/service-storageand@objectstack/trigger-record-change.Measured after building at the head:
plugin-email's four descriptions read back from the built module's exportedAUTH_EMAIL_CHANGE_NOTICE_TEMPLATES, all four id-free)..js/.mjs/.cjsfinds 0 tracker ids. Control: the same scan reads 86 inplugin-security's built output and 66 inservice-automation's.@objectstack/service-i18nis deliberately NOT in the changeset. Its only change is the route ledger, which no entry imports:I18N_ROUTE_LEDGERand the new note are in 0 files of itsdist/(positive control:registerRoutesin 4). The same holds forservice-storage's ledger (STORAGE_ROUTE_LEDGERin 0, controlreap guard: keptin 2), so that package's patch is forattachment-lifecycle.tsalone.Text-only proof
A TypeScript-AST skeleton of each changed non-test
.tsfile: 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).383a00c7against the head: 14 of 14 SAME, node counts identical per file. Control on scratch copies ofmcp-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')andtoContain('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-changearray-form-refusal-end-to-end.test.ts:116andrecord-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
codeorstatusassertion 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, throughscripts/pm/os-verify-lock.sh, eachVERDICT command-exit 0:turbo run buildover the eight packages and their dependency closure (37/37), thenturbo run build --filter=./packages/* --filter=./packages/*/*(71/71) for the dist-reading gates.vitest run --maxWorkers=2per package:plugin-email31 files, 510 tests;service-storage41 files, 629 tests;trigger-record-change10 files, 101 tests;service-queue5 files, 77 tests;service-i18n5 files, 74 tests;service-sms5 files, 74 tests;service-knowledge4 files, 44 tests;connector-mcp3 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.typecheckfor all eight, exit 0, includingcheck:test-typecheck: OKwhere the package wires it.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) at7bcf2f613: 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-loadsandcheck:i18nfirst answeredPREREQUISITE 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-closure71 built packages, 167/167 declaration files;check:sourcemap-no-sources-content68 packages, 522 maps;check:lean-entry-closureandcheck:published-filesgreen.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.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-mirrorsand 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) at7bcf2f613: exit 0, 128 s under the lock.AGENTS.md; the tree stayed clean throughout.Acceptance notes
origin/mainmoved 8 commits to393ae878after 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 withmain; the queue rebuilds it there.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:557carries the same 3457 deferral in the lint hint, and its test pins the id (validate-flow-trigger-readiness.test.ts:738).packages/lintis 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.Generated by Claude Code