Uh oh!
There was an error while loading. Please reload this page.
fix(cli,spec,i18n): default-locale bundle tracks the source; promote approval labels into the contract - #8597
Conversation
…approval labels into the contract The i18n extractor's merge branch ran for every locale including the default one, so an existing en entry always beat the source seed: an author's edit to a label/description/help never reached the committed en bundle and the drift gate stayed green (53 stale entries across 6 packages at branch time). The default locale is now rewritten from the source on every extract; translated locales keep merge semantics exactly as before (30 translated bundles verified byte-identical through a fixed regeneration). Contract-first (#8543 ruling, variant C): the five humanized sys_approval_request.status labels lived only in the generated en bundle, so a source-tracking en channel would have regressed them to raw machine values. They are promoted into packages/spec as APPROVAL_STATUS_LABELS beside APPROVAL_STATUSES, and the column derives its options from both — never re-typed at the column (#3786). Same treatment for the sibling sys_approval_action.action (#8580): APPROVAL_ACTION_KIND_LABELS, whose twelve entries previously shipped raw (submit / request_info / ...) because the #7232 humanization missed the field. Guard rail (variant B's axis): bare-string / label-less select options — including the {value, label: value} shape Field.select normalizes bare strings into — now seed through the extractor's derived channel: the value still seeds the skeleton, but inline stays unset, so the coverage gate no longer demands translations of machine identifiers and a copied value cannot masquerade as authored display text. In-repo coverage-ratchet blast radius: zero (12 configs, no count moved). Fixes#8543Fixes#8580 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 8 package(s): 115 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
…no issue-ID citation The corrected Documentation Guardrails row cited the card's issue number in operative text, which check-skill-id-lint rejects: a lesson there must stand on its own (failure mode + discipline + boundary) because a reader hitting the rule cannot go read the issue. The row now says what went wrong (a merged default locale let a stale bundle entry beat an edited source string, drifting silently under a green gate), what to do (edit the source metadata, not the generated en file), and where the boundary sits (translated-locale values stay hand-written and merge-protected) — number dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
…chet caught The two i18n test files live in the package's hidden test layer (tsconfig includes only src), where a frozen TS2835 relative-import cascade makes every bare arrow parameter an implicit any. The three parameters the #8543 tests added (one map in i18n-extract.test.ts, one filter+map pair in i18n-coverage.test.ts) each cost +1 raw tsc error against the shrink-only TEST_DEBT entry (188 -> 191). Structural annotations bring the measurement back to exactly 188; the ledger itself is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
One conflict: packages/plugins/plugin-security/src/translations/en.objects.generated.ts (generated bundle; both sides rewrote overlapping en entries). Staged main's side as a PLACEHOLDER only — the file is generator-owned and the immediately following commit regenerates it from the merged source with the fixed extractor, so the committed content comes from the generator, never from picking a side. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
Discharges the merge commit's placeholder: node scripts/check-i18n-bundles.mjs --write over the merged tree. The result carries both sides — main's #8599 source wording and this branch's source-tracking rewrites — as the extractor's own output, not a hand-picked side. Only the en file changed; the 30 translated-locale bundles were rewritten in place and came back byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
Fixes#8543
Fixes#8580
Implements the 23:51Z maintainer ruling on #8543 — variant C with variant B's authored/derived axis as the guard rail — in one atomic PR, with #8580 folded in (same treatment, same file surface, per the PM's declared fold-in ruling).
What changed
1. Contract-first label promotion (
packages/spec). The five deliberately-humanizedsys_approval_request.statuslabels lived only in the generatedenbundle. They now live in the contract asAPPROVAL_STATUS_LABELS, besideAPPROVAL_STATUSESinsrc/contracts/approval-service.ts, exhaustively checked bysatisfiesin both directions. Same treatment forsys_approval_action.action(#8580):APPROVAL_ACTION_KIND_LABELS— its twelve entries previously shipped raw machine values (submit,request_info, …) because the #7232 humanization pass missed the sibling field. Both columns derive their options from the contract (APPROVAL_STATUSES.map(...)), honouring the #3786 rule that the vocabulary is never re-typed at the column.2. The merge-mode fix (
packages/cli), atomic with the regeneration.extractTranslationsran the merge branch for every locale including the default one, so an existingenentry always beat the source seed: an edit to a label/description/help never reached the committed bundle andcheck:i18nstayed green. The default locale now always takes the source seed; translated locales keep merge semantics exactly as before. The fix cannot land without the regeneration (7enfiles would drift immediately), so both are in this PR: 60 rewrittenenentries — the 53 stale entries measured at branch time (re-measured from the dev report's 51; the +2 delta is drift that accrued onmainsince, inobject.fields.fields.reference.helpTextandobject.fields.fields.trackHistory.helpText), minus the 5 status labels that now match the promoted source, plus the 12 newly humanized action labels.3. Guard rail: derived channel for unauthored option text. Bare-string and label-less select options — including the
{ value, label: value }shapeField.selectnormalizes bare strings into — now seed throughpushDerived: the machine value still seeds the skeleton, butinlinestays unset, so the coverage gate no longer demands "translations" of machine identifiers and a copied value cannot masquerade as authored display text. The sibling coverage-gate contract change this implies is deliberate and pinned intest/i18n-coverage.test.ts(a bundle that externalizes text for a derived key still re-enters the expected set viaauthoredInBundle). Measured in-repo blast radius on the coverage ratchet: zero (12 configs, no count moved in either direction).4. Changeset. Real changeset: minor on
@objectstack/cli; minor on@objectstack/spec(two new public exports — patch would under-declare a surface addition); patch on each package whoseenbundle regenerated.Verification
check:i18ngreen (9 packages, all bundles in sync); baseline was green before the change, so the 60-entry rewrite is exactly the fix's effect.--writeregenerated all 40 bundles in place; the 30zh-CN/ja-JP/es-ESfiles came back byte-identical (30 identical, 0 differing — git saw no change).check:i18n-coveragegreen, zero ratchet movement.check:app-nav-i18ngreen.@objectstack/cli119 files / 1297 passed;@objectstack/spec397 files / 10520 passed;@objectstack/plugin-approvals21 files / 460 passed; platform-objects, plugin-audit, plugin-security, plugin-webhooks, service-messaging suites all green. Typecheck green on spec/cli/plugin-approvals.gen:api-surface,gen:export-origins— purely additive: two new consts);check:generatedgreen.check:i18nstays green (null direction, stated rather than presumed: once bundles equal source, merge-of-identical is identical — the unit test, not the repo gate, pins the conditional);check:i18nred on plugin-approvals (the promotion is what keeps the humanized English alive under the source-tracking channel);scripts/pm/dispatch-gates.mjs): all 15 green, pluscheck:nul-bytes,check:type-check-coverage,check:query-options-erasure.Docs
AGENTS.md's Documentation Guardrails row and the gate's own remedy message described the old semantics ("editing a string is fine" / "no existing translation is overwritten"); both now state the default-locale exception.content/docs/releases/untouched.Generated by Claude Code