Skip to content

docs(adr): record that author-written validation-rule messages are translatable again (ADR-0049 amendment) - #15438

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14402-adr-0049-validation-message-record
Sep 5, 2026
Merged

docs(adr): record that author-written validation-rule messages are translatable again (ADR-0049 amendment)#15438
os-zhuang merged 1 commit into
mainfrom
claude/issue-14402-adr-0049-validation-message-record

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #14402

Key placeholders below are written OBJECT / RULE / VIEW rather than in angle brackets — GitHub's body sanitizer strips angle-bracket-shaped fragments.

⚠️ Governed surface — this PR stays a draft

docs/adr/** is a governed surface (Prime Directive #14). No agent seat marks this PR ready, queues it, arms auto-merge on it, or approves it. A human merges it, and that merge is the review record. It is left as a draft deliberately; a draft here is the finished state of the task, not an unfinished one.

What this records

PR #14381 (issue #14253) gave an author-written validation rule's message a bundle key, objects.OBJECT._validations.RULE.message, with its reader on the write path in the same change. That made a recorded 17.0.0 sentence false, and until now the only trace of the decision it falsified was a retired-key tombstone and a changeset — the shape Prime Directive #13 warns is easy to miss.

The ruling being recorded was settled on #14381 and is not reopened here: this is ADR-0049 being enforced, not reversed. The 17.0.0 retirement of the top-level validationMessages group (#4667) stands, and the two are not the same route.

Form: amended in place, no new ADR

House convention left the choice of form to the domain:spec seat. The seat ruled amend the existing records, on this reasoning: the reader who is harmed is the one who lands on ADR-0049 or on the 17.0.0 retirement sentence and believes it. That reader has no reason to go looking for a new ADR, so a new document would leave the false sentence sitting exactly where it is read. Amending puts the correction on the path the misled reader is already walking, and it is the non-proliferating option.

Two sites, and the second is the one that carried the falsehood

1. docs/adr/0049-no-unenforced-security-properties.md — a status-line note plus a dated ## Amendment (2026-09-04) section in this repo's existing amendment shape (ADR-0045 is the worked example). It carries the card's four-bullet reasoning, all four verified against main rather than restated:

  • the policy is enforce-or-remove and validationMessages was removed because nothing read it;
  • the new key ships with its reader in the same change — packages/objectql/src/validation/rule-validator.ts#authoredRuleMessage, over the existing ValidationMessageContext.translate hook (Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957);
  • it is not the same key — the retired group was top-level-keyed and could not tell two objects' rules apart; the new one is object-scoped, spelled by packages/spec/src/system/i18n-resolver.ts#objectValidationMessageKey;
  • the ADR-0087 conversion that strips the old key is untouched.

The amendment also carries a scope note, because measurement contradicted the routing premise in a way worth recording rather than papering over: ADR-0049's own text is scoped to security properties, and its Non-goals place the non-security clusters elsewhere — validationMessages is a translation key. The record still lands here because the repo cites this ADR as the enforce-or-remove policy for spec-property retirement generally, including in the prescription an author sees on writing the retired key (packages/spec/src/system/translation.zod.ts#TRANSLATION_KEY_GUIDANCE). The amendment says explicitly that no new scope is claimed for the ADR.

2. content/docs/ui/translations.mdx — the only file on main carrying the falsified sentence. Under "Current boundaries" it read "Validation messages are not translated through bundles." Replaced with the limit that is actually current — the authored sentence is substituted whole, with no {variable} interpolation, because an authored message has no parameter contract — plus the record of what was retired and why the new route is not it. The "What you can translate" table gains the _validations row.

Version: 17.3.0, derived not guessed

The card required naming the version #14381 shipped in and the dispatching seat had not measured it.

  • The changeset b2eab95 sits under ## 17.3.0 in packages/spec/CHANGELOG.md, packages/objectql/CHANGELOG.md, packages/lint/CHANGELOG.md and packages/cli/CHANGELOG.md.
  • packages/spec/package.json and packages/objectql/package.json both read 17.3.0, so the version bump has landed.
  • Tags @objectstack/spec@17.3.0 and @objectstack/objectql@17.3.0 exist on origin.
  • git merge-base --is-ancestor b2eab9594bcb9816e65d834523d286b28f1d6196 refs/tags/@objectstack/spec@17.3.0 exits 0 — a positive ancestry read, which is self-proving on a shallow checkout (a control leg on the same target answered 0 as required).

Verification — union re-run on the final commit 815478bc6

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derived the change set from git and named 43 runnable families. All 43 were run against this exact tree, exit codes captured before any pipe, working tree clean:

  • 43 / 43 exit 0.
  • Four of them first answered PREREQUISITE NOT MET (unbuilt packages) rather than red — check:doc-formula-expressions and check:doc-security-posture exit 3, check:docs (missing packages/spec/json-schema) and check:skill-examples (missing .d.ts in packages/spec/dist, then in packages/client-react/dist) exit 1. Those are NOT MEASURED, not failures. After turbo run build for @objectstack/spec, @objectstack/lint, @objectstack/formula, @objectstack/client-react and @objectstack/client (both build runs through scripts/pm/os-verify-lock.sh, VERDICT command-exit 0), all four print their own green verdicts — check:skill-examples ends ✅ 257 prose examples type-check across 3 surface(s).
  • Load-bearing for this diff: check:adr-symbol-anchors green (the two new path#symbol anchors resolve), check:adr-anchors, check:adr-links, check:doc-authoring, check:docs-single-h1, check:corpus-claim-drift, check:nul-bytes.
  • grep -naP for control bytes over both changed files: no match.
  • No generated artifact moved: git status --porcelain is empty after the spec build, so gen:schema rewrote nothing that needed committing.

Declared narrowing: pnpm lint

The repo-wide ESLint scan is CI's run. It was narrowed here, and the narrowing is a measurement rather than a skip:

  1. Population read from ESLint's own config, not from a guess. pnpm exec eslint --no-inline-config --format json on both changed files returns, for each, "File ignored because no matching configuration was supplied." — ESLint's own resolution, not an assumption about which files count.
  2. File count read from --format json: 2 results, 0 files linted. Every files: block in eslint.config.mjs targets {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no markdown or MDX processor is configured (package.json and eslint.config.mjs contain no markdown/mdx entry).
  3. Invariance for untouched files. The diff is exactly two Markdown files (git diff --name-only origin/main...HEAD) and contains no eslint.config.mjs change and no file in ESLint's population. eslint.config.mjs declares no project: / projectService:, so type-aware linting is not enabled and no untouched file's verdict can move because of this diff.

Changeset

None. This PR publishes nothing from any released package: docs/adr/** is a governance record and content/docs/** feeds the private @objectstack/docs site. The skip-changeset label is applied by this seat, additively, and read back.

Out-of-scope findings, filed rather than ridden along

Neither is touched here. content/docs/releases/ is untouched: release notes are written centrally at release time.


🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

…able again

ADR-0049 gets a status-line note and a dated amendment recording that an
author-written validation rule's `message` became translatable in 17.3.0
under the object-scoped bundle key
`objects.<object_name>._validations.<rule_name>.message` — the enforce-or-remove
gate being enforced, not reversed. The 17.0.0 retirement of the top-level
`validationMessages` group stands and the two are not the same route.

`content/docs/ui/translations.mdx` carried the sentence that change falsified
("Validation messages are not translated through bundles"). The surface table
gains the `_validations` row and the boundary bullet is rewritten to the limit
that is actually current: the authored sentence is substituted whole, with no
interpolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Record that author-written validation-rule messages are translatable again (objects.OBJECT._validations.RULE.message) — ADR status line or short ADR

2 participants