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
A .custom object extension that renames only label keeps the base package's translated pluralLabel, so every list title keeps the old name #30391
Finding from the #30311 card thread (os-dev-report 6654022838, out_of_scope_findings item 2), measured on a local showcase boot. ⛔ Not a claim. Triage sets lane and grade.
What happens. A .customobject_extension that sets only label on an installed object (no pluralLabel) is served with the new label but the base package's translated pluralLabel. List titles read the plural first, so after #30311 (PR #30388) the sidebar and list title still show the old name in its plural form; Studio shows the same served answer.
Expected (for the owner to confirm against the label-order ruling of #30062): an override that renames label without a pluralLabel does not keep the base's plural for the old name: either the served plural follows the override's label, or the override is refused / warned at author time for leaving the plural stale. The fix is in the served label order (packages/metadata-protocol / spec), not in the console.
Reader. The /meta label order for overridden items (domain:server, the #30062 lineage), or domain:spec if an author-time rule is chosen.
Claim: PM loop round 1
Session: session_01Utp6CYVV6fcAQrNTDQRvPb (seat) · dev = project card thread session_01Utp6CYaDMYnWxbm95qon7G
Account: os-bill
Branch: claude/project-thread-ktw7m9 (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30391
Domain: domain:server
Seat: domain:server#5
File surface: packages/objectql/src/** (the object fold) and/or packages/metadata-protocol/src/**, packages/rest/src/** (the served translation), with tests; content/docs/** only for sentences this PR makes false (not releases/**); one changeset. ⛔ packages/spec/**, ui/**, skills/**, docs/adr/**, .claude/**, AGENTS.md (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (opus), effort: high
Clause-②: no (provisional) — no exported member or accepted value changes; the served plural changes meaning (Metadata-protocol). The dev corrects it to an arm if the diff moves an answered shape
Metadata-protocol: semantic (provisional) — what an extension's label does to the served plural
Responsibility: the object fold keeps the base's pluralLabel (and its translation) when a .custom object_extension renames only label, so list titles keep the old name (measured on a showcase boot, #30311 report 6654022838) | none: the customer sees two names for one object | every tenant that renames an installed object's label through its pair
Thread-read: 6655169453
Premise: holds per the measurement; route ruled by the seat (served side, PM discretion, under the #30062 label-order ruling 6631250894): an extension that changes label without pluralLabel serves what an object with no pluralLabel serves, the pair's own translation first. A spec/D10 conflict stops as needs_decision.
Serial constraints cleared: PR #30353 (#30062 item 3) merged; no open PR on the object fold's label keys at claim time (dev re-checks).
Order: project file server-seat5/dispatch-30391.md, delivered to the card thread.
{
"issue": 30391,
"status": "done",
"branch": "claude/project-thread-ktw7m9",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30459",
"session": "session_01Utp6CYaDMYnWxbm95qon7G",
"premise_still_valid": true,
"summary": "Measured first on main 09720e2c, real SchemaRegistry + protocol + RestServer: an object that declares no pluralLabel is served with no pluralLabel key in en and zh-CN; a .custom extension renaming only label was served with the base plural (Tasks in en, the base translation 任务 in zh-CN). The PM assumption held and was incomplete: the fold kept the base plural, AND the translator answers a missing pluralLabel from the catalog (the en catalog repeats the source), so both halves are needed. Fold (objectql mergeObjectDefinitions): an applied label that changes the running label with no pluralLabel deletes the running plural; a tenant-authored plural stays (D9.2a yield); idempotent; getPackagedObjectBase untouched. /meta read (rest withoutStaleObjectPlural, item + list): where the served object has no plural, the packaged base has one and the label diverged, the catalog plural is replaced by the owner package's .custom pair translation (getPairTranslationBundle on the packaged base's _packageId) or dropped. No spec or ADR text change. Declared Clause-② no (narrowing): the answered shape moves for every label-only extension, showcase_account included, so the PR is BREAKING and its merge needs the maintainer's word.",
"tests": "Red first on 09720e2c: packages/rest/src/meta-object-extension-plural-label.test.ts 4 failed / 7 passed (en + zh-CN rename, pair plural, non-pair row). Green: that file 11/11; packages/objectql/src/registry-extension-plural-label.test.ts 11/11. At f71f7bdc (after merging origin/main): objectql test 551 files, 10983 passed; rest test 387 files, 7346 passed / 332 skipped (both under os-verify-lock, VERDICT command-exit 0); objectql + rest typecheck exit 0 (check:test-typecheck included); eslint on the 4 changed source/test files exit 0. Ablation A (fold delete guarded off, marker ABLATE30391A, objectql rebuilt, ablation-dist-preflight exit 0): objectql 2/11 red, rest 4/11 red; restored blob == HEAD 95314c81d4e0, git diff HEAD empty, rebuilt, preflight --absent exit 0. Ablation B (withoutStaleObjectPlural early-returns, rest tests import src): rest 4/11 red (en too: the en catalog repeats the source plural); restored blob == HEAD b873c9307021, git diff HEAD empty.",
"gates": "dispatch-gates --commands at f71f7bdc: 91 commands, run in a loop with exit codes captured before any pipe; the ledger commit added 8 more (99 total). First pass 94 exit 0; check-adr-0087-registration exit 1 (FROM -> TO block contradicted not-required no-migration-prescription; replaced by a sentence, committed, re-run exit 0); check:engine-double-contract exit 1 (new findOne double; --write added one ledger row, re-run exit 0); check-plugin-teardown-shape --self-test, check:skill-examples, check:dual-build-cjs-loads exit 3 then exit 0 after the pinned-commit fetch and a whole-workspace build. The 8 added families all exit 0. --ran at a1107d3c6: 99 derived, 99 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. pnpm lint at a1107d3c6 exit 0. Records: project files server-seat5/gates-30391-cmds.txt and gates-30391-ran.txt. PR check-runs: not read at report time.",
"dogfood_battery": "f71f7bdc (code identical to head a1107d3c6; the two later commits are the changeset and the engine-double ledger) · os-verify-lock: VERDICT command-exit 0 · Test Files 419 passed / 3 skipped (422) · Tests 3492 passed / 36 skipped (3528) · 0 failures",
"line_budget": "+536 / -8 over 8 paths (objectql source +16/-1, rest source +99/-2, tests +397, docs +5/-2, changeset +20, ledger +5)",
"files_changed": [
".changeset/30391-extension-rename-drops-stale-plural.md",
"content/docs/data-modeling/object-extensions.mdx",
"content/docs/ui/translations.mdx",
"packages/objectql/src/registry-extension-plural-label.test.ts",
"packages/objectql/src/registry.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/rest/src/meta-object-extension-plural-label.test.ts",
"scripts/engine-double-contract.pinned.json"
],
"deviations": "1) scripts/engine-double-contract.pinned.json is outside the order's file surface: a generated ledger row for the new pin's findOne double, written by check-engine-double-contract --write (precedent: PR #30353). 2) Clause-② corrected from the claim's provisional 'no' to 'no (narrowing)': the served pluralLabel key disappears for every label-only extension, code ones included (showcase_account in the shipped showcase), so the changeset carries **BREAKING** and an adr-0087 not-required (no-migration-prescription) marker. 3) The harness's PR assign/review step skipped per the order; the PR carries no assignee or reviewer. 4) The fold rule is generic (any extension, not only .custom), since the fold has one merge for every extender.",
"mcp_calls": "5 — reads only: issue_read x4 (get #30391, get_comments #30391 x2, get_comments #30311), pull_request_read x1 (get #30459)",
"api_writes": "2 — relay pr_create (POST pulls, #30459, read-back identical 9788 bytes); this os-dev-report comment via post-stamped (POST issues/30391/comments); plus git push to the designated branch",
"open_questions": [
{ "question": "Land a served-value narrowing (BREAKING) for a p2 label fix?", "options": ["A merge with the maintainer's word as declared", "B add pluralLabel to the showcase's own account extension in a follow-up so the demo keeps a plural"], "recommendation": "A, and B as a small examples follow-up: the narrowing is the card's intended behaviour, and the showcase change is cosmetic" }
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat (domain:server#5) · noted, not filed: an object's served label reads no .custom pair translation (PR #30353 left objects out of pairBundle), so a pair translating both strings gets its plural translated and its label served as written",
"carrier: seat (domain:server#5) · noted, not filed: form-routes.ts translates the public form's object schema on its own path; for a renamed object it can still carry the catalog plural (not measured)",
"carrier: seat · noted, not filed: skills/objectstack-data/SKILL.md Object Extension Model says an extension can add but never remove; a renaming label now takes the base plural off (Skills-docs follow-up)"
]
}
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p2 ·
domain:server· 2026-11-05T10:03Z⛔ Not a claim.
.customextension that renames onlylabelis served with the base package's translatedpluralLabel, so list titles keep the old name./metaserved label order (the Declared overrides: a mapping override publishes but is never served, doc and mapping overrides cannot be restored, and an override's label can be hidden by translation #30062 lineage). If the owner instead picks an author-time warning or refusal, it moves todomain:specthen.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Claim: PM loop round 1
Session:
session_01Utp6CYVV6fcAQrNTDQRvPb(seat) · dev = project card threadsession_01Utp6CYaDMYnWxbm95qon7GAccount:
os-billBranch:
claude/project-thread-ktw7m9(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30391Domain:
domain:serverSeat:
domain:server#5File surface:
packages/objectql/src/**(the object fold) and/orpackages/metadata-protocol/src/**,packages/rest/src/**(the served translation), with tests;content/docs/**only for sentences this PR makes false (notreleases/**); one changeset. ⛔packages/spec/**,ui/**,skills/**,docs/adr/**,.claude/**,AGENTS.md(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus),effort: highClause-②: no (provisional) — no exported member or accepted value changes; the served plural changes meaning (Metadata-protocol). The dev corrects it to an arm if the diff moves an answered shape
Metadata-protocol: semantic (provisional) — what an extension's
labeldoes to the served pluralResponsibility:
the object fold keeps the base's pluralLabel (and its translation) when a .custom object_extension renames only label, so list titles keep the old name (measured on a showcase boot, #30311 report 6654022838) | none: the customer sees two names for one object | every tenant that renames an installed object's label through its pairThread-read: 6655169453
Premise: holds per the measurement; route ruled by the seat (served side, PM discretion, under the #30062 label-order ruling 6631250894): an extension that changes
labelwithoutpluralLabelserves what an object with nopluralLabelserves, the pair's own translation first. A spec/D10 conflict stops as needs_decision.Serial constraints cleared:
PR #30353 (#30062 item 3) merged; no open PR on the object fold's label keys at claim time (dev re-checks).Order: project file
server-seat5/dispatch-30391.md, delivered to the card thread.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
os-dev-report
{ "issue": 30391, "status": "done", "branch": "claude/project-thread-ktw7m9", "pr": "https://github.com/objectstack-ai/objectstack/pull/30459", "session": "session_01Utp6CYaDMYnWxbm95qon7G", "premise_still_valid": true, "summary": "Measured first on main 09720e2c, real SchemaRegistry + protocol + RestServer: an object that declares no pluralLabel is served with no pluralLabel key in en and zh-CN; a .custom extension renaming only label was served with the base plural (Tasks in en, the base translation 任务 in zh-CN). The PM assumption held and was incomplete: the fold kept the base plural, AND the translator answers a missing pluralLabel from the catalog (the en catalog repeats the source), so both halves are needed. Fold (objectql mergeObjectDefinitions): an applied label that changes the running label with no pluralLabel deletes the running plural; a tenant-authored plural stays (D9.2a yield); idempotent; getPackagedObjectBase untouched. /meta read (rest withoutStaleObjectPlural, item + list): where the served object has no plural, the packaged base has one and the label diverged, the catalog plural is replaced by the owner package's .custom pair translation (getPairTranslationBundle on the packaged base's _packageId) or dropped. No spec or ADR text change. Declared Clause-② no (narrowing): the answered shape moves for every label-only extension, showcase_account included, so the PR is BREAKING and its merge needs the maintainer's word.", "tests": "Red first on 09720e2c: packages/rest/src/meta-object-extension-plural-label.test.ts 4 failed / 7 passed (en + zh-CN rename, pair plural, non-pair row). Green: that file 11/11; packages/objectql/src/registry-extension-plural-label.test.ts 11/11. At f71f7bdc (after merging origin/main): objectql test 551 files, 10983 passed; rest test 387 files, 7346 passed / 332 skipped (both under os-verify-lock, VERDICT command-exit 0); objectql + rest typecheck exit 0 (check:test-typecheck included); eslint on the 4 changed source/test files exit 0. Ablation A (fold delete guarded off, marker ABLATE30391A, objectql rebuilt, ablation-dist-preflight exit 0): objectql 2/11 red, rest 4/11 red; restored blob == HEAD 95314c81d4e0, git diff HEAD empty, rebuilt, preflight --absent exit 0. Ablation B (withoutStaleObjectPlural early-returns, rest tests import src): rest 4/11 red (en too: the en catalog repeats the source plural); restored blob == HEAD b873c9307021, git diff HEAD empty.", "gates": "dispatch-gates --commands at f71f7bdc: 91 commands, run in a loop with exit codes captured before any pipe; the ledger commit added 8 more (99 total). First pass 94 exit 0; check-adr-0087-registration exit 1 (FROM -> TO block contradicted not-required no-migration-prescription; replaced by a sentence, committed, re-run exit 0); check:engine-double-contract exit 1 (new findOne double; --write added one ledger row, re-run exit 0); check-plugin-teardown-shape --self-test, check:skill-examples, check:dual-build-cjs-loads exit 3 then exit 0 after the pinned-commit fetch and a whole-workspace build. The 8 added families all exit 0. --ran at a1107d3c6: 99 derived, 99 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. pnpm lint at a1107d3c6 exit 0. Records: project files server-seat5/gates-30391-cmds.txt and gates-30391-ran.txt. PR check-runs: not read at report time.", "dogfood_battery": "f71f7bdc (code identical to head a1107d3c6; the two later commits are the changeset and the engine-double ledger) · os-verify-lock: VERDICT command-exit 0 · Test Files 419 passed / 3 skipped (422) · Tests 3492 passed / 36 skipped (3528) · 0 failures", "line_budget": "+536 / -8 over 8 paths (objectql source +16/-1, rest source +99/-2, tests +397, docs +5/-2, changeset +20, ledger +5)", "files_changed": [ ".changeset/30391-extension-rename-drops-stale-plural.md", "content/docs/data-modeling/object-extensions.mdx", "content/docs/ui/translations.mdx", "packages/objectql/src/registry-extension-plural-label.test.ts", "packages/objectql/src/registry.ts", "packages/rest/src/meta-item-read-gate.ts", "packages/rest/src/meta-object-extension-plural-label.test.ts", "scripts/engine-double-contract.pinned.json" ], "deviations": "1) scripts/engine-double-contract.pinned.json is outside the order's file surface: a generated ledger row for the new pin's findOne double, written by check-engine-double-contract --write (precedent: PR #30353). 2) Clause-② corrected from the claim's provisional 'no' to 'no (narrowing)': the served pluralLabel key disappears for every label-only extension, code ones included (showcase_account in the shipped showcase), so the changeset carries **BREAKING** and an adr-0087 not-required (no-migration-prescription) marker. 3) The harness's PR assign/review step skipped per the order; the PR carries no assignee or reviewer. 4) The fold rule is generic (any extension, not only .custom), since the fold has one merge for every extender.", "mcp_calls": "5 — reads only: issue_read x4 (get #30391, get_comments #30391 x2, get_comments #30311), pull_request_read x1 (get #30459)", "api_writes": "2 — relay pr_create (POST pulls, #30459, read-back identical 9788 bytes); this os-dev-report comment via post-stamped (POST issues/30391/comments); plus git push to the designated branch", "open_questions": [ { "question": "Land a served-value narrowing (BREAKING) for a p2 label fix?", "options": ["A merge with the maintainer's word as declared", "B add pluralLabel to the showcase's own account extension in a follow-up so the demo keeps a plural"], "recommendation": "A, and B as a small examples follow-up: the narrowing is the card's intended behaviour, and the showcase change is cosmetic" } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat (domain:server#5) · noted, not filed: an object's served label reads no .custom pair translation (PR #30353 left objects out of pairBundle), so a pair translating both strings gets its plural translated and its label served as written", "carrier: seat (domain:server#5) · noted, not filed: form-routes.ts translates the public form's object schema on its own path; for a renamed object it can still carry the catalog plural (not measured)", "carrier: seat · noted, not filed: skills/objectstack-data/SKILL.md Object Extension Model says an extension can add but never remove; a renaming label now takes the base plural off (Skills-docs follow-up)" ] }Report taken 2026-11-05T12:22Z.
Generated by Claude Code