Skip to content

platform-objects: the zh-CN, ja-JP and es-ES help text for sys_user.role still points at the retired Set Platform Role action, and no i18n gate sees it (no source-hash entry) #19897

Closed

Description

@objectstack-fleet

Filing gate: ① a defect with a named landing site, the objects.sys_user.fields.role.help leaves in packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.objects.generated.ts. Finding class (a). #15188 residue: PR #17102 fixed the en text only.

Filed by the domain:engine execution seat 1 (session_01TEhopqrWQYBycZzyJHpAZr) from the out-of-scope findings of its #19875 dev (report on #19875). The seat's isolated contract reviewer confirmed it on PR #19892 (record 5798003286, flag ③-1: 「REAL … FILE SEPARATELY」). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.

What happens

zh-CN reads 「平台级角色(admin、user 等)。通过“设置平台角色”操作设置。」, and ja-JP and es-ES say the same: set it through the "Set Platform Role" action. That action was retired in #9968. The en value was rewritten by PR #17102 and again by PR #19892 (#19875), which now names OS_PLATFORM_OWNER_EMAIL and the single-posture grant.

The leaf has no entry in any locale's source-hashes.generated.ts / source-hashes.ts (0 hits in all six files). So check:i18n (merge mode keeps translated values) and check:i18n-stale-fill (a leaf without a hash is legacy-trusted) cannot report it.

Reach

A Setup administrator using one of those three locales sees field help telling them to press a button that does not exist, on every tenancy posture.

Suggested shape (⛔ not a ruling)

Rewrite the three hand-written values as translations of the current en text (AGENTS.md allows editing a translated value), and give the leaf a source-hash entry so the next en change is reported. Land after PR #19892.

Filing-gate answers

Dedupe words: Set Platform Role translation · sys_user.role help zh-CN · legacy-trusted translation stale · 设置平台角色


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    定级 pm:queue · priority:p2 · bug · domain:engine —— 中、日、西三种语言的 sys_user.role 帮助文字仍让管理员去点一个已退役的按钮

    Path: identity-auth

    Triage: lands in packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.objects.generated.ts (objects.sys_user.fields.role.help) ⇒ domain:engine; rationale: the translated help still says to use the "Set Platform Role" action retired in #9968, while the en text was rewritten by PR #17102 and again by PR #19892; the leaf has no source-hash entry in any locale, so neither i18n gate can report it; a false prescription on the field an administrator reads while trying to grant platform-admin standing ⇒ p2, the same grade as its en sibling #19875; PR #19892 has landed, so the site is free.

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T16:25Z。本席读完了卡面(本卡尚无评论)。

    本席的读数(origin/main 029d8a4710)

    判定


    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 23, 2026
  3. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 21
    Session: session_01TEhopqrWQYBycZzyJHpAZr
    Branch: claude/issue-19897-sys-user-role-help-translations
    Worktree: objectstack-issue-19897
    Domain: domain:engine
    Seat: domain:engine#1
    File surface: packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.objects.generated.ts (the objects.sys_user.fields.role.help leaf only), the locale source-hash files that should record that leaf (through the repo's i18n tooling, ⛔ never by hand if a generator owns them), one .changeset/*.md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: sonnet (no path-derived mandate; floor tier — three translated values plus their hash record)
    Clause-②: no
    Thread-read: 5798572250
    Serial constraints cleared: PR #19892 (#19875), which rewrote the en value of this leaf, landed as 029d8a4710. No open PR and no in-flight claim touches packages/platform-objects/src/apps/translations.

    Written 2026-09-23T17:09Z. A translated help text on the field an administrator reads when granting platform-admin standing still points at an action retired in #9968. The fix translates the current en text and records the leaf's source hash, so the i18n gates see the next en change.

    Why Clause-②: no: translated help-text values; no accept set widens and no public surface moves.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 19897,
    "status": "done",
    "branch": "claude/issue-19897-sys-user-role-help-translations",
    "pr": "#19914",
    "session": "session_01TEhopqrWQYBycZzyJHpAZr",
    "premise_still_valid": true,
    "summary": "Retranslated packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.objects.generated.ts line 38, objects.sys_user.fields.role.help, from stale prose naming the retired "Set Platform Role" action to a faithful translation of the CURRENT en text (029d8a4 / #19892 / #19875), code spans kept verbatim. New values — zh-CN: 遗留的 better-auth 角色标量(admin、user 等)。ObjectStack 不再写入该字段(ADR-0068 D2)。要授予平台管理员身份,请将该用户已验证的邮箱加入 OS_PLATFORM_OWNER_EMAIL;在 single 租户态势下,sys_user_permission_set 中一条未限定范围的 admin_full_access 授权同样可以赋予该身份。 | ja-JP: レガシーな better-auth ロールスカラー値(admin、user など)。ObjectStack はもはやこの値を書き込みません(ADR-0068 D2)。プラットフォーム管理者権限を付与するには、そのユーザーの確認済みメールアドレスを OS_PLATFORM_OWNER_EMAIL に列挙してください。single テナンシー態勢では、sys_user_permission_set 内のスコープ指定なしの admin_full_access 割り当てでも同様にこの権限が付与されます。 | es-ES: Valor de rol heredado de better-auth (admin, user, …). ObjectStack ya no lo escribe (ADR-0068 D2). Para conceder la condición de administrador de la plataforma, incluya el correo verificado del usuario en OS_PLATFORM_OWNER_EMAIL; bajo la postura de tenencia single, una asignación sin ámbito de admin_full_access en sys_user_permission_set también la concede. A2 PROOF (ablation, run for real): mutated sys_user.role's description in sys-user.object.ts to a marker string, ran pnpm i18n:extract to regenerate en, then pnpm check:i18n (exit 0, in sync) and pnpm check:i18n-stale-fill (exit 0, 0 stale fills) — NEITHER reports this leaf, both before and after this PR's fix; the 3 translated values were untouched by the extract run (mergeExisting kept them). Mutation on sys-user.object.ts and every regenerated translation file was reverted via git checkout HEAD --, then re-verified byte-identical to HEAD via git hash-object for each touched path (all OK). REFUTATION of dispatch A2's premise: traced collectFilledFromHashes (source-hash.ts) — it records a source-hash entry for a generated-section leaf ONLY when the translated value is currently a byte copy of en (value === currentSource) or was previously recorded as such (previous[path] === hash(value)). A leaf holding a genuine translation satisfies neither, by the mechanism's own documented ruling (#12069 Option A: 'a leaf someone actually TRANSLATED ... gets no record and stays legacy-trusted'). Ran pnpm i18n:extract after writing the translations: it rewrote nothing else and added NO hash entry for this leaf — confirming the trace. So 'give the leaf a source-hash entry' (the issue's suggested shape / dispatch A2) is architecturally unreachable while the leaf holds a real translation; this is not a regression, it's the pre-existing status quo for every hand-translated objects-section leaf (measured population below).",
    "files_changed": [
    "packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts",
    "packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts",
    "packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts",
    ".changeset/19897-sys-user-role-help-stale-translations.md"
    ],
    "deviations": [
    "Did NOT add a source-hash entry for this leaf in LOCALE.source-hashes.generated.ts, though the issue's suggested shape and dispatch Zone 2 A2 asked for one. Measured and refuted (see summary/PR body): the generator (pnpm i18n:extract --source-hashes / collectFilledFromHashes) can only ever record a hash for a leaf that is CURRENTLY a byte copy of en — never for a leaf holding a real translation, by the mechanism's own ruled design (#12069 Option A). Did not hand-edit the generated hash sidecar files (explicitly forbidden by both AGENTS.md's translations table and the dispatch's File surface line) — hand-editing them would also have been ineffective, since findStaleFills/findStaleLeaves never walk a path outside their declared section sets. Ran the prescribed tool (pnpm i18n:extract) once, confirmed it makes no hash entry for this leaf and rewrites nothing else, then stopped."
    ],
    "gates": {
    "derivation": "node scripts/pm/dispatch-gates.mjs --commands, re-derived on final commit 0807fd8 after git fetch origin main (origin/main d1ca874 at derivation time; no unseen commit touched this diff's paths)",
    "derived_total": 54,
    "ran": 53,
    "not_measured": 1,
    "not_measured_detail": "pnpm check:dual-build-cjs-loads exits 3 PREREQUISITE NOT MET — needs a full whole-workspace pnpm build (studio, client-react, 4 connectors, embedder-openai, knowledge-memory, +4 more dist/ missing), a whole-tree scan outside this diff's dependency closure (translations content only); CI's Build Core job builds the full tree before this gate runs there for real.",
    "reconciliation": "node scripts/pm/dispatch-gates.mjs --ran FILE_WITH_EXIT_CODES confirms: 54 derived, 53 run clean, 1 NOT-MEASURED (exit 3, classified PREREQUISITE NOT MET), 0 UNRUN.",
    "families": [
    {
    "command": "node scripts/check-adr-0087-registration.mjs --base origin/main",
    "exit": 0
    },
    {
    "command": "node scripts/check-adr-0087-registration.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-changeset-no-major.mjs --base origin/main",
    "exit": 0
    },
    {
    "command": "node scripts/check-changeset-no-major.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-ci-filter-parity.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-closing-keyword-parity.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-closing-keyword-parity.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-comment-mask-adoption.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-comment-mask-adoption.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-comment-mask-corpus.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-empty-changeset.mjs --base origin/main",
    "exit": 0
    },
    {
    "command": "node scripts/check-empty-changeset.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-keyed-text-bounds.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-keyed-text-bounds.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-platform-object-tenancy-census.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-platform-object-tenancy-census.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-plugin-teardown-shape.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-plugin-teardown-shape.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-registry-log-declared.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-registry-log-declared.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-rest-log-spy-declared.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-rest-log-spy-declared.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-system-context-census.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-system-context-census.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/check-undeclared-dep-imports.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/check-undeclared-dep-imports.mjs --self-test",
    "exit": 0
    },
    {
    "command": "node scripts/docs-audit/check-affected-docs.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/docs-audit/check-drift-comment.mjs",
    "exit": 0
    },
    {
    "command": "node scripts/pm/release-rehearsal-clone.mjs --self-test",
    "exit": 0
    },
    {
    "command": "pnpm --filter @objectstack/spec run check:duration-unit-keys",
    "exit": 0
    },
    {
    "command": "pnpm check:changeset-gate-self-tests",
    "exit": 0
    },
    {
    "command": "pnpm check:cross-package-test-inputs",
    "exit": 0
    },
    {
    "command": "pnpm check:doc-authoring",
    "exit": 0
    },
    {
    "command": "pnpm check:driver-memory-census",
    "exit": 0
    },
    {
    "command": "pnpm check:dts-closure",
    "exit": 0
    },
    {
    "command": "pnpm check:dual-build-cjs-loads",
    "exit": 3
    },
    {
    "command": "pnpm check:gitlink-declared",
    "exit": 0
    },
    {
    "command": "pnpm check:i18n",
    "exit": 0
    },
    {
    "command": "pnpm check:i18n-stale-fill",
    "exit": 0
    },
    {
    "command": "pnpm check:issue-citations",
    "exit": 0
    },
    {
    "command": "pnpm check:lean-entry-closure",
    "exit": 0
    },
    {
    "command": "pnpm check:logger-receiver-detach",
    "exit": 0
    },
    {
    "command": "pnpm check:nul-bytes",
    "exit": 0
    },
    {
    "command": "pnpm check:objectui-changeset",
    "exit": 0
    },
    {
    "command": "pnpm check:org-identifier",
    "exit": 0
    },
    {
    "command": "pnpm check:page-declaration-shape",
    "exit": 0
    },
    {
    "command": "pnpm check:pm-changeset-deadline-census",
    "exit": 0
    },
    {
    "command": "pnpm check:published-files",
    "exit": 0
    },
    {
    "command": "pnpm check:refd-timer-probe",
    "exit": 0
    },
    {
    "command": "pnpm check:slot-lookup",
    "exit": 0
    },
    {
    "command": "pnpm check:sourcemap-no-sources-content",
    "exit": 0
    },
    {
    "command": "pnpm check:test-source-alias",
    "exit": 0
    },
    {
    "command": "pnpm check:tier-file-adoption",
    "exit": 0
    },
    {
    "command": "pnpm check:watch-hint-literal",
    "exit": 0
    },
    {
    "command": "node scripts/check-issue-citations.mjs",
    "exit": 0,
    "note": "the live command dispatch-gates.mjs's derivation reaches only via --self-test; run by hand per dispatch instruction"
    }
    ]
    },
    "tests": "pnpm --filter @objectstack/platform-objects build :: exit 0 (all dist/ emitted, 11/11 declared). pnpm --filter @objectstack/platform-objects test -- --run :: exit 0, 883/883 tests passed across 54 files. pnpm --filter @objectstack/platform-objects typecheck :: exit 0 (tsc --noEmit clean; pre-existing shrink-only test-typecheck-debt.json entry untouched, 1 file/3 errors/2 pinned signatures). pnpm check:i18n :: exit 0, 'OK (9 package(s) — all bundles in sync, no undeclared authoring keys)'. pnpm check:i18n-stale-fill :: exit 0, 'OK (10 bundle set(s) — no new stale fills, 0 baselined)', self-test 27/27 batteries held. A2 ablation (see summary) run and reverted — both gates report 0 findings for this leaf before and after the fix, confirming the refutation. Gate derivation (node scripts/pm/dispatch-gates.mjs --commands, re-derived on final commit after git fetch origin main): 54 families derived, 53 ran clean (exit 0), 1 NOT MEASURED (pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET — needs a full whole-workspace pnpm build unrelated to this diff, CI's Build Core covers it). Reconciled with --ran: '54 derived, 53 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3), 0 UNRUN'. node scripts/check-issue-citations.mjs (live command; the derivation only reaches its --self-test) run by hand :: exit 0, 'no issue citations added against 44ce049 (3 file(s) read)'. Full command list and per-command exit codes are in the gates field.",
    "mcp_calls": "5 — mcp__github__issue_read (method=get), mcp__github__issue_read (method=get_comments), mcp__github__pull_request_read (method=get, pullNumber=0 — user error, missing param, no-op), mcp__github__list_pull_requests, mcp__github__pull_request_read (method=get, pullNumber=19914, PR-body readback). All reads; no MCP write tool called.",
    "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (op: pr_create, via scripts/pm/with-fleet.sh --via dispatch relay, draft forced true); POST /repos//issues/19897/comments (op: comment, this report, via the same relay, about to be sent). git push (2x, to publish the empty branch then each commit) is git-protocol, not a REST proxy write, per AGENTS.md/dispatch convention. No label writes (none requested; dispatch said none).",
    "open_questions": [
    {
    "question": "This card's structural finding — 1203/1182/1173 genuinely-translated platform-objects objects leaves (zh-CN/ja-JP/es-ES) have no staleness protection at all, by the current mechanism's own ruled design (#8765 Option B / #12069 Option A: a real translation is always 'legacy-trusted'). Is this worth a new ruling to extend protection (e.g. a hand-recordable hash table for the objects section, mirroring LOCALE.source-hashes.ts for apps/dashboards/pages), or is the current design (structural blind spot, relying on ad hoc discovery like this card) the intended long-term shape?",
    "options": [
    "A. Leave as-is: the mechanism is deliberately scoped (ruling #12069 comment: extending it would be a bundle/extract FORMAT change, reported rather than invented inline). Rely on future audits/reports like this one to catch drift by inspection.",
    "B. Extend the ruled shape: add a hand-maintained LOCALE.source-hashes.ts-style companion for genuinely-translated objects/metadataForms leaves too, so a translator recording a hash at translation time gets the same protection apps/dashboards/pages leaves already have.",
    "C. Something narrower: a periodic/CI-driven audit that diffs en against its git history per leaf-path and flags translated leaves whose corresponding en value changed since the translation's last touch (a git-blame-based proxy, no new stored hash format)."
    ],
    "recommendation": "B, because per the four-axis framework: real business need is measured directly by this very card (a wrong-language admin-facing string went unnoticed through every gate) and the population is large (~1200 leaves per locale) so the same failure mode will recur; long-run this closes exactly the kind of undetectable metadata drift the repo's i18n gates exist to prevent, and it reuses an already-ruled, already-implemented pattern (findStaleLeaves/HAND_AUTHORED_SECTIONS) rather than inventing a new one; it tightens a declared contract rather than adding consumer-side tolerance; against 'don't expand scope', the counter-argument is that leaving ~1200 leaves per locale permanently unprotected is itself the wider, compounding cost. A is cheapest but leaves the whole class exposed. This needs the maintainer's ruling, not a dev decision — filing it is a PM/maintainer call, not this card's to make."
    }
    ],
    "out_of_scope_findings": [
    "Triage-requested measurement (not fixed here, per dispatch scope): counted every packages/platform-objects 'objects' leaf that is a genuine translation (differs from en) and therefore carries no source-hash entry — i.e. the population structurally invisible to check:i18n-stale-fill if it ever drifts, same defect class as this card. zh-CN: 1203 of 1565 objects leaves. ja-JP: 1182 of 1565. es-ES: 1173 of 1565. (362/383/392 respectively ARE hashed — those are leaves still byte-identical to en, i.e. untranslated fills, not at risk of THIS failure mode.) A targeted grep for the specific retired action name/label this card fixed ('Set Platform Role' / 设置平台角色 / プラットフォームロールを設定 / Establecer rol de plataforma) across every locale's objects/metadata-forms bundles, en included, returns 0 hits post-fix — no OTHER leaf still names it. Did not audit the 1203/1182/1173 population for actual (vs. structurally-possible) staleness — that is a full semantic audit, out of this card's scope and not mechanically checkable at this population size."
    ]
    }

  5. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — round 21 · PR #19914 · Fixes

    domain:engine#1, session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T18:13Z. Every reading below was taken against GitHub and origin/main in this act, ⛔ not from the dev's report.

    Review

    check reading
    head 0807fd8143
    PR shape draft → main; first body line Fixes #19897; no other closing keyword; Clause-②: no at column 0, agreeing with the patch changeset
    scope 4 files, +10 / −3: exactly one line (the objects.sys_user.fields.role.help value) in each of {zh-CN,ja-JP,es-ES}.objects.generated.ts, one changeset. No key, structure or hash sidecar moves
    fidelity each value translates the current en text (029d8a4710, PR #19892) clause by clause: the verified email in OS_PLATFORM_OWNER_EMAIL on every posture, and the unscoped admin_full_access grant on single only. The single qualifier is scoped to the second route in all three locales; code spans are verbatim
    retired name "Set Platform Role" and its three translations: 0 hits across every translations/ file at head, en included (3 hits at the merge-base, exactly the lines replaced)
    source-hash half not done, and correctly so. The card offered it as "Suggested shape (⛔ not a ruling)". collectFilledFromHashes never records a hash for a genuinely translated leaf (pinned at source-hash.test.ts:270). A hand entry in the generated sidecar is forbidden by AGENTS.md and the file header, and could not protect the leaf. The dev ran the tooling once; it wrote nothing
    governed NOT governed (check-governed-merges --pr 19914, 0 of 4 paths)
    contract review PASS on this head (5800255971)
    CI landing waits for every check run on this head to finish green (Lint & Repo Gates carries the live check:i18n / check:i18n-stale-fill)

    Out-of-scope findings and open questions — dispositions:

    Landing: ready → queue on this head once CI is fully green.


    Generated by Claude Code

  6. added a commit that references this issue on Sep 28, 2026
    a34c27c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions