Skip to content

refactor(cli): move the pages.* i18n parity verdict off the nav roster onto the page test, and translate the three platform page labels - #16505

Merged
os-sales merged 4 commits into
mainfrom
claude/issue-15743-pages-parity-verdict-move
Sep 7, 2026
Merged

refactor(cli): move the pages.* i18n parity verdict off the nav roster onto the page test, and translate the three platform page labels#16505
os-sales merged 4 commits into
mainfrom
claude/issue-15743-pages-parity-verdict-move

Conversation

@claude

@claude claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #15743

Moves the pages.* i18n parity verdict off the nav roster and onto the page test, then translates the three English platform page labels. Option B, ruled at PR #15739 (5549604828); option A stays refused.

The measurement that justified the move, taken here rather than inherited

With the three bundle entries added and the gate untouched, check:app-nav-i18n returned exactly what the card predicted:

check-app-nav-i18n: 2 problem(s)
  - pages.sys_user_detail — the en bundle declares this page, but the booted
    composition contains no page by that name. ...
  - pages.sys_organization_detail — ... (same orphan verdict)

pages.sys_position_detail verified clean, because plugin-security is in the roster.

Then the population itself, probed off the gate's own boot:

composition pages: ["cloud_connection_settings","connect_agent","marketplace_installed","sys_position_detail"]

Four pages in the composition the gate boots; six shipped by the platform. That is the defect this card names — one roster silently serving two populations — measured rather than argued. @objectstack/plugin-auth contributes the missing two and cannot join a NAV roster at all: new AuthPlugin({}) refuses to boot without a secret, and its only nav contribution (nav_sso_providers) is conditional, so it can never satisfy the at-least-one-nav-id invariant the roster is built on.

What moved

Out of packages/cli/scripts/check-app-nav-i18n.mjs: the pages.* header section, DEFAULT_LOCALE, PAGE_HEADER_COMPONENT, pageSourceCopy, defaultLocalePageDrift, renderPageFinding, the pageAuthoredBy map, verdict bucket 3, its footer, its --self-test cases, and the pages.* clause of the OK line. The nav gate keeps nav. A pointer block records where the verdict went and why, and refuses option A by name.

Into packages/cli/test/platform-page-i18n-parity.test.ts: the same three finding kinds (drift / orphan / no-source), verbatim in behaviour, plus every red-shape control the gate ran under --self-test, now ordinary test cases. A verdict moved without them would be a verdict nobody has seen fail.

The move is a coverage increase, not a trade: the test reads its population from the plugins' UI bundles and the @objectstack/platform-objects/pages barrel — six pages, a strict superset of the four the roster could reach — and needs no boot, no credential and no nav id. It also gains the reverse direction the gate never had: a page in the population with no pages.* entry now reds, which is the exact shape of the debt below.

The nine units

User / Organization / Position are page-level labels on sys_user_detail, sys_organization_detail and sys_position_detail. All three pages author regions: [], so the shared walk offers exactly one key each; nothing else on them is bundle-addressable. Entries land in all four locale files (en mirrors the source literal, as that section already does for the plugin pages), and the recorded source hashes go in alongside — computed with the package's own collectSourceHashes(en), never by hand, so a later edit to an English literal marks the translations stale instead of serving a translation of a string that no longer exists.

The es-ES term is Posición, the spelling the bundle already uses (nav_positions: 'Posiciones', and Posición in the generated metadata-forms bundle) rather than Puestos, which appears once in a page file.

packages/spec/src/system/i18n-resolver.ts is untouched. The 45 authored labels under slots.* are fenced by the 2026-08-06 maintainer ruling recorded in translation.zod.ts, and widening the walk here would reverse it in passing. Scope was 3 keys × 3 locales and stayed there.

Two ownership pointers this change falsified, corrected in the same diff

  • platform-objects/src/apps/translations/source-hash.ts named check:app-nav-i18n as what holds pages.* in parity with its sources. It no longer does.
  • platform-objects/scripts/i18n-extract.config.ts lumped pages in with apps / dashboards under that same gate.

Verification

  • Ablation, both legs proven. Mutating en's sys_organization_detail label and reverting zh-CN's sys_user_detail to English turned three assertions red: the moved parity verdict, the non-English-label claim, and — unpredicted, and correct — the end-to-end translatePage case. ablation-dist-preflight confirmed the marker live in 6 built files before the run was read, and absent from all 66 after the restore rebuild. The restore leg is proven by git diff HEAD empty, not by an exit code.
  • Gates. All 47 families from dispatch-gates.mjs --commands run and green, asserted against the script's own Reconciliation line total of 47 (34 by path + 8 by kind + 7 whole-tree, 2 reached both ways). The Artifact rosters block, which sits outside that total, run separately: 36 of 39 green; 3 NOT MEASURED and none of them a finding — check-partof-closing-keyword and check-single-claim-paths print NOT WIRED without PR context, and check:react-declaration-parity refuses without an objectui SDUI manifest.
  • Three gates needed their prerequisite cleared before their verdict meant anything: check:i18n (the 10-package extract closure — now 9 package(s) in sync), check:dual-build-cjs-loads (a full build), and check:type-check-debt, which OOM'd under --max-old-space-size=4096 and passes at 6144, the CI-shaped ceiling the gate itself pins.
  • @objectstack/platform-objects: 539 tests, 36 files, plus typecheck. @objectstack/cli unit tier: 2478 passed / 6 expected-fail across 182 files, plus typecheck.
  • Lint was not narrowed. The whole population ran: pnpm lint at ac80f66ca exits 0 over 6253 files, 0 errors and 0 warnings, measured from eslint's own --format json output.

Scope notes

Clause ② grades no, from the delivered diff rather than from expectation. Nothing lands in packages/spec/src/** and no symbol is newly exported. The three bundle entries are new keys on a published payload, but they widen no accept set: TranslationDataSchema.pages is already z.record(z.string(), strictObject(...)), and the only member key used, label, is already declared — so neither the accepted key set nor the accepted value set moved. Stated explicitly so the grading can be overturned if a reviewer reads it the other way.

No out-of-scope findings were filed. packages/cli/scripts/** and packages/cli/test/** are not published surfaces (files: ['dist', 'README.md', 'CHANGELOG.md']), so the release is @objectstack/platform-objects alone, at minor.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ


Generated by Claude Code

`User` / `Organization` / `Position` are page-level `label` members on
`sys_user_detail`, `sys_organization_detail` and `sys_position_detail`.
They rendered English in every locale: the pages author `regions: []`, so
the extractor offers exactly one key each and nothing had a bundle entry.
Adds `pages.*` entries in all four shipped locales.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
`check:app-nav-i18n` carried a second, unrelated assertion: that the
default locale's `pages.*` section still says what the composed page
metadata says. Its `CONTRIBUTORS` roster is a NAV roster — every entry
must land a nav id, and each is booted to get them — so it was one
roster serving two populations, and the two are not the same set.
Measured: the booted composition holds four pages while the platform
ships six.

The verdict moves to `platform-page-i18n-parity.test.ts`, which reads
its population from the plugins' UI bundles and the platform's `pages`
barrel and needs no boot. Its three finding kinds and every red-shape
control the gate ran under `--self-test` move with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/platform-objects/src/apps/translations/source-hash.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/platform-objects/src/apps/translations/source-hash.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9bcd9bef8a8efc1d788c8607bdd3a6c94adf6d9dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0539a05b6c069a286bf7837578356282d2c4ae0f — the merge of head ac80f66caa802b2db7b6692be0460da31aa7e684 into base 9bcd9bef8a8efc1d788c8607bdd3a6c94adf6d9d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0539a05b6c069a286bf7837578356282d2c4ae0f && git checkout 0539a05b6c069a286bf7837578356282d2c4ae0f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9bcd9bef8a8efc1d788c8607bdd3a6c94adf6d9d ac80f66caa802b2db7b6692be0460da31aa7e684 && git checkout -B drift-repro 9bcd9bef8a8efc1d788c8607bdd3a6c94adf6d9d && git merge --no-ff ac80f66caa802b2db7b6692be0460da31aa7e684

node scripts/docs-audit/affected-docs.mjs --json 9bcd9bef8a8efc1d788c8607bdd3a6c94adf6d9d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move the pages.* i18n parity verdict off the nav roster onto the page test, then translate the three English platform page labels

2 participants