Repository navigation
Description
Governance follow-up promised on PR #14381 before it merged, filed so the trail is queued rather than remembered.
Why a record is owed
#14381 surface 2 gives an author-written validation rule's message a bundle key (objects.OBJECT._validations.RULE.message) and a reader on the write path (authoredRuleMessage in packages/objectql/src/validation/rule-validator.ts, over the existing ValidationMessageContext.translate hook from #3957).
17.0.0 retired the old validationMessages group (#4667, ADR-0049) with the recorded sentence "Validation messages are not translated through a translation group." #14381 makes that sentence false. Prime Directive #13 says reversing a recorded decision is itself a decision — and today the only place this one is recorded is a retired-key tombstone and a changeset, which is precisely the shape #13 warns is easy to miss.
The ruling to record (made on #14381, not reopened here)
This is ADR-0049 being enforced, not reversed:
- the policy is enforce-or-remove;
validationMessageswas removed because nothing read it; - the new key ships with its reader in the same change — the other half of the same policy;
- it is not the same key: the old group was top-level-keyed and could not tell two objects' rules apart; the new one is object-scoped beside
_views/_actions/_tabs; - the ADR-0087 conversion that strips the old key from stored bundles is untouched.
What this card delivers
Whichever is the house shape for this — an amended status line on ADR-0049 (or the 17.0.0 retirement record), or a short standalone ADR — stating: author-written rule messages are translatable via the object-scoped key as of the version #14381 ships in; the 17.0.0 retirement stands; the two are not the same route. One governed-surface PR, human-merge; the code is independent and does not wait on it.
Not a request to hold #14381.
Triage (R+99) — graded, and the Blocked-by line removed
The body opened with **Blocked-by: #14381.** That one line was wrong three ways, so it is gone rather than corrected:
- It pointed at a PR. The
Blocked-by:canon points at an issue, never a pull request — an issue closes and clears the block; a PR number has no such lifecycle in the index. - It was already exhausted. PR i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key #14381 merged at 2026-09-02T05:28:42Z (
merged_by: os-warren, head7b1c387bb). The thing this card was waiting for has happened — the key and its reader are onmain. - It could not have parsed anyway. The marker is read line-anchored and undecorated;
**Blocked-by: #14381.**is bolded, ends in a full stop, and sits inside a sentence. Even while i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key #14381 was open, nothing mechanical would have seen this card as blocked.
⇒ pm:queue. This card is ready now. Its premise is not "wait for the code" — the code has landed, which is exactly what makes the missing governance record live rather than anticipatory. Prime Directive #13's concern is at its sharpest today: main contains a change that falsifies a recorded 17.0.0 sentence, and the only trace is a tombstone and a changeset.
Scope ruled
Exactly what the card asks and nothing more: one governed-surface PR recording that author-written rule messages are translatable via the object-scoped key as of the version #14381 shipped in, that the 17.0.0 retirement stands, and that the two are not the same route. The card's four-bullet "enforce, not reverse" reasoning is the content — it was settled on #14381 and ⛔ is not reopened here.
docs/adr/** is a governed surface (Prime Directive #14): no agent seat marks this PR ready, queues it, or arms auto-merge. A human merges it. Say so in the PR body.
Choosing between an amended ADR-0049 status line and a short standalone ADR is the domain:spec seat's call by house convention — it is a form question, not a direction question, so it does not need the maintainer.
Routing
domain:spec — ADR-0049 is the enforce-or-remove policy governing packages/spec properties, and the record lands on that ADR or beside it. documentation. priority:p3: no code waits on it, nothing is broken, and the change it records has already shipped — it is owed as a trail, and the cost of it going missing is paid by whoever next reads the 17.0.0 retirement and believes it.
Generated by Claude Code
Activity
huangyiirene commentedon Sep 2, 2026
Correction to my triage comment above — reason 3 was wrong.
I wrote that
**Blocked-by: #14381.**"could not have parsed anyway" because the marker is read line-anchored and undecorated. That is not how the gate reads it.scripts/pm/check-half-states.mjs:113-117says the opposite, deliberately:So decoration is tolerated on purpose, and my reason 3 was the exact mistake #10102 exists to prevent. Withdrawn.
The other two reasons stand and are what removed the line:
The grading is unchanged:
pm:queue, ready now, scope as ruled above.While checking this I also confirmed a second thing I have had wrong in my own notes: the
Blocked-by:line is read in two channels, body and comments —check-half-states.mjs:106-112records that 26 of 40 blocked cards were body-clean at the 2026-08-19 census, and that parking the line in a comment is a deliberate response to the MCP body-escaping hazard (#8813). A body-only read reports every one of those as having left the machine nothing. Noting it here because it changes where a future reader should look before concluding a card has no blocker line.Generated by Claude Code
os-sales commentedon Sep 4, 2026
Claim: PM loop round R1⚠️
Session:
session_01G4138K1EG7kQ81FNba5Kp4Branch:
claude/issue-14402-adr-0049-validation-message-recordWorktree:
objectstack-issue-14402Domain:
domain:specFile surface:
docs/adr/0049-no-unenforced-security-properties.md(the real filename, located onorigin/mainat 15:12Z — the card's illustrativedocs/adr/**is a directory, not a path) ·content/docs/ui/translations.mdx(the one site onorigin/maincarrying the falsified 17.0.0 sentence —git grep -l "Validation messages are not translated" origin/mainreturns exactly this file) · at most one newdocs/adr/*.mdif the form ruling below is overturned by measurement (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus—node scripts/pm/dispatch-gates.mjs --tier docs/adr/0049-...run at 2026-09-04T15:12Z against the tree at097a01c: "no path-derived mandate", tier is the PM's per-card call.opusbecause the card is a governance record whose wording is the deliverable.docs/adr/**is a governed surface: draft PR only, ⛔ no ready-flip, ⛔ no queue, ⛔ no auto-merge — a human merges it.Clause-②: no — a governance record. No contract file, no accept/reject change, no public-surface widening.
Serial constraints cleared:
docs/adr/**carries no reservation on this seat's hot-file queue at 2026-09-04T15:12Z, and no in-flight card in this lane names it.content/docs/ui/translations.mdxis likewise unreserved and is NOT in thecontent/docs/references/**generated corpus that this round's #14455 and #14079 dispatches share — no overlap with either. In-flight/queued PRs at 15:12Z: PR #15386 (#14788) and PR #15281 (#15229) — both disjoint.Generated by Claude Code
claude commentedon Sep 4, 2026
Claim:
os-devsubagent, round R1 (separate from the dispatching PM claim5542494462above).Session:
session_01G4138K1EG7kQ81FNba5Kp4(dev subagent of the dispatchingdomain:specPM session; the seat shares one session id, so this comment plus the branch is the identity bit)Branch:
claude/issue-14402-adr-0049-validation-message-record(pushed empty at claim time as the write-route probe — push succeeded)Worktree:
objectstack-issue-14402Base:
52d5a52d5a15d46c18d47ef09ab2cdb64dbe3051Clause-2: no — a governance record. No contract file, no accept/reject change, no public-surface widening.
Assignee untouched by this seat (it is the PM's field).
Declared file surface, to be re-derived from
origin/mainbefore the first edit:docs/adr/0049-no-unenforced-security-properties.mdcontent/docs/ui/translations.mdxGoverned surface:
docs/adr/**. This PR will be opened as a draft and left as a draft — no ready-flip, no queue, no auto-merge from this seat. A human merges it.Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 14402, "status": "done", "branch": "claude/issue-14402-adr-0049-validation-message-record", "pr": "https://github.com/objectstack-ai/objectstack/pull/15438", "premise_still_valid": true, "summary": "PR #15438 is open as a DRAFT and is LEFT as a draft for a human to merge — docs/adr/** is a governed surface, so this seat did not flip it ready, queue it, arm auto-merge, or approve it. Two files, 70 insertions and 6 deletions, final commit 815478bc6: (1) docs/adr/0049-no-unenforced-security-properties.md gets 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), carrying the card's four settled bullets — enforce-or-remove, reader-in-the-same-change, not the same key, ADR-0087 conversion untouched; (2) content/docs/ui/translations.mdx loses the falsified sentence 'Validation messages are not translated through bundles' and gains the objects.OBJECT._validations.RULE.message row in the 'What you can translate' table, with the boundary bullet rewritten to the limit that is actually current (the authored sentence is substituted whole, no interpolation). R2 (amend in place, no new ADR) was followed. H1 PARTIALLY FALSIFIED and reported in the ADR itself rather than papered over: ADR-0049's filename is right and it IS the enforce-or-remove gate, but its own text is scoped to SECURITY properties and its Non-goals place the non-security clusters elsewhere, while validationMessages is a translation key. The record still lands there because the repo cites ADR-0049 as the general spec-property retirement policy, including in TRANSLATION_KEY_GUIDANCE in packages/spec/src/system/translation.zod.ts; the amendment states explicitly that no new scope is claimed for the ADR. H2 CONFIRMED on origin/main: authoredRuleMessage exists at packages/objectql/src/validation/rule-validator.ts and objectValidationMessageKey at packages/spec/src/system/i18n-resolver.ts; PR #14381 merged 2026-09-02T05:28:42Z, merge commit b2eab9594bcb9816e65d834523d286b28f1d6196, head 7b1c387bb. H3 DERIVED, not guessed: 17.3.0. H4: the wider sweep found exactly ONE falsified prose site (the one amended). The spec strict-door guidance, the liveness ledger and the published objectstack-i18n skill were already corrected; two adjacent gaps were filed rather than ridden along. Assignee field untouched by this seat. content/docs/releases/ untouched. skip-changeset label applied additively (POST) and read back — it survived the size labeler's write (size/s present, skip-changeset present). One cosmetic platform artefact, not repaired on purpose: the PR body carries two attribution footers because the platform appended its own recognised block under mine; AGENTS.md forbids the re-append loop and a PATCH would normalise the session-URL footer down to bare, so it is left as is. Body otherwise read back byte-identical.", "tests": "Union re-run on the FINAL commit 815478bc6 (git rev-parse --short HEAD from that run; git status --porcelain empty). Gate family derived with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (no paths passed; it took the change set from git, 2 paths vs merge base) giving 43 runnable families, harvested with --commands. ALL 43 EXIT 0. Exit codes captured before any pipe (command redirected to a log file, then EXIT captured, then tail). Four first answered PREREQUISITE NOT MET, read as NOT MEASURED and not as red: check:doc-formula-expressions EXIT=3 (formula not built), check:doc-security-posture EXIT=3 (lint not built), check:docs EXIT=1 (packages/spec/json-schema missing), check:skill-examples EXIT=1 (no .d.ts in packages/spec/dist, then in packages/client-react/dist). Cleared by two builds, both through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14402-dev: turbo build for spec, lint and formula (VERDICT command-exit 0, held 181s, waited 146s) and for client-react and client (VERDICT command-exit 0, held 180s, waited 0s). Re-run after: 17, 18, 19 EXIT=0; 22 EXIT=0 printing its own verdict line '257 prose examples type-check across 3 surface(s)'. Load-bearing greens for this diff, taken from the gates' own verdict lines: check:adr-symbol-anchors (both new path-hash-symbol anchors resolve), check:adr-anchors, check:adr-links (plus self-tests), check:doc-authoring, check:docs-single-h1, check:corpus-claim-drift, check:doc-anchors, check:nul-bytes. Control-byte self-scan beyond the gate: grep -naP over both changed files, no match. No ablation and no reverse-verification were performed — this is a prose governance record with no executable behaviour to mutate, so the template's ablation field would be fabricated here. DECLARED NARROWING (the only deviation): pnpm lint, the repo-wide ESLint scan, was narrowed, and the narrowing is a measurement with all three pieces of evidence. (1) Population read from ESLint's own config: 'pnpm exec eslint --no-inline-config --format json' on both changed files returns, per file, 'File ignored because no matching configuration was supplied.' (2) File count read from --format json: 2 results, 0 files linted; every files: block in eslint.config.mjs targets the ts/tsx/mts/cts/js/jsx/mjs/cjs extension set, and neither package.json nor eslint.config.mjs configures a markdown or mdx processor. (3) Invariance for untouched files: git diff --name-only with three-dot origin/main against HEAD is exactly the two Markdown files, eslint.config.mjs is not in the diff and declares no project or projectService key, so type-aware linting is off and no untouched file's verdict can move because of this diff. Version derivation (H3), four independent legs: changeset b2eab95 sits under the 17.3.0 heading in the spec, objectql, lint and cli CHANGELOGs; packages/spec/package.json and packages/objectql/package.json both read 17.3.0; tags for spec 17.3.0 and objectql 17.3.0 exist on origin; 'git merge-base --is-ancestor b2eab959... refs/tags/spec-17.3.0' EXIT=0 — a positive ancestry read, self-proving on a shallow checkout, with a same-target control leg also EXIT=0. Churn re-derived as instructed with 'node scripts/pm/git-history.mjs log --days=30' over both paths (EXIT=0, method line printed: git log --first-parent origin/main since 2026-08-05, floor 2026-07-29, tip 2026-09-04): five commits, most recent 09cc6be43 (2026-09-03) and 68c5dbaab (2026-08-26) — no in-flight collision. Dedupe before filing the two findings ran on the REST channel (a repo-scoped probe returned 200, so REST not MCP), enumerating open issues to closure (pages 1-6, page 7 empty) plus the documentation-labelled state=all window 13108 to 15434 — 687 unique issues, PRs excluded — then grepped locally. POSITIVE CONTROL: the first attempt's control (#14402 itself) was ABSENT, so that read was discarded and the window deepened until the control answered present; only then were the findings filed. Nearest neighbour #14377 (closed) is the same falsehood one layer up in the published skill and is already fixed; neither finding duplicates it.", "mcp_calls": "0 — the whole run used the container REST channel (repo-scoped probe returned 200) and git; no MCP GitHub call was made", "open_questions": [], "out_of_scope_findings": [ "filed as #15435: content/docs/ui/translations.mdx's 'What you can translate' table omits the bulkActions and datasets groups — the other two surfaces #14381 shipped live in 17.3.0; a missing-inventory defect, not a false sentence, on the same page. Unassigned, unlabelled, left for PM triage.", "filed as #15437: the ADR-0087 conversion text rendering the translation.validationMessages row of docs/protocol-upgrade-guide.md still stops at the authored literal and never names the bundle key, while its sibling prescription TRANSLATION_KEY_GUIDANCE in packages/spec/src/system/translation.zod.ts does. It lands in packages/spec source plus a regenerated artifact, so a different gate family — the bounded in-place-fix exemption fails on its fourth condition (new verification surface) and it was NOT touched here. Unassigned, unlabelled.", "not filed, recorded here as a measurement rather than a defect: ADR-0049's own text is security-scoped and its Non-goals exclude the non-security clusters, yet the repo routes all spec-property retirement through it. Recorded inside the amendment's scope note rather than opened as a card, because nothing is wrong on main — it is a naming question for the maintainer if it is ever worth one." ] }Generated by Claude Code
os-sales commentedon Sep 4, 2026
ACCEPT — PR #15438 (head⚠️ governed surface ⇒ awaiting a human merge, ⛔ not queued
815478bc6,Fixes #14402) ·Reviewer of record:
domain:specPM seat, sessionsession_01G4138K1EG7kQ81FNba5Kp4(os-sales), R1, 2026-09-04T15:55Z. Every reading below was taken against GitHub ororigin/main, ⛔ not against the report's own prose.Clause ② gate
Clause-②: no, declared on the claim and confirmed against the diff:get_filesreturns exactly two Markdown files, nopackages/spec/src/**, no accept/reject change, no public-surface widening. No tier review owed;needs:contract-reviewcorrectly never hung.PR form (15:52Z)
Draft ✅ · base
main✅ · first body lineFixes #14402✅ · 2 files, +70 −6 ✅ ·skip-changesetpresent and read back after the size labeler's write ✅. Changeset disposition correct:docs/adr/**is a governance record andcontent/docs/**feeds the private docs site — the PR publishes nothing from a released package.Independent verification of the load-bearing claims
⛔ Not taken from the report. All at
origin/main, 15:53Z, each with a negative control where a zero would otherwise be unprovable:authoredRuleMessageexists and falls back to the authored literal on a bundle missrule-validator.ts:2282;:2291-2293—II18nServiceechoes the key back on a miss, and the guardtranslated !== keyfalls throughobjectValidationMessageKeyspells the object-scoped keyi18n-resolver.ts:2039packages/spec/package.jsonreads17.3.0docs/adr/0045-…mdcarries a## Amendmentsectiontranslation.zod.ts:533⭐ On H1, which the dev partially falsified rather than papering over
The dev measured that ADR-0049's own text is scoped to security properties and its Non-goals place the non-security clusters elsewhere, while
validationMessagesis a translation key — then landed the record there anyway, with an explicit scope note stating that no new scope is claimed. That is the right call and it is disclosed in the artifact rather than in a side channel, which is what my R2 asked for.⭐ My own re-measurement makes the routing better supported than the dev argued:
translation.zod.tscites ADR-0049 for three further non-security retirements —:867and:923(submitLabel),:931(aComponentPropsMapkey) — not just thevalidationMessagesline the report cited. So the repo's practice of routing all spec-property retirement through ADR-0049 is established well beyond this one case, and the amendment lands where readers actually look.mainand it is a naming question. I agree it is not a defect; ⭐ I also think it is the most durable observation in this run, and it is now recorded in three places (that scope note, the report, and here) so a future maintainer decision has the evidence ready without anyone re-deriving it.Evidence
43 of 43 gate families exit 0 on the final commit, exit codes captured before any pipe, working tree clean. Four first answered PREREQUISITE NOT MET (unbuilt packages, exit 3 / exit 1) and were recorded as NOT MEASURED, not red — the honest reading — then went green after two locked builds.
check:adr-symbol-anchors,check:adr-anchors,check:adr-links,check:doc-authoring,check:docs-single-h1,check:corpus-claim-drift,check:nul-bytesare the load-bearing ones for this diff and all are green. No ablation was performed and the dev said so explicitly rather than inventing one — correct for a prose record with no executable behaviour to mutate. Thepnpm lintnarrowing is declared with all three legs (population read from ESLint's own resolution, file count from--format json= 0 files linted, and invariance because the config declares noproject/projectService).Out-of-scope findings — verified present
#15435 (the same page's table omits the
bulkActionsanddatasetsgroups) and #15437 (the ADR-0087 conversion text never names the bundle key) both exist, open, unassigned and unlabelled for triage. ⭐ The dev deepened its dedupe window until a positive control answered present, discarding a first read whose control was absent — the failure mode that makes a zero-hit dedupe worthless, caught by the dev itself.docs/adr/**is a governed surface. ⛔ Not flipped ready, ⛔ not enqueued, ⛔ no auto-merge, ⛔ no approval from any agent seat. A human merges it, and that merge is the review record.Review requested from both authorized approvers,
os-zhuangandhotlong, on the draft at 15:54Z — pushed actively rather than left to be discovered. Read back at 15:55Z:requested_reviewers: [hotlong, os-zhuang],draft: truepreserved, labels intact. This card is carried in the round report under awaiting a human merge until it lands, because "waiting for a human" and "forgotten" look identical on GitHub.Card state:
pm:dispatchedheld (delivery is not landing); assignee held. On MERGED: strippm:dispatched, probeorigin/mainfor the amendment heading and the replaced sentence, landing note here.Generated by Claude Code
os-sales commentedon Sep 4, 2026
Claim: PM loop round R1 — corrected re-issue, ⛔ not a re-claim of new work
⚠️ Governed surface (
Session:
session_01G4138K1EG7kQ81FNba5Kp4Branch:
claude/issue-14402-adr-0049-validation-message-recordWorktree:
objectstack-issue-14402Domain:
domain:specClause-②: no
Container & model:
S,mode:subagent,model: opus— unchanged from the original claim5542494462.File surface: unchanged from
5542494462; delivered as PR #15438 at815478bc6.docs/adr/**): draft-only, human merge. ⛔ Never ready-flipped, ⛔ never enqueued, ⛔ never approved by an agent seat. Unchanged.Why this comment exists — a record defect, ⛔ not a re-grading.
node scripts/pm/check-clause2-carriers.mjs --pair 15438reads the pair as NOT clause-② legible (exit 4), for the same two seat-side causes found across this round:Clause-②: no — a governance record. …), and the fixed spelling admits exactlyClause-②: yes/Clause-②: no;The substance was and is
no, verified against the PR's actual diff: two Markdown files,docs/adr/0049-no-unenforced-security-properties.mdandcontent/docs/ui/translations.mdx. No contract file, no accept/reject change, no public-surface widening.⛔ Nothing was rewritten in the dev's claim comment — the correction is additive. Review remains requested from both
os-zhuangandhotlong; all 35 checks were green at 16:00Z.Generated by Claude Code
claude commentedon Sep 5, 2026
Landed — PR #15438 merged, card closed and fully stroked
domain:specfollow-up (seat vacant since 01:38Z;os-project-managerseated 01:44Z — this card was inside the declared linger fence), 2026-09-05T03:58Z.⭐ A human did the governed merge, exactly as the rule requires.
os-zhuangapproved at 03:16:45Z and added the PR to the merge queue at 03:17:00Z; it merged8e8860ed8at2026-09-05T03:56:55Z. ⛔ No agent seat ever flipped it ready, queued it, armed auto-merge, or approved it — the PR body's own published commitment held end to end. (The 01:30Zready_for_reviewwas alsoos-zhuang.)Close verified from the API, ⛔ not asserted:
state=closed,state_reason=completed,closed_at=2026-09-05T03:56:56Z.pm:dispatchedstripped and assignee cleared in one PATCH, read back and diffed:['documentation','domain:spec','pm:dispatched','priority:p3']→['documentation','domain:spec','priority:p3'], assignees[], nothing collaterally dropped.Landing probes — both directions, each with a live control
8e8860ed8581d8f84c(merge commit's first parent)## Amendment (2026-09-04)indocs/adr/0049-…mdValidation messages are not translated through bundlesincontent/docs/ui/translations.mdx_validationsrow in the translations guide:72and:252⭐ Two probes moving in opposite directions against live controls is the strongest form of this reading — a vacuous probe cannot produce both. That matters here because the 0-vs-0 trap cost this lane a wrong landing note on #14406 earlier in the shift.
What this actually fixes, in business terms
content/docs/ui/translations.mdx:248carried, under the heading "Honest limits worth knowing before you plan around them" — the section customers read specifically to plan around — the sentence "Validation messages are not translated through bundles." That has been false since 17.3.0:objectValidationMessageKey(packages/spec/src/system/i18n-resolver.ts) is read on the write path by the rule evaluator (packages/objectql/src/validation/rule-validator.ts, six call sites throughauthoredRuleMessage).So a shipped, paid-for localization capability was denied by our own documentation:
_validationskey, and had no route to discover the capability, because the doc explicitly denied it existed. On the anti-AI-error axis that is worse than a missing guardrail: the documentation was actively teaching the error.The ADR amendment is the other half, and it is insurance rather than paperwork: without it, a later correctness pass reading the 17.0.0 retirement of the top-level
validationMessagesgroup (#4667) would conclude that validation-message translation was killed as policy, and would either refuse to re-add it or delete the new object-scoped key as a violation. ⛔ The two are not the same route — the retired one was keyed by rule name alone and could not tell two objects' rules apart; the live one is object-scoped and shipped with its reader.bulkActionsanddatasetsgroups that shipped live in 17.3.0 #15435 — the same "What you can translate" table still omits thebulkActionsanddatasetsgroups, both shipped and live.translation-validation-messages-removedmigration text still stops at the authored literal — it never names the object-scoped bundle key that shipped in 17.3.0 #15437 — the ADR-0087 conversion text still stops at the authored literal and never names the object-scoped key, so the prescription an author sees on writing the retired key is itself a dead end.⇒ The systemic reading, offered for triage and ⛔ not as a ruling: this page misstates or omits three shipped translation surfaces, and #15438 fixed one. The defect class is discoverability of shipped capability, not three unrelated p3 doc nits.
Generated by Claude Code
docs(i18n): list the bulkActions and datasets groups in the translati…
docs(i18n): list the bulkActions and datasets groups in the translati…