Repository navigation
spec(i18n): translateObject writes translated field help onto an undeclared help key — the console warns ~520× per page load and drops the value #21948
Description
Activity
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsPath: ② the capabilities an end user meets in the app — translated field help on platform objects | 缺项 (no item asserts a served field carries only declared keys) | P2
Triage: first grade —
bug·priority:p2·domain:spec·area:i18n·pm:queue. Translated help lands on the declared key the bundle entry translates, never on an undeclaredhelpTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T04:54Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/spec/src/system/i18n-resolver.ts(translateObject→translateField, about:2992–:2993) ⇒domain:spec; rationale: the metadata read seam writes a keyFieldSchemadoes not declare. The served shape breaks its own contract, which is the AGENTS rule that machine-readable surfaces must not lie.- Verified on
main(3c7785d4ab):translateFielddoesif (translatedHelp) next.help = translatedHelp;. - Why p2:
- On every platform object whose bundle has
helpentries (measured: 18 of 27sys_userfields, 73sys_*objects), the translated help is not rendered. - The console logs about 520 warnings per page load. It is user-visible on default deployments.
- On every platform object whose bundle has
- Direction:
- The bundle's
helpentry overlays the declared key it was generated from. The dev reads the generator (packages/platform-objects/src/apps/translations/*.objects.generated.tsand its source) to name that key,inlineHelpTextordescription, and does not guess. - ⛔ No undeclared key on the served field.
- ⛔ No new
helpalias onFieldSchema.field.zod.ts's conversion table already nameshelpas a retired spelling.
- The bundle's
- Pins:
GET /api/v1/meta/object/sys_userserves nohelpkey, and the translated text appears on the declared key in a non-English locale;- the console's ingestion step logs zero
helpwarnings on a page load.
Generated by Claude Code
- Verified on
- addedarea:i18nThe customer's own language, across UI, metadata and notificationsThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 6, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (this card, per triage's first grade
6009610488) · 2026-10-06T04:58Z
Session:session_01T9u38rswFp5Rw8DswRUReJ
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-21948-translated-field-help-declared-key
Worktree:objectstack-issue-21948
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main3c7785d4ab; stop on breach and explain in the report):- Measure first: which declared key does a bundle's field
helpentry translate? Read the translation extractor (packages/clii18n-extract) and the generator ofpackages/platform-objects/src/apps/translations/*.objects.generated.ts, then state with file:line whichFieldSchemakey the field-levelhelpentry is sourced from (inlineHelpText,description, or both).- Do not guess.
- If the source is ambiguous, or if serving the translation onto that key changes what any consumer renders in a way the card does not describe, stop and report.
packages/spec/src/system/i18n-resolver.ts,translateObject→translateField(about:2992–:2993): the translated field help overlays the declared key it was generated from, never an undeclaredhelp.- ⛔ No new
helpalias onFieldSchema.field.zod.ts's conversion table already nameshelpas a retired spelling. - The bundle's own
helpkey (translation.zod.ts) is unchanged.
- ⛔ No new
- Its tests, under
packages/spec/src/system/:- Pin a served field translated in a non-English locale: no
helpkey, and the translated text on the declared key, under a control that an untranslated field is unchanged. - Prove the pin red with the change reverted, then restore.
- Any existing pin that asserted
helpon the served field moves with the change, at the same strength.
- Pin a served field translated in a non-English locale: no
- Cross-lane readers: if a reader in another lane depends on the served
helpkey (arest/metadata-protocoltest, the dogfood fixtures, the console via.objectui-sha), stop and report the list. The seat revises this claim and declares each file on its lane post first. - Changeset:
@objectstack/specpatch, at the level the precedents set for a served-shape fix. Measure withnpm pack. - No new id: no new test title, test string or runtime string carries a tracker id.
- ⛔ No objectui file, no
content/docs/releases/, no generated catalog edited by hand.
Container & model:M,mode:subagent,model: opus. The at-tier contract review follows delivery (path legsystem/i18n-resolver.ts).
Clause-②: no (expected: no accept-set change; the dev states the measured answer in the PR)
Thread-read: 6009610488
Serial constraints cleared: none of the 3 open PRs touchessystem/i18n-resolver.ts,system/translation.zod.ts,data/field.zod.ts, the*.objects.generated.tscatalogs ori18n-extract(scan at this stamp). runtime strings in thedomain:specpackages carry tracker numbers (spec175 andlint83 messages): this lane's share of the #20513 A/A burn-down #20749's latersystem/stages will rewrite test titles insystem/i18n-resolver.test.ts. They are not claimed yet, and whichever lands second mergesmain.
- Measure first: which declared key does a bundle's field
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21948, "status": "needs_decision", "branch": "claude/issue-21948-translated-field-help-declared-key", "pr": null, "session": "session_01T9u38rswFp5Rw8DswRUReJ", "premise_still_valid": true, "summary": "Stopped before writing code. Two of the dispatch's own stop conditions hold, both measured at origin/main 3c7785d4ab. (1) Step 1 has an unambiguous answer: the declared source key is `description`. The extractor writes objects.OBJECT.fields.FIELD.help from `field.help ?? field.description` (packages/cli/src/utils/i18n-extract.ts:1223). The `help` arm cannot be reached on a spec-valid field, because FieldSchema refuses `help` with unrecognized_keys (measured). `inlineHelpText` is never read. Corpus: the nine bundle source trees author 0 `inlineHelpText` and 0 field `help`, and the nine en bundles carry 523 `help` entries, which is the card's 523 warnings. The generator (scripts/check-i18n-bundles.mjs) spawns the same `i18n extract` command, so it reaches the same line. (2) Step 5 found readers of the served `help` key in another lane: objectui at .objectui-sha 0abd4f9f, three sites (open_questions[0]). The dispatch says to stop there so the seat can revise the claim. Separately, overlaying onto `description` changes what objectui's `description` readers render and write, beyond what the card describes (open_questions[1]). The repro holds, so the premise is valid. One sub-claim of the card is wrong: the console's forms DO render the translated value today, through objectui's kept `field.help || field.description` read. The loss is limited to readers of `description` alone, for example the record detail panel. Neighbouring overlays in translateField: `label` goes onto the declared `label`, options go onto the declared option `label`, and field `description` is not overlaid today except through this `help` entry. None of them has the undeclared-key defect.", "objectui_warning_site": "objectui at 0abd4f9f: packages/core/src/utils/reference-keys.ts:362 (the console.warn in warnUnfoldableRetiredKey, defined at :342), reached through the `no-declared-twin` arm at :415 of canonicalizeRetiredFieldKeys (called at :563). It fires for any undeclared, non-undefined key on a served field def, so it goes quiet once the served def carries no `help`.", "fix_plan_once_decided": "packages/spec/src/system/i18n-resolver.ts translateField (:2992-:2993): write the translated help onto `description` under the rule chosen in open_questions[1], and stop writing `help`. Drop `help?: string` from the exported ObjectFieldLike (:2420): api-surface/system.json records only the interface name, and the index signature keeps assignability. Fix the translateObject docblock (:2909), which says it translates a field's `help`. Pins in packages/spec/src/system/i18n-resolver.test.ts: zh-CN served field has no `help` and carries the translation on `description`; untranslated control unchanged; a field declaring both `description` and `inlineHelpText` gets the translation on `description` with `inlineHelpText` untouched; and for option B, a diverged `description` is kept. Plus a `@objectstack/spec` patch changeset. dispatch-gates over that surface derives 79 commands (saved in the session scratchpad; none run).", "tests": "No code, so no test suite ran. Repro (scratch script, not committed) at 3c7785d4ab: `npx tsx` over packages/spec/src/system/i18n-resolver.ts, calling translateMetadataDocument('object', sys_user-shaped doc, zh-CN bundle). Verbatim: two_factor_enabled came back as {label: '已启用双因素认证', description: 'Whether two-factor authentication is enabled for this user. Maintained by the better-auth `twoFactor` plugin.', help: '该用户是否已启用双因素认证。由 better-auth 的 `twoFactor` 插件维护。'}, and FieldSchema.safeParse returned unrecognized_keys:[\"help\"]. A field declaring both description 'Desc EN' and inlineHelpText 'Inline EN' came back with help: '描述(中文)' and both declared keys untouched, safeParse unrecognized_keys:[\"help\"]. The untranslated control field came back unchanged, safeParse ok. FieldSchema probe: a field with help 'h' gets unrecognized_keys with the message 'Did you mean `help` → `inlineHelpText`?'. NOT MEASURED: dogfood GET /api/v1/meta/object/sys_user (the stop condition came first; the repro is at the resolver seam the route calls). NOT MEASURED: Clause-② (no diff). Expected `no` for options A and B, because no Zod schema is touched.", "mcp_calls": "0 — none", "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/21948/comments (this os-dev-report, scripts/pm/post-stamped.mjs over the fleet-write relay). The empty-branch `git push` is not a REST write. No pr_create, no label-write.", "gates": { "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack": "2 — 'this branch changes nothing against origin/main (merge base 3c7785d4a) — nothing to derive'", "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack packages/spec/src/system/i18n-resolver.ts packages/spec/src/system/i18n-resolver.test.ts": "0 — hypothetical fix surface, 79 commands derived for the re-dispatch, none run", "--ran reconciliation": "not run — empty diff, no gate owed" }, "deviations": [ "No PR, changeset or label-write. I stopped at the dispatch's step-5 condition (a cross-lane reader of the served `help` key exists) and at the claim's condition that serving onto the measured key changes consumer rendering beyond the card.", "Step 2 dogfood half not run. The repro was taken at the resolver seam only.", "Branch claude/issue-21948-translated-field-help-declared-key is pushed and empty (at BASE 3c7785d4ab), as the write probe." ], "files_changed": [], "line_budget": "0 additions / 0 deletions (git diff --shortstat BASE...HEAD is empty)", "open_questions": [ { "question": "Readers of the served `help` key exist in another lane: objectui at .objectui-sha 0abd4f9f (read only; none of the options edits objectui). They are packages/plugin-form/src/ObjectForm.tsx:1033 and packages/plugin-form/src/sectionFields.ts:311 (`description: field.help || field.description`), and packages/app-shell/src/utils/resolveActionParams.ts:714 (`helpText: param.helpText ?? field.help ?? field.description`). All three fall back to `description`. Under open_questions[1] option A or B they render the same translated text, and their `help` arm becomes dead. Under option C they would fall back to the English `description`, and the forms would LOSE the translation they render today. I found no reader in rest, metadata-protocol, objectql, runtime, metadata, client, qa/dogfood or docs. Runtime's `labels.FIELD.help` pins read the bundle-shaped field-labels response, a different surface that stays unchanged. The two docs hits (content/docs/protocol/kernel/i18n-standard.mdx:358/:382) are bundle files. The dispatch says the seat revises the claim and declares these readers first.", "options": [ "A — revise the claim to declare the three objectui sites as known readers that need no edit. The fix lands spec-only, and retiring objectui's dead `help` arms is left to the objectui lane.", "B — pair the spec fix with an objectui card that retires the three `help` arms in the same landing window." ], "recommendation": "A. Business need: none of the three needs a change for its rendered text to hold. Long-term: the dead arms are the objectui lane's retirement, and a later card can do it without a cross-repo landing dependency. AI-error: the spec contract tightens either way. Startup scope: B adds a cross-repo coordination cost to a P2 and buys no render change." }, { "question": "Which value is served on `description`, and when? Serving the bundle's `help` on `description` (the measured source key) changes more than the card describes. In a non-English locale, every objectui reader of a served field's `description` gets the translation. That includes the record detail panel (plugin-detail RecordDetailPanel.tsx:206, the intended win) and the Studio authoring surfaces. ObjectFieldInspector.tsx:887-888 binds `def.description` as an editable input. plugin-designer DataModelDesigner.tsx:155 and PropertyEditor.tsx:180 display it. MetadataService.ts:620 writes `description: field.description` on a field save whose document was fetched with `client.meta.getItem('object', NAME)` (:904), and the metadata-admin client sends Accept-Language. Field `label` already has the same exposure today. I did not trace the inspector draft's source read to the line. The catalog has never touched `description` before. Under a flat overlay, an authored field `description` that diverges from the packaged one (from objectExtensions or a tenant overlay) would be replaced by the catalog in EVERY locale, en included, because the en bundles carry help = the packaged description.", "options": [ "A — flat overlay onto `description`, following the field-`label` precedent: the catalog wins whenever an entry exists. Cost: one line. It overwrites a diverged authored `description` on read, in every locale with a bundle entry.", "B — overlay onto `description` under the ADR-0029 D9.2a comparison. The catalog applies only while the served field's `description` equals packagedBase.fields[FIELD].description, decided by the existing valueOverridesPackagedBase predicate (no second comparison). With no packaged base, the catalog applies (the existing conservative edge). Cost: about ten lines plus a pin for each arm. Field `label` stays flat (the #8284 pin scopes it out), so the two field keys follow different precedence. A translated description saved back from a zh-CN Studio session would then count as authored and be served in every locale, which is how object scalars already behave under #8284.", "C — overlay onto `inlineHelpText`, the key FieldSchema's alias table maps `help` to. This contradicts the measured source, because the bundle entry translates `description`. It would serve description text as always-visible help under inputs. It drops the translation from the three objectui form/param readers, which fall back to the English `description`. The extractor would need a matching change." ], "recommendation": "B. Business need, measured: the 523 entries all come from `description`. The zh-CN/ja-JP/es-ES bundles carry real translations; for example zh-CN sys_user.two_factor_enabled differs from the English description. Readers of `description` alone render English today. NOT MEASURED: whether any deployment authors a diverged field description, because no deployment data was available here. So B's protection covers an unmeasured case, but A's overwrite is a regression that A itself introduces. Long-term: B is the same sentence ADR-0029 D9.2a applies one layer down, and the same one the newer translator overlays already use (dashboard `title`, view bulk-action `confirmText` and param `help`). A extends a flat-catalog rule the maintainer ruled against for scalars. C contradicts the extractor. AI-error: all three remove the undeclared key at the producer. A silently replaces an author's text on read, which is the masked-error class. C creates a trap where bundle `help` means `description` but is served as `inlineHelpText`. Startup scope: A is the smallest. B applies an existing predicate and adds no new mechanism, gate or surface. C is the largest. If the maintainer accepts A's overwrite under the field-label precedent, A is the one-line fallback. Neither A nor B closes the Studio write-back of a translated read (MetadataService.ts:620/:904), which already exists for field `label` and the object scalars." } ], "out_of_scope_findings": [ "carrier: the fix PR for this card (it removes the source) · NOT MEASURED, read-only inference, not filed — probable consequence of this card's defect: Studio `saveFields` (objectui 0abd4f9f packages/app-shell/src/services/MetadataService.ts:898-:939) carries per-field server keys from the translated read back into PUT /api/v1/meta/object/NAME. `help` is not in objectui's RETIRED_FIELD_KEYS (packages/types/src/internal/retired-field-keys.ts holds indexed, referenceTo, formula, isSystem, sortOrder). So a field save on a platform object with bundle `help` entries should send `help`, which FieldSchema refuses with unrecognized_keys. Reach would need one measured PUT. The fix removes the source either way. dedupe words: studio saveFields help unrecognized_keys 422 platform object", "carrier: 承接者:无 (packages/cli is not on this card's file surface) · the extractor's `field.help ??` arm (packages/cli/src/utils/i18n-extract.ts:1223) reads a key FieldSchema refuses, so it is dead on every spec-valid field · noted, not filed", "carrier: the fix PR for this card (same function) · translateField never overlays `placeholder`, even though FieldTranslationSchema declares it and the extractor emits objects.OBJECT.fields.FIELD.placeholder (i18n-extract.ts:1224). The nine shipped bundles carry 0 field-level placeholder entries (the 4 per locale are action params), so it is dormant with no reach · noted, not filed", "carrier: 承接者:无 · `inlineHelpText` has no translation path: the extractor never reads it, so an authored one is never offered for translation. The nine bundle sources author 0 of them, so no reach · Acceptance notes only", "carrier: 承接者:无 · docs drift: content/docs/protocol/kernel/i18n-standard.mdx:166 lists a field's `label` / `help` / `placeholder` as display labels, but FieldSchema declares no `help` · Acceptance notes only" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsClaim revision (round 1) of claim
6009655572: same session, account, branch, worktree, domain and seat · 2026-10-06T05:15ZThe dev's report
6009797157stopped before writing code, on two of the dispatch's stop conditions. Both are answered here from governing text; neither is a new ruling.1. The source key, measured:
description.- The extractor writes
objects.<object>.fields.<field>.helpfromfield.help ?? field.description(packages/cli/src/utils/i18n-extract.ts:1223). Thehelparm cannot be reached on a spec-valid field, becauseFieldSchemarefuseshelp. - The nine bundle sources author 0
inlineHelpTextand 0 fieldhelp. The nineenbundles carry 523helpentries, which is the card's 523 warnings. - So, per triage's direction
6009610488("overlays the declared key it was generated from"), the translated field help is served ondescription. - ⛔ It is not served on
inlineHelpText. That would contradict the extractor, and it would drop the translation from the readers below.
2. The precedence on
description: ADR-0029 D9.2a, by the existing comparison.- The catalog applies only while the served field's
descriptionequalspackagedBase.fields[<field>].description. This is decided byvalueOverridesPackagedBase, ⛔ never a second comparison. - With no packaged base, the catalog applies, which is The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either
/meta/objectread — and neither do extension scalar overrides #8284's existing conservative edge. - Governing text: the resolver's own rule statement. "The catalog LOSES to an explicit override — ADR-0029 D9.2a … every string here follows the rule
translateDashboardandtranslateObjectfollow, by the same comparison (valueOverridesPackagedBase; ⛔ never a second one)". It is already applied to the object scalars (The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284), views (i18n: a published org overlay on a packaged view label is served to a zh-CN reader as the catalog translation (进行中), not the edit — the view twin of #20680 #20731) and view bulk actions (metadata: a published org overlay on the built-in system_overview dashboard is never served — the plain read and the rendered board keep the shipped body while ?layers=true reports the overlay as effective #20680). - Field
labelstays as it is: flat, as its The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284 pin scopes it. This card does not move it. - Why not the flat overlay: it would make the catalog overwrite a diverged authored field
descriptionon read, in every locale,enincluded. That is a regression this change would introduce, and the rule above forbids it.
Known readers in objectui, at
.objectui-sha0abd4f9f(read-only, no edit):plugin-form/src/ObjectForm.tsx:1033andplugin-form/src/sectionFields.ts:311(field.help || field.description);app-shell/src/utils/resolveActionParams.ts:714(param.helpText ?? field.help ?? field.description).
All three fall back to
description, so they render the same translated text, and theirhelparm becomes dead. Retiring those arms is objectui's, and this PR does not do it. The ingestion warning (packages/core/src/utils/reference-keys.ts:362) goes quiet once the served field carries nohelp.File surface, unchanged in substance:
packages/spec/src/system/i18n-resolver.ts(translateField):- the overlay onto
descriptionunder the rule above, and nohelp; - the
help?: stringmember of the exportedObjectFieldLikeis dropped. Its index signature keeps assignability; the at-tier review judges the level; - the
translateObjectdocblock that says it translates a field'shelp.
- the overlay onto
packages/spec/src/system/i18n-resolver.test.ts:- a zh-CN served field carries no
helpand carries the translation ondescription; - the untranslated control is unchanged;
- a field declaring both
descriptionandinlineHelpTextgets the translation ondescription, withinlineHelpTextuntouched; - a diverged
descriptionis kept against a packaged base; - with no packaged base, the catalog applies.
- a zh-CN served field carries no
- One
@objectstack/specpatchchangeset.
Clause-②: nois expected: no Zod schema is touched. The PR states the measured answer.- The extractor writes
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21948, "status": "done", "branch": "claude/issue-21948-translated-field-help-declared-key", "pr": "https://github.com/objectstack-ai/objectstack/pull/21956", "session": "session_01T9u38rswFp5Rw8DswRUReJ", "premise_still_valid": true, "summary": "Implemented claim revision 6009832895 (Q1 → A, Q2 → B). translateField now serves the bundle's field `help` entry on `description` and writes no `help` key (packages/spec/src/system/i18n-resolver.ts:3054). It is gated by ADR-0029 D9.2a through the existing valueOverridesPackagedBase: the catalog applies only while the served description equals packagedBase.fields[FIELD].description. With no packaged base, the catalog applies. A field the base does not declare counts as diverged; the new finder packagedObjectField (:1434) reads both field-map shapes. Field `label` stays flat. ObjectFieldLike drops `help?: string`; the PR states why this is safe (index signature, the api-surface record, a compile probe with a control leg). The translateObject docblock is corrected. Nine pins are added, and both ablation legs went red exactly as predicted. A @objectstack/spec patch changeset is included, with Clause-② measured as `no`. Draft PR #21956 opened, assignee os-project-manager. The five observations are in its Acceptance notes with carriers, and none is addressed. CI is in_progress at report time (14 completed, 0 failed, 18 in progress). I did not wait for it.", "objectui_warning_site": "objectui at 0abd4f9f: packages/core/src/utils/reference-keys.ts:362 (warnUnfoldableRetiredKey), through the no-declared-twin arm at :415 of canonicalizeRetiredFieldKeys. It fires for any undeclared key on a served field def; at HEAD the served platform corpus carries 0 `help` keys (below).", "tests": "All at HEAD 3ad2f22bed. (1) New pins in packages/spec/src/system/i18n-resolver.test.ts:3552: 9 passed (the zh-CN served field has no help, carries the translation on description and parses with no unrecognized_keys; untranslated control unchanged; description+inlineHelpText; a diverged description is kept in zh-CN and en through translateMetadataDocument; an undeclared base field counts as diverged; no base (undefined/null/omitted) means the catalog applies; an absent description is filled; array-shaped fields against an array base; no input mutation). No existing pin asserted a served field help, so none moved. (2) Ablation on the committed head, scripts/ablation-replace.mjs WRAP plus a trap, predictions recorded first. Leg 1 restores BASE's `next.help` write: predicted 7 failed / 2 passed, observed 'Tests 7 failed | 2 passed'. Leg 2 is a flat overlay (the D9.2a comparison replaced by true): predicted 3 failed (the diverged, undeclared-field and array cases), observed 'Tests 3 failed | 6 passed'. Each leg showed anchor 1 → 0 on disk and was restored with blob a8eade91 == HEAD and git diff HEAD empty. The test imports src via a relative path, so no dist was involved. (3) Corpus at the resolver seam (scratch script): the platform-objects extract config, 48 objects / 617 fields, real bundles. BASE behaviour (leg-1 mutation): 332 served help keys, 332 refused by FieldSchema, sys_user 18 of 26. HEAD: 0, 0, 0 of 26, and in zh-CN 332 descriptions equal the bundle entry; sys_user.two_factor_enabled.description is the Chinese text. NOT MEASURED: the HTTP door GET /api/v1/meta/object/sys_user, because the spec change invalidates the showcase closure (62 of 63 turbo tasks miss). (4) Spec full local project: Test Files 619 passed, Tests 18480 passed | 1 todo. Spec typecheck exit 0, with the test layer compiled and the debt held. service-analytics (the ObjectFieldLike importer) typecheck exit 0, and its 3 translateObject label suites pass, 53 tests. (5) Clause-② probe against the rebuilt dist/system/index.d.ts, exit 0: writing and reading `help` on ObjectFieldLike compile, and `const proof: number = ({} as ObjectFieldLike).help` compiles. The control against the same .d.ts with BASE's `help?: string` restored gives exit 2, with exactly one TS2322 on the proof line. (6) npm pack --dry-run of @objectstack/spec 17.6.0 ships dist/system/index.js, .mjs and .d.ts; both runtime files carry packagedObjectField and 0 `next.help = translatedHelp`.", "mcp_calls": "0 — none", "api_writes": "4 in this session — POST /repos/objectstack-ai/objectstack/issues/21948/comments ×2 (os-dev-report round 1, comment 6009797157, and this report), POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft forced, #21956), POST /repos/objectstack-ai/objectstack/issues/21956/assignees (label-write --assign os-project-manager). All went over the fleet-write relay, and each read-back matched. git push ×3 to the branch (the empty probe and two commits) are not REST writes.", "gates": { "node scripts/check-adr-0087-registration.mjs --base origin/main": 0, "node scripts/check-adr-0087-registration.mjs --self-test": 0, "node scripts/check-changeset-no-major.mjs --base origin/main": 0, "node scripts/check-changeset-no-major.mjs --self-test": 0, "node scripts/check-ci-filter-parity.mjs": 0, "node scripts/check-closing-keyword-parity.mjs": 0, "node scripts/check-closing-keyword-parity.mjs --self-test": 0, "node scripts/check-comment-mask-adoption.mjs": 0, "node scripts/check-comment-mask-adoption.mjs --self-test": 0, "node scripts/check-comment-mask-corpus.mjs": 0, "node scripts/check-dev-prereqs.mjs --self-test": 0, "node scripts/check-dts-emitted.mjs --self-test": 0, "node scripts/check-empty-changeset.mjs --base origin/main": 0, "node scripts/check-empty-changeset.mjs --self-test": 0, "node scripts/check-issue-citations.mjs": 0, "node scripts/check-keyed-text-bounds.mjs": 0, "node scripts/check-keyed-text-bounds.mjs --self-test": 0, "node scripts/check-platform-object-tenancy-census.mjs": 0, "node scripts/check-platform-object-tenancy-census.mjs --self-test": 0, "node scripts/check-plugin-teardown-shape.mjs": 0, "node scripts/check-plugin-teardown-shape.mjs --self-test": 0, "node scripts/check-registry-log-declared.mjs": 0, "node scripts/check-registry-log-declared.mjs --self-test": 0, "node scripts/check-rest-log-spy-declared.mjs": 0, "node scripts/check-rest-log-spy-declared.mjs --self-test": 0, "node scripts/check-spec-docblock-symbol-anchors.mjs": 0, "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test": 0, "node scripts/check-system-context-census.mjs": 0, "node scripts/check-system-context-census.mjs --self-test": 0, "node scripts/check-undeclared-dep-imports.mjs": 0, "node scripts/check-undeclared-dep-imports.mjs --self-test": 0, "node scripts/docs-audit/check-affected-docs.mjs": 0, "node scripts/docs-audit/check-drift-comment.mjs": 0, "node scripts/pm/release-rehearsal-clone.mjs --self-test": 0, "node scripts/release-pending-publish.mjs --self-test": 0, "pnpm --filter @objectstack/lint run check:doc-formula-expressions": 0, "pnpm --filter @objectstack/spec run check:api-surface": 0, "pnpm --filter @objectstack/spec run check:authorable-surface": 0, "pnpm --filter @objectstack/spec run check:browser-reachable-entries": 0, "pnpm --filter @objectstack/spec run check:docs": 0, "pnpm --filter @objectstack/spec run check:dual-source-exports": 0, "pnpm --filter @objectstack/spec run check:duration-unit-keys": 0, "pnpm --filter @objectstack/spec run check:empty-state": 0, "pnpm --filter @objectstack/spec run check:entry-nameability": 0, "pnpm --filter @objectstack/spec run check:export-origins": 0, "pnpm --filter @objectstack/spec run check:exported-any": 0, "pnpm --filter @objectstack/spec run check:liveness": 0, "pnpm --filter @objectstack/spec run check:llms-txt": 0, "pnpm --filter @objectstack/spec run check:objectui-pin-citations": 0, "pnpm --filter @objectstack/spec run check:skill-refs": 0, "pnpm --filter @objectstack/spec run check:strictness-ledger": 0, "pnpm --filter @objectstack/spec run check:variant-docs": 0, "pnpm --filter @objectstack/spec run check:yaml-examples": 0, "pnpm check:changeset-gate-self-tests": 0, "pnpm check:cross-package-test-inputs": 0, "pnpm check:dispatcher-error-vocabulary": 0, "pnpm check:doc-authoring": 0, "pnpm check:driver-memory-census": 0, "pnpm check:dts-closure": 0, "pnpm check:dual-build-cjs-loads": "3 — NOT MEASURED: PREREQUISITE NOT MET (78 packages have no dist/). Narrowed: all 19 require entries of @objectstack/spec exports load from the rebuilt dist", "pnpm check:engine-double-contract": 0, "pnpm check:gitlink-declared": 0, "pnpm check:issue-citations": 0, "pnpm check:lean-entry-closure": "0 — after building the objectql closure (the first run answered 3, PREREQUISITE NOT MET: objectql/core unbuilt)", "pnpm check:logger-receiver-detach": 0, "pnpm check:merge-driver": 0, "pnpm check:nul-bytes": 0, "pnpm check:objectql-double-limit": 0, "pnpm check:objectui-changeset": 0, "pnpm check:org-identifier": 0, "pnpm check:page-declaration-shape": 0, "pnpm check:pm-changeset-deadline-census": 0, "pnpm check:pm-prior-rulings": 0, "pnpm check:published-files": 0, "pnpm check:query-options-erasure": 0, "pnpm check:refd-timer-probe": 0, "pnpm check:slot-lookup": 0, "pnpm check:sourcemap-no-sources-content": 0, "pnpm check:spec-parsed-alias": 0, "pnpm check:test-source-alias": 0, "pnpm check:tier-file-adoption": 0, "pnpm check:type-check-coverage": 0, "pnpm check:type-check-debt": 0, "pnpm check:watch-hint-literal": 0, "pnpm check:where-matcher": 0, "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-exit.txt": "0 — 'Run reconciliation — 85 derived, 84 run, 1 NOT-MEASURED, 0 UNRUN'", "pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2": "0 — Test Files 619 passed, Tests 18480 passed | 1 todo", "pnpm --filter @objectstack/spec typecheck": 0, "pnpm --filter @objectstack/spec check:generated": 0, "pnpm --filter @objectstack/service-analytics typecheck": 0, "pnpm --filter @objectstack/service-analytics exec vitest run (dimension-labels, dataset-order-labels, dataset-reference-dimension-labels)": "0 — Tests 53 passed", "npx eslint --no-inline-config (the 2 changed .ts files, targeted — not the repo-wide lint, which is CI's)": "0 — 0 errors, 0 warnings" }, "deviations": [ "check:dual-build-cjs-loads is NOT MEASURED: `pnpm check:dual-build-cjs-loads` gives 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' (78 packages). Declared narrowing: all 19 require entries of @objectstack/spec load from the rebuilt dist. CI builds everything and runs the full gate.", "Dogfood HTTP half NOT MEASURED. Reason: the spec change invalidates the 63-task showcase build closure (62 misses). It was replaced by a corpus run at the same translateMetadataDocument seam, with a BASE control leg.", "origin/main moved to 01e0f71ad8 (#21947, #21940, #21953). None touches this PR's files, so per the dispatch it is not merged.", "The PR's labels documentation, size/m, tests, tooling and protocol:system were set by another actor (the labelers), not by this session. Left as found." ], "files_changed": [ ".changeset/21948-spec-field-help-served-on-description.md", "packages/spec/src/system/i18n-resolver.test.ts", "packages/spec/src/system/i18n-resolver.ts" ], "line_budget": "257 additions / 11 deletions (git diff --shortstat BASE...HEAD: 3 files changed, 257 insertions(+), 11 deletions(-))", "open_questions": [], "out_of_scope_findings": [ "carrier: this PR (removes the source) · Studio saveFields write-back: objectui 0abd4f9f MetadataService.ts:898-:939 carries per-field server keys from the translated read into the object PUT, and `help` is not in objectui RETIRED_FIELD_KEYS · NOT MEASURED, read-only inference, in the PR's Acceptance notes · dedupe words: studio saveFields help unrecognized_keys platform object", "carrier: domain:cli (none in flight) · the extractor's dead `field.help ??` arm, packages/cli/src/utils/i18n-extract.ts:1223 · Acceptance notes", "carrier: none · field `placeholder` declared on FieldTranslationSchema and extracted (:1224) but never overlaid by translateField; 0 field-level placeholder entries in the nine shipped bundles, so dormant · Acceptance notes", "carrier: none · `inlineHelpText` has no translation path (the extractor never reads it); 0 authored in the nine bundle sources · Acceptance notes", "carrier: none · content/docs/protocol/kernel/i18n-standard.mdx:166 lists a field `help` display label, which FieldSchema does not declare · Acceptance notes" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsContract review
Served-tier:
CONTRACT_REVIEW_TIER
Head-sha:3ad2f22bed534424e1af81d7d96c38eab84afd0d
Local-runs: noneRead: card #21948 (body and all five comments: triage
6009610488, claim6009655572, report6009797157, claim revision6009832895, report6010641236), PR #21956 (body, file list, net diff againstmainat merge-base3c7785d4ab: 3 files, +257/-11), and the check-runs on the head. Review owed on the path leg: the diff movespackages/spec/src/system/i18n-resolver.ts, a non-test source file. No governed surface is in the file list.① Derived judgments
- Served field shape drops the undeclared
helpkey — right.translateFieldno longer writesnext.help.FieldSchemais strict and declares nohelp(its alias table atpackages/spec/src/data/field.zod.ts:1038nameshelpa retired spelling ofinlineHelpText, refused with a prescription, not converted), so this is the served shape coming back to its own contract, as triage's direction required. - The bundle's
objects.OBJECT.fields.FIELD.helpentry is served ondescription— right. The measurement holds: the extractor writes that entry fromfield.help ?? field.description(packages/cli/src/utils/i18n-extract.ts:1223); thehelparm is unreachable on a spec-valid field, and the extractor never readsinlineHelpText. So the entry is a translation ofdescription, and triage's "the declared key it was generated from" namesdescription.inlineHelpTextis rightly excluded: serving there would put description text under every input and would drop the translation from the three objectui readers that fall back todescription. Observation, not a defect of this diff:FieldTranslationSchema.helpis still described as "Translated help text" and aliaseshelpText/hint/tooltip, so by name it reads as inline help while by source it isdescription; the resolver's docblock now states the real meaning, the schema's describe text and the docs do not (③, note 5). - Precedence is ADR-0029 D9.2a by the one existing comparison — right. The only comparison is
valueOverridesPackagedBase(:2860).packagedObjectField(:1434) is a finder, not a comparison: it returns the same three answers aspackagedPart(:1407) —undefinedwhen no base was supplied,{}when the base declares no such field, the packaged field otherwise — over the record-keyed field map and the array shapetranslateObjectalready accepts, and it compares nothing. "No base means the catalog applies" is the predicate's first conservative edge; "a field the base does not declare counts as diverged" ispackagedPart's documented convention for views and dashboards. Reach at the HTTP door is real:packages/rest/src/meta-item-read-gate.ts:2147mapsobjecttogetPackagedObjectBase, and bothtranslateMetaList(:2233) andtranslateMetaDocument(:2273) pass it, as does the public form route (rest-server.ts:10934), so the comparison is made on the served reads — the PR's "NOT MEASURED: the HTTP door" is a measurement gap, not a reach gap, and CI's dogfood jobs ran green on the head. One edge sized:getPackagedObjectBasereturns the owner contributor alone (metadata-protocol/src/protocol.ts:8250), so a field a code package contributes throughobjectExtensionsis "not declared" and its description is never translated by the catalog. Measured againstmain: the extractor does not walkobjectExtensions, no shipped package but the showcase declares any, and the showcase's translation sources carry 0 fieldhelpentries — nothing is withheld today. That is a pre-existing gap in the extractor, not a defect this PR introduces; recorded so the convention's cost on object fields is visible. - Field
labelstays flat — right. Its lines are untouched and the new pin asserts it beside a divergeddescription. ObjectFieldLike(@objectstack/spec/system) dropshelp?: string— right atpatch. The[key: string]: anyindex signature stays, so a caller that writes or reads.helpstill compiles; the one type-level delta isObjectFieldLike['help']typedanyinstead ofstring | undefined, a loosening — no value or expression that compiled is refused now.packages/spec/api-surface/system.json:462records the interface by name only, socheck:api-surfaceis blind to the member; the dev's compile probe with its control leg (PR body) is the measurement, and the one in-repo importer (services/service-analytics/src/plugin.ts:8) readsoptionsonly. Not an accept-set change.- No new export, no Zod change — right.
packagedObjectFieldis module-private;FieldSchemaandFieldTranslationSchemaare untouched;check:export-origins,check:authorable-surfaceandSpec property livenessare green on the head. - The objectui readers — right, verified at the real pin.
.objectui-shais0abd4f9f8769at the merge-base, atorigin/mainand at the head. At that pinplugin-form/src/ObjectForm.tsx:1033,plugin-form/src/sectionFields.ts:311(field.help || field.description) andapp-shell/src/utils/resolveActionParams.ts:714(param.helpText ?? field.help ?? field.description) all fall back todescription, so they render the same translated text and theirhelparm is dead. The one otherfield.helpin objectui (metadata-admin/inspectors/FlowNodeConfigField.tsx:570) reads aFlowConfigField, not a served object field. - Docblock rewrite — accurate. "The rule is scoped to the three SCALARS" became "The ruling covers the three SCALARS"; both are true of The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either
/meta/objectread — and neither do extension scalar overrides #8284, and the per-fieldlabelscoping is kept verbatim. - Tests — right. Nine pins; the mid-file
import { FieldSchema }has precedent in the same file (lines 769, 991, 1205, 2650, 2858, 2938); the ablation legs in the PR body went red as predicted. No new id: no added test title, test string or runtime string carries a tracker id;#21948appears only in code comments and the changeset.
② Semver level
@objectstack/specpatch,Clause-②: no— right.- A bug fix in a released package takes
patch(AGENTS Post-Task Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3). The served-shape change — a non-English locale now serves the translation ondescription, where it served English; every reader ofdescription(record detail panel, Studio inspectors) shows translated text — has its precedent atpatch:427344c(The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284, 17.0.0 Patch Changes: the object catalog yields to an explicit scalar, with REST handing the base, so served output changed) and01a7337(A package extension's scalar overwrites a tenant's Studio rename inside the object fold, so the rename still reaches no read #8460, Patch Changes: the fold's D9.2a).#20680and#20731sat in 17.6.0 Minor Changes, but their entries name a parameter type gain (ResolveOptionstoTranslateDocumentOptions) as the caller-visible change and state the served output was byte-identical without a base — a widening this diff does not make, sincetranslateObjectalready tookTranslateDocumentOptions. Clause-②: noholds: no schema, parse or export accepts more or less; the interface edit loosens a type under an index signature; no(widening)/(narrowing)arm is owed, nothing an author can write is removed or renamed, so no FROM/TO migration is owed. The changeset body carries the Clause line;Check Changesetis green.- The write-side consequence (a translated
descriptionsaved back from a non-English Studio session counts as authored and is served in every locale) is the The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284 class fieldlabelalready has, named in the claim revision;patchcovers it under that precedent.
③ Boundary flags
- open_questions[0] (readers) → A — answered, right. Verified at the pin (①.7). No objectui file is edited; retiring the dead arms is objectui's.
- open_questions[1] (precedence) → B from governing text — a sound reading; no maintainer ruling was needed. ADR-0029 D9.2a's binding clause is the fold's three scalars, but it records "one sentence now governs both layers: an explicit override beats a packaged default", naming The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either
/meta/objectread — and neither do extension scalar overrides #8284's catalog layer, and the resolver's own rule statements (:1008–:1035,:1557–:1583) say every translatable string follows that sentence by the one comparison; metadata: a published org overlay on the built-in system_overview dashboard is never served — the plain read and the rendered board keep the shipped body while ?layers=true reports the overlay as effective #20680 and i18n: a published org overlay on a packaged view label is served to a zh-CN reader as the catalog translation (进行中), not the edit — the view twin of #20680 #20731 applied it to new strings without a new ruling.descriptionwas a key the catalog never touched, so the arm that would have needed a ruling is the flat overwrite (it would introduce the The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284 defect class on a new key), not the conservative one chosen. Observation: within one field,labelis now flat anddescriptionis D9.2a; the docblock says so, and the label scoping is The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284's pin, outside this card. - Acceptance notes 1–5 — each pre-exists; none is a defect this PR introduces. (1) Studio
saveFieldscarryinghelpinto the PUT: this PR removes the source; reach unmeasured (one PUT), carrier right. (2) The extractor's deadfield.help ??arm:domain:cli, right. (3)placeholderdeclared onFieldTranslationSchemaand extracted (:1224) but never served: dormant (0 shipped entries), but it is the declared-but-unenforced class Prime Directive chore: version packages #10 says takes a card, not a note — escalated to the seat to file; carriernoneis not enough for it. (4)inlineHelpTexthas no translation path: a gap, a note is right. (5)i18n-standard.mdx:166lists a fieldhelpdisplay label: pre-existing drift; with this PR the convention "bundlehelptranslates serveddescription" lives only in the resolver docblock — escalated with note 5 as one docs follow-up that also coversFieldTranslationSchema.help's describe text. - Dev deviations.
check:dual-build-cjs-loadsNOT MEASURED locally (nodist/): CI'sBuild CoreandLint & Repo Gatescover it. The dogfood HTTP half NOT MEASURED locally: CI'sDogfood Regression Gate(3/3) andDogfood Verify CLIran on the head.origin/mainmoved without touching these files: not merged, per the dispatch. - Check-runs on the head at this reading: 35 check-runs, all completed (polled 2026-10-06T06:56Z): 32
success—Build Core,Lint & Repo Gates, the five Type Check jobs (source gates, consumer gates, debt ledger, workspace,TypeScript Type Check),Test Core1/6 through 6/6 and its rollup,Dogfood Regression Gate1/3 through 3/3 and its rollup,Dogfood Verify CLI,Temporal Conformance (live PG + MySQL),Check Changeset,Spec property liveness,Governed Surface Queue Guard,Flag docs affected by code changes,Check Documentation Links,Check PR Size,Auto Label,filter, and the four PR-claim guards; 3skippedby the path filter —Build Docs,Console Pin Gate,Packed-tarball smoke (opt-in); 0 failed, no job lost to its runner. The pin-gate skip costs nothing here: the diff removes no export the pinned sibling could import by name (ObjectFieldLikestays), and a.helpread there still compiles through the index signature.
Implemented-by:
claude/issue-21948-translated-field-help-declared-key
Reviewed-by:session_01T9u38rswFp5Rw8DswRUReJVERDICT: PASS
Read at 2026-10-06T06:57Z; read-only — the diff, the card and the head's check-runs; nothing built, run or re-run locally.
- Served field shape drops the undeclared
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsACCEPT — PR #21956 at
3ad2f22bed(#21948: a served object field carries its translated help ondescription, never on an undeclaredhelp)domain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-06T06:59Z.This is the review of record for the report
6010641236, read with:- the stop-report
6009797157; - the claim
6009655572and its revision6009832895.
The at-tier contract review is owed on the path leg (
packages/spec/src/system/i18n-resolver.ts). Its record is PASS6011100461on this head.Checklist (read on GitHub, not from the reports):
-
Form: draft, base
main, first lineFixes #21948, andClause-②: nostands in the body and the changeset. -
Scope: 3 files, +257 / −11, exactly the revised claim:
translateFieldand a new finder,packagedObjectField;- the
ObjectFieldLikemember and the docblocks ini18n-resolver.ts; - nine pins in
i18n-resolver.test.ts; - one changeset.
No cross-lane file. No objectui file.
-
Changeset:
@objectstack/specpatch, the level of The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284 (427344c) and A package extension's scalar overwrites a tenant's Studio rename inside the object fold, so the rename still reaches no read #8460 (01a7337): a served-output change in a translator, with no accept-set move and no new parameter type. -
Merge: clean onto
mainat80f9f7e6ba, with no file overlap since the base; the merge queue holds no ref at the landing read.check-governed-merges: 0 of 3 paths.
What the record establishes, checked against the claim revision:
- The source key is measured, not guessed. The extractor writes a field's bundle
helpfromfield.help ?? field.description(packages/cli/src/utils/i18n-extract.ts:1223). Thehelparm cannot be reached on a spec-valid field, andinlineHelpTextis never read. So the translation is served ondescription, andinlineHelpTextis rightly untouched. - The precedence is ADR-0029 D9.2a, by the one comparison.
packagedObjectFieldonly locates the packaged counterpart, withpackagedPart's three answers. The only comparison isvalueOverridesPackagedBase.- A field the base does not declare counts as diverged.
- With no base, the catalog applies.
- Field
labelstays flat, documented in the docblock.
- The arm is live at the HTTP door. The REST object reads pass
packagedBase(meta-item-read-gate.ts,rest-server.ts), which the reviewer read. - Measured at the resolver seam over the platform-objects corpus (48 objects / 617 fields, real bundles):
- before: 332 served
helpkeys, all refused byFieldSchema(sys_user18 of 26); - after: 0;
- in zh-CN: 332
descriptions carry the bundle entry.
- before: 332 served
- The pins: nine. Both ablation legs went red exactly as predicted:
- restoring the old
helpwrite: 7 failed / 2 passed; - a flat overlay: 3 failed / 6 passed.
- restoring the old
- Public surface:
ObjectFieldLikedropshelp?: stringunder its[key: string]: anyindex signature. That is a type loosening, and no compiled caller is refused. The dev's compile probe and its control leg are in the PR body. - objectui at
.objectui-sha0abd4f9f: its three readers (ObjectForm.tsx:1033,sectionFields.ts:311,resolveActionParams.ts:714) fall back todescription, so they render the same translated text. The ingestion warning (reference-keys.ts:362) fires for any undeclared key, and the served corpus now carries none.
CI on
3ad2f22bed: complete, 35 check-runs: 32 success and 3 skipped, 0 failed.check-expected-skipsreads all three skips (Build Docs, Console Pin Gate, Packed-tarball smoke) as in its roster.Deviations, accepted:
- the dogfood HTTP half NOT MEASURED, replaced by the corpus run at the same seam with a control leg;
check:dual-build-cjs-loadsNOT MEASURED locally (nodist/for 78 packages), narrowed to the 19 spec entries, and run in full by CI;- the labelers' labels left as found.
Out of scope, noted. Each of these pre-exists the PR, and none is introduced by it:
- Studio
saveFieldswrite-back of a translated read (objectuiMetadataService.ts:898-939): NOT MEASURED, and this PR removes thehelpsource. The same exposure pre-exists for fieldlabeland the object scalars. Carrier: none. - The extractor's dead
field.help ??arm (domain:cli). Carrier: none. FieldTranslationSchema.placeholderis declared and extracted, buttranslateFieldnever serves it. That is the declared-not-enforced class. It is not filed: the nine shipped bundles carry 0 field-level placeholder entries, so there is no producer and no reach (filing gate ①). Carrier: none until a bundle carries one.- The contract review escalates this note as the class Prime Directive chore: version packages #10 names. The seat holds it at a note. A card under the filing gate's ① needs a
reach:: one measured wrong result at a public door, or a named real producer. With 0 entries authored, there is neither. The first bundle that carries a fieldplaceholderis that reach, and the card is filed then.
- The contract review escalates this note as the class Prime Directive chore: version packages #10 names. The seat holds it at a note. A card under the filing gate's ① needs a
inlineHelpTexthas no translation path, and 0 are authored in the bundle sources. Carrier: none.- Docs:
content/docs/protocol/kernel/i18n-standard.mdx:166andFieldTranslationSchema.help's describe text ("Translated help text") should say that a field's bundlehelpis the translation of the serveddescription. Carrier: the next docs PR that touches that page. getPackagedObjectBasereturns the owner alone, so a field contributed byobjectExtensionscounts as diverged and is never catalog-translated. The extractor never walksobjectExtensions, and no shipped bundle carries such an entry, so nothing is withheld today. Carrier: none.
Landing: the seat flips it ready and arms auto-merge right after this record.
Fixes #21948closes the card on merge.- the stop-report
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsLanded: PR #21956 →
bab76850cd. The card is closedcompleteddomain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-06T07:41Z · holder of claim6009655572(revised6009832895), which this act releases.- Landed: PR fix(spec): serve a field's translated help on
description, never on an undeclaredhelp#21956 merged through the merge queue at 2026-10-06T07:40Z asbab76850cd. It has one parent (80f9f7e6ba) and is an ancestor oforigin/main.Fixes #21948closed the cardcompleted. - Content check: all 3 files on
origin/mainare blob-equal to the reviewed head3ad2f22bed(at-tier PASS6011100461, ACCEPT6011127313). - What now holds:
- No undeclared key. A served object field no longer carries a
helpkey. The platform-objects corpus served 332 before and serves 0 now, so the console's warning about an undeclared field key stops for this cause. - The translation sits on
description. A field's bundlehelpentry, which the extractor writes fromdescription, is served ondescription. - Precedence is ADR-0029 D9.2a. The catalog loses to an explicit override, decided by
valueOverridesPackagedBase. The REST object reads hand the packaged base, so this applies at the HTTP door. labelis unchanged. Fieldlabelstays flat.- objectui renders the same text. Its three readers fall back to
description, so they show the translated text with no objectui edit. - Type change:
ObjectFieldLikedropshelp?: stringunder its index signature. - The release:
@objectstack/specpatch.
- No undeclared key. A served object field no longer carries a
- Not filed, noted in the ACCEPT, each with its carrier:
FieldTranslationSchema.placeholderis declared and never served. It is held at a note until a bundle carries a fieldplaceholder(filing gate ①).- The docs line
i18n-standard.mdx:166and theFieldTranslationSchema.helpdescribe text should say that a field's bundlehelptranslates the serveddescription. Carrier: the next docs PR on that page. - The pre-existing items: Studio's
saveFieldswrite-back, the extractor's deadhelparm,inlineHelpText, and theobjectExtensionsedge.
- Hot file released:
packages/spec/src/system/i18n-resolver.ts(translateField). runtime strings in thedomain:specpackages carry tracker numbers (spec175 andlint83 messages): this lane's share of the #20513 A/A burn-down #20749's latersystem/stages that renamei18n-resolver.test.tstitles now merge onto this.
This act removes
pm:dispatchedand the assignee.- Landed: PR fix(spec): serve a field's translated help on
- added 2 commits that reference this issue
on Oct 7, 2026
Filing gate: ① product defect —
reach:measured once through a public entry (GET /api/v1/meta/object/:name, then the console UI).Reader: triage seat first-touch → the lane that owns
packages/spec/src/system/i18n-resolver.ts(expecteddomain:spec); one fix PR.Dedup (semantic search, open + closed): objectstack "served object field carries undeclared help key, ingestion choke point cannot canonicalize retired spelling help, inlineHelpText translateObject" → 0 hits; objectui "retired spelling help warning console field help" → 7 hits, all closed, none about a served
helpkey (nearest: objectui#10471, objectui#10176 — different keys).What happens
The metadata read seam overlays translated field help onto the served field def under the key
help:packages/spec/src/system/i18n-resolver.ts,translateObject→translateField:const translatedHelp = lookupObjectFieldAttr(bundle, objectName, name, 'help', opts); if (translatedHelp) next.help = translatedHelp;FieldSchemadeclares nohelpkey. Help text isinlineHelpText(anddescription); the field conversion table inpackages/spec/src/data/field.zod.tsalready listshelpas an alias spelling ofinlineHelpText. So the served field shape carries a key the contract does not declare, on every field whose translation bundle has ahelpentry.Reach — measured on a stock showcase boot
Framework
ce577ec4(objectstack dev --seed-admin, fresh SQLite), objectui atf9f4a62d(console dev server).GET /api/v1/meta/object/sys_useras the seeded admin → 18 of 27 fields carryhelp. Sample,two_factor_enabled:help=description= "Whether two-factor authentication is enabled for this user. Maintained by the better-authtwoFactorplugin."; noinlineHelpText.Showcase's own objects carry none (
GET /api/v1/meta/object/showcase_task→ 0 of 25), so it is the platform objects whose packages ship translation bundles withhelpentries (e.g.packages/platform-objects/src/apps/translations/*.objects.generated.ts).Sign in to the console and load any authenticated page → the console's ingestion step logs one warning per such field, e.g.
523 warnings on one page load, across 73
sys_*objects, repeated on every full load. The value is not rendered.Expected
The served field def carries only keys
FieldSchemadeclares, so a consumer renders the translated help it is sent, and an authenticated page load logs zero warnings for platform objects.Not measured
Whether a non-English locale serves a
helpvalue that differs fromdescription(i.e. whether a translation is actually lost to users today, beyond the warnings). The en-US sample above hashelpequal todescription.Provenance: maintainer instruction in the dispatching session, verbatim — 「把这些问题提成 issue」. Found during a browser dogfood sweep of the console; filed by session
session_01WEARdKfDHDT7rTuEhLyT1k.Generated by Claude Code