docs(spec): name the object-scoped bundle key in the validationMessages migration text - #17604
Conversation
…es migration text The ADR-0087 conversion `translation-validation-messages-removed` told an author whose retired `validationMessages` key was rejected to author the message on the rule and stopped there. Since 17.3.0 (#14381, #14253) there is a translation route for that message — `objects.<object_name>._validations.<rule_name>.message`, resolved on the write path — and the sibling prescription in the same package (`TRANSLATION_KEY_GUIDANCE.validationMessages`) already names it. Everything the old text said is true; the defect is silence. It is read by exactly the population that authored the retired key — the authors who wanted translated rule messages — and it steered them to a plain authored literal without telling them the bundle key now exists. Both texts in the file carry the narrow prescription, so both are completed: the conversion `summary` and the docblock above it, which asserted "not translated through a group" directly above the corrected summary. The literal advice is kept in both — it is still correct. `docs/protocol-upgrade-guide.md` is regenerated with `gen:upgrade-guide`, never hand-edited. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
`check:generated` caught `spec-changes.json` stale: the ADR-0087 conversion registry feeds two generated artifacts, not one, and the completed summary renders into both. Regenerated with `gen:spec-changes`, never hand-edited. The changeset is measured, not assumed. `registry.ts` is not a `.zod.ts` so it does not ship as source, but two paths in `packages/spec`'s `files[]` move: the new sentence is emitted into six files under `dist`, and `spec-changes.json` is itself a `files[]` entry. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 135 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 d2904ee076bfc0efacdf3ef3be28f4ccdbc1adb0 && git checkout d2904ee076bfc0efacdf3ef3be28f4ccdbc1adb0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3ef96b47120a989d9333aeeeb6181bc371bc9b52 7293dafada6099a8d73c2252e801aea98ab3d283 && git checkout -B drift-repro 3ef96b47120a989d9333aeeeb6181bc371bc9b52 && git merge --no-ff 7293dafada6099a8d73c2252e801aea98ab3d283
node scripts/docs-audit/affected-docs.mjs --json 3ef96b47120a989d9333aeeeb6181bc371bc9b52
|
|
Seat acceptance — content reviewed, ①②③ read; ④ outstanding.
⛔ Nothing enqueued until ④ is green. ⭐ Three things this round did that the order asked for and that are easy to fakeIt swept for the claim instead of trusting two line numbers. Triage said the fix is two texts in one file and cited the docblock at It found that THREE generated artifacts hang off this registry, not the one the card named. It answered the third surface with a measurement and a reason, not a silent choice. The order required a decision on On the content⭐ The docblock addition does the thing triage actually asked for — it writes the conclusion into the contract text, and it pre-empts the obvious misreading: "It is not Landing proof by content, occurrences via ⛔ No merge of The two out-of-scope items: ⛔ neither filed, both recorded with a named carrier
⇒ This seat agrees with both dispositions and is ⛔ not overriding them into cards. Generated by Claude Code |
Fixes #15437
Clause-②: no — migration prose is completed to name a route that already exists and already works. No accept set moves, no authorable key is added or removed, no behaviour changes. Re-derived from what this PR ships and it holds: the diff is two comment/string texts, three regenerated artifacts and a changeset.
What was wrong: silence, not falsity
The ADR-0087 conversion
translation-validation-messages-removedtells an author whose retiredvalidationMessageskey was rejected to author the message on the rule (object.validations[].message) — and stopped there. Since 17.3.0 (#14381, #14253) that message has a translation route,objects.OBJECT._validations.RULE.message, which the write path resolves, and the sibling prescription ten metres away in the same package (TRANSLATION_KEY_GUIDANCE.validationMessages, the text the strict door actually returns) already names it.Everything the old text said is true, and none of it is deleted. This is the migration text — read by exactly the population that authored the retired key, i.e. the authors who wanted their rule messages translated — and it steered them to a plain authored literal without mentioning that the bundle key now exists. The literal advice stays; the route is added after it.
Two texts in the file, not one
Triage caught what the card did not. The docblock above the conversion asserted that rule messages are "not translated through a group" — landing the summary alone would have left that narrower claim asserted directly above the corrected one. Both are completed.
The docblock keeps its 17.0.0 sentence (still true of the retired key) and then says what 17.3.0 changed, including why the object-scoped group is not
validationMessagesreturning: the retired one was keyed by rule name at the TOP level, could not tell two objects' rules apart, and had no reader.I swept the file rather than taking the two line numbers:
grep -nforvalidations[].message,validationMessages,not translated through a groupand_validationsoverregistry.tsreturns six hits — the two prose texts (both corrected),surface:, thestripKeyscall and the fixture data (all mechanics, untouched).Anchors: cited as measured, on my own merge base
Every figure below was re-measured on my merge base
07f93e089c, not quoted.id:3160:3129summary:3164:3133(now:3143):3152:3121(now:3124)translation.zod.ts:398, record at:432docs/protocol-upgrade-guide.md:283The docblock had drifted 31 lines from the number triage recorded.
Landing proved by content, each with a lit control
grep -ccounts lines, so every count below isgrep -o | wc -l.Source (
packages/spec/src/conversions/registry.ts):Regenerated artifact (
docs/protocol-upgrade-guide.md): lit control 1, new route 1, kept literal 1 — one row, one changed line.A second generated artifact, which the card did not name
check:generatedrefused withspec-changes.json is stale. The conversion registry feeds two generated artifacts, not one, and the completed summary renders into both (twice insidespec-changes.json). Regenerated withgen:spec-changes.This was not luck:
pnpm check:spec-changesis one of the five artifact-roster familiesscripts/pm/dispatch-gates.mjsflags as "roster under packages/spec, which one of your paths is in — there the silence is not evidence in EITHER direction". Reading those rather than treating them as passed is what surfaced it.The changeset is measured, not assumed
The dispatch declined to assert one. Measured on the built tree, a changeset is owed —
patchon@objectstack/spec.registry.tsis not a.zod.ts, so it does not ship as source, but two entries ofpackages/spec'sfiles[]move:dist— the new sentence is emitted into six built files (dist/index.js/.mjs,dist/shared/index.js/.mjs,dist/browser/index.js/.mjs). A negative control string scored 0 on the same tree. An author runningos migrate meta --from 16reads the changed notice out of that runtime string.spec-changes.json— itself a literalfiles[]entry, carrying the summary twice.Precedent agrees:
.changeset/15052-search-fields-docblock-icontains.mdcarries apatchfor exactly this shape — a non-.zod.tsspec source whose published reach is what the build emits.The third surface: measured, and deliberately NOT changed
docs/qa/platform-checklist/areas/i18n.jsonpins the same narrow prescription at:363. The measurement says it is legitimately scoped to the refusal channel, so it does not move with the others — stated here rather than changed silently.The reading, three parts:
/items/3), whose subject is the strict-door refusal channel: step 5 is "verify the guidance channel", the acceptance clause is tagged"oracle": "log", and itsverifyreads "the error texts". Not one of them references the ADR-0087 conversion,migrate meta, or the upgrade guide.TRANSLATION_KEY_GUIDANCE.validationMessages(translation.zod.ts:543), which has named the object-scoped route since 17.3.0. This PR does not touch it.TRANSLATION_KEY_GUIDANCEhas no reference anywhere outsidetranslation.zod.ts, so the checklist clause is prose about that channel, not a pin on this one.So the card's hypothesis — that it "may want the new route naming too" as part of this fix — is not confirmed: its subject is a different text. What is true, and recorded in the acceptance notes below rather than fixed here, is that the clause under-describes its own channel.
验收备注
docs/qa/platform-checklist/areas/i18n.json/items/3/acceptance/3: the clause parenthetical and itsverifyline both stop at the authored literal, while the guidance string they pin has named two homes since 17.3.0. A checklist run would therefore pass a guidance string that had been truncated back to the literal. This is incompleteness in a QA acceptance bar, not a defect that reproduces, so it is not a fileable finding; the carrier is the nextchecklist-testrun of areai18n, or achecklist-authorsweep, both of which read this exact item. Deliberately left for that pass rather than ridden in on apackages/specPR with a different gate family.mainwas performed, so theos-regenmerge driver ondocs/protocol-upgrade-guide.mdwas never exercised and incident [incident] Themerge=os-regendriver exited 0 on a live merge while DISCARDING main's side ofcontent/docs/references/ui/view.mdx— caught and repaired in-round, but a zero exit from that driver is not evidence the merge happened #17602 does not apply to this branch. The branch is cut fromorigin/main07f93e089c, fetched minutes before the first edit. Confirmed routed regardless:git check-attr -a docs/protocol-upgrade-guide.mdreturnsmerge: os-regen.packages/spec/src/ui/**,component-type-vocabulary.ts,packages/spec/src/contracts/**,packages/plugins/plugin-approvals/**,union-author-message-pins.test.ts,action-params.zod.tsorexamples/app-todo/**is in this diff.content/docs/releases/and everyCHANGELOG.mdare untouched.Verification
pnpm --filter '@objectstack/spec...' buildVERDICT command-exit 0(underos-verify-lock.sh)pnpm --filter @objectstack/spec exec vitest run src/conversions/VERDICT command-exit 0— 5 files, 257 tests passedpnpm --filter @objectstack/spec run typecheckVERDICT command-exit 0check:upgrade-guidecheck:generatedspec-changes.json→ 0check-adr-0087-registration --base origin/maincheck:spec-changes·check:nul-bytes·check:doc-authoringcheck:merge-driver·check:published-files·check:affected-docscheck-spec-docblock-symbol-anchors·check-keyed-text-boundscheck-comment-mask-adoption·check-registry-log-declaredcheck-empty-changeset·check-changeset-no-major·check-changeset-fixed·check:objectui-changesetGate families were derived mechanically with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(71 commands) and re-derived after the changeset existed, which added five more. This is a declared narrowing: the targeted set above is the subset whose inputs this diff actually moves; the full farm is CI's run, including the whole-tree, wide-population and path-scheduled families that derivation prints as explicitly NOT covered locally. Every exit code was captured before any pipe.🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code