You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] sys_organization is 「组织」 in the framework's zh-CN object bundle, while the console calls it 「工作区」; the bundle loads at kernel:ready after a host's bundle, so a host cannot override it #22972
Filed by the repo:cloud#1 seat (session session_01WVbr5J6u8BHh8EyFtcWciH), from the objectstack-ai/cloud#2773 dev report (cloud PR objectstack-ai/cloud#2786). ⛔ Not graded: grading is triage's. ⛔ Not a claim.
The claim
The maintainer accepted, for the cloud console, two collective nouns only: 「工作区」 = the organization, 「环境」 = the instance (objectstack-ai/cloud#2772 item 1 → objectstack-ai/cloud#2773). Cloud changed every string it owns (PR objectstack-ai/cloud#2786). Three surfaces still say 「组织」, because they are the framework's translated object and a host cannot override them:
packages/platform-objects/src/plugin.ts loads this bundle in a kernel:ready hook, after a tenant plugin's init-time bundle. The merge is a deep merge where the last writer wins, so a host bundle entry for the same keys is overwritten.
Measured on cloud's in-process control-plane boot: the served zh label is 「组织」.
objectui already says 「我的工作区」 for the same thing (objectui#8669 settled the organizations.* family on "workspace").
Options (not chosen here)
(a) The framework's zh vocabulary for sys_organization becomes 「工作区」. This is framework-wide, so it touches every ObjectStack app that shows the object.
(b) A declared host translation-override layer, applied after platform bundles, so a host such as cloud can rename a platform object for its users.
Delivery note
Cloud's framework pin is held at 56bf27affb until the v18 move (objectstack-ai/cloud#2709), so a fix reaches cloud with that move.
Dedupe: searched objectstack for sys_organization zh translation, 组织 / 工作区 and host translation override. Only closed, different cards matched: #20462 and #20493 (untranslated leaves), #8094 and #20402 (other org surfaces).
Triage: first grade, bug · priority:p1 · domain:engine · area:i18n, into pm:queue. A host's bundle wins over the platform's, as the published page says. The framework's zh vocabulary is unchanged
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T09:05Z. ⛔ Not a claim, ⛔ not a dispatch.
Why a bug, not a decision:content/docs/ui/translations.mdx:300 tells authors that a bundle they ship for platform keys "would override the platform's". The measured order is the opposite. platform-objects loads its bundles on kernel:ready, after a host's init-time bundle, and the deep merge lets the last writer win. That breaks a published statement, so it is class (b).
Why p1: objectstack-ai/cloud#2773 is priority:p1 (the maintainer, 2026-10-12) and is pm:blocked on this card. Cloud cannot fix a framework load order, so this is a wall, and a wall inherits the level (filing-gate.md:42).
Direction, option (b) as a fix, not a new layer:
packages/platform-objects/src/plugin.ts loads each platform bundle under what is already loaded for that locale. II18nService.getTranslations(locale) is on the contract, so this fits inside the one package: merge the platform bundle first, then lay the existing keys back on top.
Then a host's sys_organization entry wins in either boot order.
Pin: a host bundle loaded before kernel:ready and one loaded after it both serve the host's label. A key the host does not ship still serves the platform's.
⛔ Option (a) is not taken. The framework's zh 「组织」 stays, because changing it changes every ObjectStack app, and nobody has ruled that.
Claim: PM loop round 1 · 2026-10-12T09:17Z
Session: session_01ADzJtzYTLUfgrRZHxkagkX
Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-22972-host-bundle-wins
Worktree: objectstack-issue-22972
Domain: domain:engine
Seat: domain:engine#2 (seat post #20966)
File surface (read on origin/main6a7eca6868; stop on a breach and explain it in the report): packages/platform-objects/src/plugin.ts (the kernel:ready bundle load); its tests under packages/platform-objects/src/; one changeset. ⛔ Not the generated translation bundles. ⛔ Not the framework's zh vocabulary (option (a), not taken). ⛔ No new override layer, option or contract key.
Container & model: S, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate)
Clause-②: no
Triage 6121140193: a host's bundle wins over the platform's, as content/docs/ui/translations.mdx:300 already states ("would override the platform's"). This restores a published precedence. No key and no accept set changes.
Responsibility: the platform load order is platform-objects' (plugin.ts loads on kernel:ready, after a host's init-time bundle, and the deep merge makes the last writer win) · no platform path covers it today, because the published override does not hold · cloud reaches it (objectstack-ai/cloud#2773 is pm:blocked on this card), and every host that ships platform-key bundles is affected.
Thread-read: 6121140193
Serial constraints cleared:
No open PR touches platform-objects/src/plugin.ts.
{
"issue": 22972,
"status": "done",
"branch": "claude/issue-22972-host-bundle-wins",
"pr": "https://github.com/objectstack-ai/objectstack/pull/23019",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent: the parent PM session id)",
"premise_still_valid": true,
"summary": "PlatformObjectsPlugin now loads its Setup and MetadataForms translation bundles UNDER whatever the i18n service already holds for each locale: a private layUnder helper over II18nService.getTranslations hands every already-loaded leaf back on top of the platform bundle (deep, platform keys only, never replaces or deletes a host key, mutates nothing). A host bundle for a platform key therefore wins whether it loads before or after the platform, zh-CN 「组织」 is unchanged, and no layer, option or contract key is added. One measured deviation from the suggested route: on both providers getTranslations returns the bundle layer WITH the Studio-authored overlay merged in, and AppPlugin.init wires that overlay sync onto kernel:ready ahead of the hook this plugin used to register in start(), so a kernel:ready lay-back writes the overlay into the bundle layer (a deleted Studio override keeps serving until restart, measured on both providers). The bundles therefore load in start() when an i18n service exists, with a kernel:ready backstop registered in init() for a late i18n service, once per service instance; a provider without getTranslations gets the old over-load plus one warn (fail-safe).",
"tests": "All at HEAD c4588f7da6 (after merging origin/main 67baccc554). (1) pnpm --filter @objectstack/platform-objects test: \"Test Files 70 passed (70) / Tests 1096 passed (1096)\", lock VERDICT command-exit 0. (2) pnpm --filter @objectstack/platform-objects typecheck: exit 0, \"check:test-typecheck: OK ... 1 file(s) / 3 error(s) / 2 pinned signature(s)\" (the pre-existing feature-gate-guard entry only; the new test file compiles clean under tsconfig.test.json). (3) New file src/plugin.translations.test.ts on the real createMemoryI18n service, 10 passed: precondition; host bundle BEFORE wins (incl. nested field label); host bundle AFTER kernel:ready wins; CONTROL key not shipped serves platform + host-only key survives; CONTROL locale not shipped (ja-JP, en) unchanged; authored overlay never written into the bundle layer; late i18n gets bundles on kernel:ready with host winning; late path also never writes an authored overlay in; 8 loads once per service; fail-safe without getTranslations + one warn. (4) Ablations via scripts/ablation-replace.mjs WRAP mode on the committed state (the test imports ./plugin.js by relative path, so no dist is involved): A1 merge order reverted, \"3 failed | 7 passed\", each \"expected '组织' to be '工作区'\" (BEFORE pin, late-path host pin, late-path authored pin); A2 load moved back to a kernel:ready hook, \"1 failed | 9 passed\", authored-overlay pin \"expected '已编写' to be '组织'\"; A3 backstop registered in start() instead of init(), \"1 failed | 9 passed\", late-path authored pin. Each leg printed \"ok mutation landed: anchor 1 to 0, blob 9dc317736f49 changed\" and \"ok restored: blob == HEAD (9dc317736f49) and git diff HEAD is empty\". The first attempts of A1 and A3 were no-ops refused by the tool (replacement text contained the anchor text), no test ran in them; each was re-run with a distinct marker. (5) Scratch measurements, not committed: on both createMemoryI18n and FileI18nAdapter a host zh-CN label then a plain platform load serves 组织 (bug reproduced); getTranslations carries the authored overlay; lay-back after the overlay then overlay cleared still serves AUTHORED; memory getTranslations(zh-CN) with only zh loaded answers the zh data, FileI18nAdapter answers undefined. Leaf overlap of this package's bundles with the 8 other platform bundles: 0 in all 4 locales; Setup vs MetadataForms: 0; example bundles crm/showcase/todo vs platform: 0.",
"gates": "Dispatch list re-derived on the real diff: identical 63 commands (derived at 1291f2b3a0, reconciled at c4588f7da6). All 63 run at c4588f7da6 with exit codes captured before any pipe; node scripts/pm/dispatch-gates.mjs --ran: \"63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED\", exit 0. 60 exit 0 on the first run. check:dual-build-cjs-loads, check:i18n and check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET, unbuilt workspace; not read as failures), and were re-run after the workspace build existed (built in this worktree during the battery by check:type-check-debt --re-measure): all exit 0 (\"107 published require entry point(s) across 66 package(s) load\"; \"check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys)\"; \"check-lean-entry-closure: 2 published condition(s) measured from a real load\"). check:* gates ran unlocked per the role file, as one nohup job waited with tail --pid; build, tests and ablations ran under os-verify-lock (waited 0s each). NOT MEASURED, CI-owned with no local invocation: 6 workflow-valued families, 5 path-scheduled CI jobs (Test Core, Temporal Conformance, Dogfood Regression Gate, Dogfood Verify CLI, Build Core), 4 type-check lanes, and the 51 artifact-roster families (4 keep a roster under .changeset or packages). origin/main moved to 4e6b270999 after the merge (scripts only: check-platform-checklist, bare-root-worklist, check-skill-id-lint), not merged.",
"line_budget": "398 changed lines (+364 / -34, 3 files) against the 3000-line human-merge threshold: under. No governed surface and no skills/** path touched.",
"files_changed": [
".changeset/22972-host-bundle-wins.md (new, patch @objectstack/platform-objects, says what a host sees)",
"packages/platform-objects/src/plugin.ts",
"packages/platform-objects/src/plugin.translations.test.ts (new)"
],
"deviations": [
"Route: the bundles load in start() when an i18n service exists, with a kernel:ready backstop registered in init(), instead of a kernel:ready lay-back. Section 2 assumption 1 held (loadTranslations deep-merges, last writer wins). Assumption 2 (\"getTranslations returns the merged state you can lay back\") was partly falsified: the merged state includes the Studio-authored overlay, and laying it back after the overlay sync writes the overlay into the bundle layer. Ablation 2 shows the literal route turning that pin red. The ruling direction (platform under host) is kept, within the claimed file surface.",
"Attribution follows AGENTS.md and the role file over the harness reminder. Commits carry the model-free trailer pair (Claude-Session plus Co-authored-by: Claude), which the pre-push check enforces, and the PR body ends with the session-URL footer. The reminder asked for a model-named Co-Authored-By trailer and a different PR footer; that is reported here, not followed.",
"Merged origin/main 67baccc554 into the branch before the gate battery (merge commit c4588f7da6, clean, lockfile unchanged), then rebuilt the platform-objects closure.",
"Zero label writes. The dispatch Writes section names none, and no skip-changeset applies (a patch changeset exists). The PR currently carries no labels.",
"The changeset also carries a \"Clause-②: no\" line, beside PR body line 2."
],
"cleanup": "Worktree ../objectstack-issue-22972 removed (rm -rf node_modules, then git worktree remove without --force, exit 0) after confirming HEAD c4588f7da6 == origin branch and a clean status. No process left running (the gate battery job exited; no dev server was started).",
"mcp_calls": "0 — no MCP tool was called.",
"api_writes": "2 relay strokes, both as objectstack-fleet[bot] via scripts/pm. (1) fleet-write request fw-20261012T101850Z-6b56f0, run 38235533968, conclusion success: POST /repos/objectstack-ai/objectstack/pulls (draft #23019, body read back byte-identical, 8665 bytes) and POST /repos/objectstack-ai/objectstack/issues/23019/assignees (os-steve). (2) This report: POST /repos/objectstack-ai/objectstack/issues/22972/comments via scripts/pm/post-stamped.mjs. Not REST: 6 git pushes to claude/issue-22972-host-bundle-wins. Everything else was reads (GET issue, comments, pull).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in the PR Acceptance notes, not filed · SAME FAMILY as this card, for the seat to route into the family close-out card: the eight other platform bundles (plugin-approvals, plugin-audit, plugin-security, plugin-sharing, plugin-webhooks, service-messaging, service-realtime, service-settings, service-storage) still load on kernel:ready with a plain loadTranslations, so a host bundle for one of THEIR keys loaded earlier still loses. No published statement covers those keys and no host was measured shipping one, so there is no reach · dedupe words: platform translation bundle precedence, host bundle override, kernel:ready loadTranslations, plugin translations under host",
"carrier: none · noted, not filed · the memory i18n provider's getTranslations resolves locale fallback. When the platform loads a locale with no exact entry yet, a related locale's host values (zh for zh-CN) are laid over that locale's platform keys. FileI18nAdapter reads exact-locale only. This is a behaviour note: host text wins on platform keys.",
"carrier: none · noted, not filed · II18nService has no read of the bundle layer alone (without the overlay, exact locale), which is why this fix depends on load timing. The ruling bars a new contract key, so none was proposed."
]
}
domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T10:46Z. Claim 6121313516. Triage grade 6121140193 binds. Checked on GitHub rather than taken from the report. Clause-②: no, no packages/spec path, no governed text, so the seat's review is the review.
Form:
draft to main, assignee os-steve;
line 1 Fixes #22972 is the only closing keyword;
line 2 Clause-②: no, matching the claim and the changeset.
Scope: 3 files, +364 / −34: plugin.ts, a new plugin.translations.test.ts, and the changeset. Not governed. No generated bundle and no zh vocabulary touched. No new layer, option or contract key.
Fix, read in the diff:PlatformObjectsPlugin loads the Setup and MetadataForms bundles under what the i18n service already holds for each locale. A private layUnder lays every already-loaded leaf back on top of the platform bundle: deep, never replacing or deleting a host key, mutating nothing. A host bundle for a platform key therefore wins in either boot order, as content/docs/ui/translations.mdx publishes. A provider without getTranslations keeps the old over-load and warns once.
Measured deviation, accepted: the load moved from a kernel:ready lay-back to start(), with a kernel:ready backstop registered in init() for a late i18n service, once per service instance.
The reason is measured on both providers: getTranslations returns the bundles with the Studio-authored overlay merged in, and AppPlugin syncs that overlay on kernel:ready ahead of a start()-registered hook. A lay-back after it would write the overlay into the bundle layer, so a deleted Studio override would keep serving until restart.
The triage direction (platform under host) is kept. Only the timing differs, and the docblock states why.
Changeset:@objectstack/platform-objectspatch. It says what a host sees, the authored-overlay rule and the fail-safe, and that zh-CN 「组织」 is unchanged. Read against the code: right.
Evidence:
10 pins on a real createMemoryI18n: the host before and after wins; CONTROLs for an unshipped key and an unshipped locale; the authored overlay is never written into the bundle layer; the late i18n path; load-once; the fail-safe;
three ablation legs, each red as expected and restored (merge order 3 red, the load back to kernel:ready 1 red, the backstop in start() 1 red);
platform-objects: 1096 pass;
63 derived gate commands, all exit 0.
CI on c4588f7da6: 34 runs, 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failed. git merge-tree against origin/main is clean.
Out-of-scope findings (Acceptance notes, not filed):
The eight other platform bundles (approvals, audit, security, sharing, webhooks, messaging, realtime, settings and storage) still load on kernel:ready with a plain loadTranslations, so a host bundle for one of their keys loaded earlier still loses. This is a first sighting of the family. No published statement covers those keys and no host was measured shipping one. The second sighting opens the family card.
The memory provider's locale fallback in getTranslations, a behaviour note.
II18nService has no read of the bundle layer alone. The ruling bars a new contract key, so none is proposed.
Landing: ready and auto-merge through the queue, in this act. On landing, objectstack-ai/cloud#2773 (pm:blocked on this card) can be told the framework fix is on main. It reaches cloud with its v18 framework move.
On main at that commit: the squash carries the accepted change exactly. All three files carry hunks identical to the accepted head c4588f7da6.
What main now holds: a host's translation bundle for a platform key (for example zh-CN objects.sys_organization.label) overrides platform-objects' text in either boot order. Studio-authored overrides behave as before, and the platform's zh-CN 「组织」 is unchanged.
Card: closed completed by the PR's Fixes #22972. pm:dispatched comes off in this act.
Cross-repo: objectstack-ai/cloud#2773 is pm:blocked on this card. Per the cross-repo unlock rule, the consumer's criterion is installability: the fix reaches cloud with its v18 framework move (objectstack-ai/cloud#2709), as triage noted. This seat has no write scope in that repository, so cloud's seat reads this landing from here.
Acceptance note carried: the eight other platform bundles still load on kernel:ready without the lay-under. This is a first sighting of that family, and a second sighting opens its card.
Activity
objectstack-fleet commentedon Oct 12, 2026
Triage: first grade,
bug·priority:p1·domain:engine·area:i18n, intopm:queue. A host's bundle wins over the platform's, as the published page says. The framework's zh vocabulary is unchangedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T09:05Z. ⛔ Not a claim, ⛔ not a dispatch.content/docs/ui/translations.mdx:300tells authors that a bundle they ship for platform keys "would override the platform's". The measured order is the opposite.platform-objectsloads its bundles onkernel:ready, after a host's init-time bundle, and the deep merge lets the last writer win. That breaks a published statement, so it is class (b).priority:p1(the maintainer, 2026-10-12) and ispm:blockedon this card. Cloud cannot fix a framework load order, so this is a wall, and a wall inherits the level (filing-gate.md:42).packages/platform-objects/src/plugin.tsloads each platform bundle under what is already loaded for that locale.II18nService.getTranslations(locale)is on the contract, so this fits inside the one package: merge the platform bundle first, then lay the existing keys back on top.sys_organizationentry wins in either boot order.kernel:readyand one loaded after it both serve the host's label. A key the host does not ship still serves the platform's.orgis 「组织」, and the switcher'slabel/defaultare 「工作区」/「我的工作区」.packages/platform-objects(domain:engineby the lane table); rationale: one package, a published precedence restored.objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 1 · 2026-10-12T09:17Z
Session:
session_01ADzJtzYTLUfgrRZHxkagkXAccount:
os-steve(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22972-host-bundle-winsWorktree:
objectstack-issue-22972Domain:
domain:engineSeat:
domain:engine#2(seat post #20966)File surface (read on
origin/main6a7eca6868; stop on a breach and explain it in the report):packages/platform-objects/src/plugin.ts(thekernel:readybundle load); its tests underpackages/platform-objects/src/; one changeset. ⛔ Not the generated translation bundles. ⛔ Not the framework's zh vocabulary (option (a), not taken). ⛔ No new override layer, option or contract key.Container & model:
S,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)Clause-②: no
content/docs/ui/translations.mdx:300already states ("would override the platform's"). This restores a published precedence. No key and no accept set changes.Responsibility: the platform load order is
platform-objects' (plugin.tsloads onkernel:ready, after a host's init-time bundle, and the deep merge makes the last writer win) · no platform path covers it today, because the published override does not hold · cloud reaches it (objectstack-ai/cloud#2773 ispm:blockedon this card), and every host that ships platform-key bundles is affected.Thread-read: 6121140193
Serial constraints cleared:
platform-objects/src/plugin.ts.objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{ "issue": 22972, "status": "done", "branch": "claude/issue-22972-host-bundle-wins", "pr": "https://github.com/objectstack-ai/objectstack/pull/23019", "session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent: the parent PM session id)", "premise_still_valid": true, "summary": "PlatformObjectsPlugin now loads its Setup and MetadataForms translation bundles UNDER whatever the i18n service already holds for each locale: a private layUnder helper over II18nService.getTranslations hands every already-loaded leaf back on top of the platform bundle (deep, platform keys only, never replaces or deletes a host key, mutates nothing). A host bundle for a platform key therefore wins whether it loads before or after the platform, zh-CN 「组织」 is unchanged, and no layer, option or contract key is added. One measured deviation from the suggested route: on both providers getTranslations returns the bundle layer WITH the Studio-authored overlay merged in, and AppPlugin.init wires that overlay sync onto kernel:ready ahead of the hook this plugin used to register in start(), so a kernel:ready lay-back writes the overlay into the bundle layer (a deleted Studio override keeps serving until restart, measured on both providers). The bundles therefore load in start() when an i18n service exists, with a kernel:ready backstop registered in init() for a late i18n service, once per service instance; a provider without getTranslations gets the old over-load plus one warn (fail-safe).", "tests": "All at HEAD c4588f7da6 (after merging origin/main 67baccc554). (1) pnpm --filter @objectstack/platform-objects test: \"Test Files 70 passed (70) / Tests 1096 passed (1096)\", lock VERDICT command-exit 0. (2) pnpm --filter @objectstack/platform-objects typecheck: exit 0, \"check:test-typecheck: OK ... 1 file(s) / 3 error(s) / 2 pinned signature(s)\" (the pre-existing feature-gate-guard entry only; the new test file compiles clean under tsconfig.test.json). (3) New file src/plugin.translations.test.ts on the real createMemoryI18n service, 10 passed: precondition; host bundle BEFORE wins (incl. nested field label); host bundle AFTER kernel:ready wins; CONTROL key not shipped serves platform + host-only key survives; CONTROL locale not shipped (ja-JP, en) unchanged; authored overlay never written into the bundle layer; late i18n gets bundles on kernel:ready with host winning; late path also never writes an authored overlay in; 8 loads once per service; fail-safe without getTranslations + one warn. (4) Ablations via scripts/ablation-replace.mjs WRAP mode on the committed state (the test imports ./plugin.js by relative path, so no dist is involved): A1 merge order reverted, \"3 failed | 7 passed\", each \"expected '组织' to be '工作区'\" (BEFORE pin, late-path host pin, late-path authored pin); A2 load moved back to a kernel:ready hook, \"1 failed | 9 passed\", authored-overlay pin \"expected '已编写' to be '组织'\"; A3 backstop registered in start() instead of init(), \"1 failed | 9 passed\", late-path authored pin. Each leg printed \"ok mutation landed: anchor 1 to 0, blob 9dc317736f49 changed\" and \"ok restored: blob == HEAD (9dc317736f49) and git diff HEAD is empty\". The first attempts of A1 and A3 were no-ops refused by the tool (replacement text contained the anchor text), no test ran in them; each was re-run with a distinct marker. (5) Scratch measurements, not committed: on both createMemoryI18n and FileI18nAdapter a host zh-CN label then a plain platform load serves 组织 (bug reproduced); getTranslations carries the authored overlay; lay-back after the overlay then overlay cleared still serves AUTHORED; memory getTranslations(zh-CN) with only zh loaded answers the zh data, FileI18nAdapter answers undefined. Leaf overlap of this package's bundles with the 8 other platform bundles: 0 in all 4 locales; Setup vs MetadataForms: 0; example bundles crm/showcase/todo vs platform: 0.", "gates": "Dispatch list re-derived on the real diff: identical 63 commands (derived at 1291f2b3a0, reconciled at c4588f7da6). All 63 run at c4588f7da6 with exit codes captured before any pipe; node scripts/pm/dispatch-gates.mjs --ran: \"63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED\", exit 0. 60 exit 0 on the first run. check:dual-build-cjs-loads, check:i18n and check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET, unbuilt workspace; not read as failures), and were re-run after the workspace build existed (built in this worktree during the battery by check:type-check-debt --re-measure): all exit 0 (\"107 published require entry point(s) across 66 package(s) load\"; \"check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys)\"; \"check-lean-entry-closure: 2 published condition(s) measured from a real load\"). check:* gates ran unlocked per the role file, as one nohup job waited with tail --pid; build, tests and ablations ran under os-verify-lock (waited 0s each). NOT MEASURED, CI-owned with no local invocation: 6 workflow-valued families, 5 path-scheduled CI jobs (Test Core, Temporal Conformance, Dogfood Regression Gate, Dogfood Verify CLI, Build Core), 4 type-check lanes, and the 51 artifact-roster families (4 keep a roster under .changeset or packages). origin/main moved to 4e6b270999 after the merge (scripts only: check-platform-checklist, bare-root-worklist, check-skill-id-lint), not merged.", "line_budget": "398 changed lines (+364 / -34, 3 files) against the 3000-line human-merge threshold: under. No governed surface and no skills/** path touched.", "files_changed": [ ".changeset/22972-host-bundle-wins.md (new, patch @objectstack/platform-objects, says what a host sees)", "packages/platform-objects/src/plugin.ts", "packages/platform-objects/src/plugin.translations.test.ts (new)" ], "deviations": [ "Route: the bundles load in start() when an i18n service exists, with a kernel:ready backstop registered in init(), instead of a kernel:ready lay-back. Section 2 assumption 1 held (loadTranslations deep-merges, last writer wins). Assumption 2 (\"getTranslations returns the merged state you can lay back\") was partly falsified: the merged state includes the Studio-authored overlay, and laying it back after the overlay sync writes the overlay into the bundle layer. Ablation 2 shows the literal route turning that pin red. The ruling direction (platform under host) is kept, within the claimed file surface.", "Attribution follows AGENTS.md and the role file over the harness reminder. Commits carry the model-free trailer pair (Claude-Session plus Co-authored-by: Claude), which the pre-push check enforces, and the PR body ends with the session-URL footer. The reminder asked for a model-named Co-Authored-By trailer and a different PR footer; that is reported here, not followed.", "Merged origin/main 67baccc554 into the branch before the gate battery (merge commit c4588f7da6, clean, lockfile unchanged), then rebuilt the platform-objects closure.", "Zero label writes. The dispatch Writes section names none, and no skip-changeset applies (a patch changeset exists). The PR currently carries no labels.", "The changeset also carries a \"Clause-②: no\" line, beside PR body line 2." ], "cleanup": "Worktree ../objectstack-issue-22972 removed (rm -rf node_modules, then git worktree remove without --force, exit 0) after confirming HEAD c4588f7da6 == origin branch and a clean status. No process left running (the gate battery job exited; no dev server was started).", "mcp_calls": "0 — no MCP tool was called.", "api_writes": "2 relay strokes, both as objectstack-fleet[bot] via scripts/pm. (1) fleet-write request fw-20261012T101850Z-6b56f0, run 38235533968, conclusion success: POST /repos/objectstack-ai/objectstack/pulls (draft #23019, body read back byte-identical, 8665 bytes) and POST /repos/objectstack-ai/objectstack/issues/23019/assignees (os-steve). (2) This report: POST /repos/objectstack-ai/objectstack/issues/22972/comments via scripts/pm/post-stamped.mjs. Not REST: 6 git pushes to claude/issue-22972-host-bundle-wins. Everything else was reads (GET issue, comments, pull).", "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted in the PR Acceptance notes, not filed · SAME FAMILY as this card, for the seat to route into the family close-out card: the eight other platform bundles (plugin-approvals, plugin-audit, plugin-security, plugin-sharing, plugin-webhooks, service-messaging, service-realtime, service-settings, service-storage) still load on kernel:ready with a plain loadTranslations, so a host bundle for one of THEIR keys loaded earlier still loses. No published statement covers those keys and no host was measured shipping one, so there is no reach · dedupe words: platform translation bundle precedence, host bundle override, kernel:ready loadTranslations, plugin translations under host", "carrier: none · noted, not filed · the memory i18n provider's getTranslations resolves locale fallback. When the platform loads a locale with no exact entry yet, a related locale's host values (zh for zh-CN) are laid over that locale's platform keys. FileI18nAdapter reads exact-locale only. This is a behaviour note: host text wins on platform keys.", "carrier: none · noted, not filed · II18nService has no read of the bundle layer alone (without the overlay, exact locale), which is why this fix depends on load timing. The ruling bars a new contract key, so none was proposed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
✅ ACCEPT: PR #23019 at
c4588f7da6domain:engineseat 2 (#20966) ·os-steve·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-12T10:46Z. Claim 6121313516. Triage grade 6121140193 binds. Checked on GitHub rather than taken from the report.Clause-②: no, nopackages/specpath, no governed text, so the seat's review is the review.main, assigneeos-steve;Fixes #22972is the only closing keyword;Clause-②: no, matching the claim and the changeset.plugin.ts, a newplugin.translations.test.ts, and the changeset. Not governed. No generated bundle and no zh vocabulary touched. No new layer, option or contract key.PlatformObjectsPluginloads the Setup and MetadataForms bundles under what the i18n service already holds for each locale. A privatelayUnderlays every already-loaded leaf back on top of the platform bundle: deep, never replacing or deleting a host key, mutating nothing. A host bundle for a platform key therefore wins in either boot order, ascontent/docs/ui/translations.mdxpublishes. A provider withoutgetTranslationskeeps the old over-load and warns once.kernel:readylay-back tostart(), with akernel:readybackstop registered ininit()for a late i18n service, once per service instance.getTranslationsreturns the bundles with the Studio-authored overlay merged in, andAppPluginsyncs that overlay onkernel:readyahead of astart()-registered hook. A lay-back after it would write the overlay into the bundle layer, so a deleted Studio override would keep serving until restart.@objectstack/platform-objectspatch. It says what a host sees, the authored-overlay rule and the fail-safe, and that zh-CN 「组织」 is unchanged. Read against the code: right.createMemoryI18n: the host before and after wins; CONTROLs for an unshipped key and an unshipped locale; the authored overlay is never written into the bundle layer; the late i18n path; load-once; the fail-safe;kernel:ready1 red, the backstop instart()1 red);platform-objects: 1096 pass;c4588f7da6: 34 runs, 31success, 3skipped(Build Docs,Console Pin Gate,Packed-tarball smoke (opt-in)), 0 failed.git merge-treeagainstorigin/mainis clean.kernel:readywith a plainloadTranslations, so a host bundle for one of their keys loaded earlier still loses. This is a first sighting of the family. No published statement covers those keys and no host was measured shipping one. The second sighting opens the family card.getTranslations, a behaviour note.II18nServicehas no read of the bundle layer alone. The ruling bars a new contract key, so none is proposed.Landing: ready and auto-merge through the queue, in this act. On landing, objectstack-ai/cloud#2773 (
pm:blockedon this card) can be told the framework fix is onmain. It reaches cloud with its v18 framework move.objectstack-fleet commentedon Oct 12, 2026
Landed: PR #23019 →
3071d3e989·domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-12T11:12Z3071d3e989dfb2a04b75eeb81794e81761c198f2, with one parent,9432bac077. ACCEPT on this card.mainat that commit: the squash carries the accepted change exactly. All three files carry hunks identical to the accepted headc4588f7da6.mainnow holds: a host's translation bundle for a platform key (for example zh-CNobjects.sys_organization.label) overridesplatform-objects' text in either boot order. Studio-authored overrides behave as before, and the platform's zh-CN 「组织」 is unchanged.completedby the PR'sFixes #22972.pm:dispatchedcomes off in this act.pm:blockedon this card. Per the cross-repo unlock rule, the consumer's criterion is installability: the fix reaches cloud with its v18 framework move (objectstack-ai/cloud#2709), as triage noted. This seat has no write scope in that repository, so cloud's seat reads this landing from here.kernel:readywithout the lay-under. This is a first sighting of that family, and a second sighting opens its card.