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
An overridden object's validation message never takes its .custom pair's translation, while the object's labels do #30245
Filing gate: ③ an accepted deviation of #30193 (PR #30236), carried as a follow-up; contract review 6648581045, should-fix 2. ⛔ Not a claim. Suggested-grade: p3, because it is visible only on an overridden object whose .custom pair ships its own translation of a validation message, and nothing is served with a pair input until #30062 lands. Route: domain:spec (resolver) together with objectql.
What a user sees. Once #30062 passes pairBundle, an overridden object's labels follow the ruled order (the pair's translation, then the override's own text, then the base's translation). Its validations[].message does not. A message the override reworded never takes the pair's translation. A message it left alone reads the merged catalog, where the pair's translation can lose to the base's by merge order (packages/services/service-i18n/src/file-i18n-adapter.ts:189 deep-merges every package; readAuthoredTranslationLayer merges authored rows by item name).
Why it was left.translateObjectValidations stays on the plain merged bundle because the objectql rule evaluator (packages/objectql/src/validation/rule-validator.ts:3785) answers the same message from i18n.t() with no pair input, and the pinned invariant is refusal text = served text. Moving only the resolver would break that invariant.
Wanted
Give the rule evaluator the same pair input the resolver gets (the override's packagedBase and its pair's own translations), and move translateObjectValidations and the evaluator onto the labelCatalogs / catalogFor rule together.
Pin refusal text = served text on an overridden object with a pair translation, for a reworded and an unchanged message.
Claim: PM loop round 1 · 2026-11-05T16:44Z
Session: session_01L6bhgVugtCCwBs98FeR27P (seat); the dev is a card thread the project's coordinator opens
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
Worktree: objectstack-issue-30245
Domain: domain:spec
Seat: domain:spec#2
File surface: packages/spec/src/system/i18n-resolver.ts, the translateObjectValidations region and its use of labelCatalogs / catalogFor only (⛔ resolveBundleLocale: #30538, domain:spec#3, same file, other region; the later lander merges main); packages/objectql/src/validation/rule-validator.ts, the validation-message lookup only; the wiring that hands the evaluator the override's packagedBase and its pair's translations (where #30062 hands them to the read door: packages/rest/src/meta-item-read-gate.ts / packages/metadata-protocol/src/protocol.ts, read first, edit only if the evaluator cannot reach them otherwise); their tests; one changeset. ⛔ packages/services/service-i18n/** merge order (report, do not fix), ui/**, skills/**, docs/adr/**, .claude/**, AGENTS.md (stop on breach; explain in the report)
Container & model: M, mode:thread (cross-package: spec resolver plus the objectql evaluator), model: default (no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: packages/objectql (the rule evaluator answers a validation message from i18n.t() with no pair input) and packages/spec (translateObjectValidations stays on the merged bundle to match it) | no platform path: labels already follow the ruled order through labelCatalogs, validation messages do not | every reader of an overridden object whose .custom pair ships a translated validation message, now that #30062 serves pair input
Thread-read: 6649374709
Premise: holds, read at origin/main27b0bc79: Blocked-by: #30062 is closed (completed); i18n-resolver.ts L449 labelCatalogs, L473 catalogFor; pairBundle reaches the read door (meta-item-read-gate.ts L3112). The evaluator's lookup line is to be re-located by the dev (the card cites rule-validator.ts:3785 at an older commit). No maintainer ruling on this card; the ruled label order is #30193's.
Serial constraints cleared: #30538 (domain:spec#3) shares i18n-resolver.ts, other region; no open pm:dispatched claim names rule-validator.ts, meta-item-read-gate.ts or service-i18n (read of every open claim's File surface:)
席位核对 · Amendment · 2026-11-05T16:46Z · claim 6663677926 branch named
Branch: claude/project-thread-8h5swn
The card thread's harness-designated branch (a project-thread platform exception to the claude/issue-<n>-<slug> convention); the dev thread is session session_01L6bhgVKekJZDrM66tV6mbB. Everything else in claim 6663677926 stands.
{
"issue": 30245,
"status": "done",
"branch": "claude/project-thread-8h5swn",
"pr": null,
"session": "session_01L6bhgVKekJZDrM66tV6mbB (card thread; seat session_01L6bhgVugtCCwBs98FeR27P)",
"premise_still_valid": false,
"summary": "Premise falsified at origin/main 2ffdb649: an object is never a declared override, so no served object read ever carries a pair input, and its labels and its validations[].message both read the deployment catalog today; the asymmetry the card describes exists only for a direct caller that hands translateObject a pairBundle, which no production caller does. Evidence: OVERRIDE_TYPE_CLASSIFICATION gives object disposition extend_only, basis adr_0146_d10_extended (packages/spec/src/kernel/customization-package.ts:341); the write gate refuses overrides on an object (protocol.packaged-base-refusal.test.ts:763, message 'extended, never replaced', points at objectExtensions); the read side's servedOverrideBaseOf returns undefined for type object (packages/metadata-protocol/src/served-override.ts:132, isOverridableMetadataType), and both pair hand-offs at the read door go through it (packages/rest/src/meta-item-read-gate.ts:3112 list, :3180 item, via pairTranslationBundleOf :3021). git grep pairBundle across packages and ui: the only non-test production sites are those two, the protocol accessor and the spec resolver. So 'refusal text = served text' holds today for every object: the evaluator (rule-validator.ts:3779 authoredRuleMessage, lookup at :3785, i18n.t of objectValidationMessageKey, D9.2a rewrite test via scalarOverridesPackagedBase) and translateObjectValidations (i18n-resolver.ts:4668, the merged bundle) read one catalog by one rule. The merge-order concern does not bite either reader for a pair translation: a pair ships only authored translation rows (protocol getPairTranslationBundle docblock), and FileI18nAdapter lays the authored layer over every code bundle in both getTranslations (file-i18n-adapter.ts:189, the bundle the resolver reads) and t() (resolveFromLocale :367, what the evaluator asks), so an unchanged message takes the pair's authored row over the base's code-bundle translation in both. Moving the two readers onto labelCatalogs/catalogFor would wire a pair input no object can have; writing it is speculative code against an unreachable state, so no PR.",
"tests": "Reading, no code change. Closure build turbo --filter=@objectstack/metadata-protocol^... under os-verify-lock VERDICT command-exit 0 (12/12 tasks). Existing pins run at 2ffdb649: vitest metadata-protocol served-override.test.ts 'names none for a type that takes no declared override' (object row) + protocol.packaged-base-refusal.test.ts 'overrides on an object is refused' under lock VERDICT command-exit 0, Test Files 2 passed (2), Tests 2 passed, 99 skipped. No ablation: nothing tested changed.",
"gates": "n/a — no diff, no PR; dispatch-gates --commands derives nothing from an empty change set.",
"dogfood_battery": "n/a — no PR",
"mcp_calls": "2 — reads only: issue_read get x1, issue_read get_comments x1 (issue 30245). No MCP write tool.",
"api_writes": "1 — post-stamped comment on issue 30245 (this report). Two REST reads via gh api GET of comments 6648581045 and 6506001053.",
"open_questions": [
{ "question": "Should a .custom pair's translations rank first on an EXTENDED object (objectExtensions, ADR-0146 D10), as ruling 6631250894 ranks them on a declared override? Today neither labels nor validation messages of any object take pair-first order, consistently, because no object read carries a pair input.", "options": ["A close #30245 as premise-false: the ruled order covers declared overrides, an object cannot be one, both readers agree today", "B file a product question card for pair-first order on extended objects; if ruled yes, one card moves the object read door, translateObject and the evaluator together"], "recommendation": "A, because the ruling text scopes the order to an overridden item and D10 says an object is never one; B only on a maintainer ask" }
],
"security_findings": "none",
"out_of_scope_findings": [
"observation, not filed, carried from the card body and not re-measured: two AUTHORED translation rows from different packages for one key and locale merge in readAuthoredTranslationLayer by item name, so a pair's authored row can lose to another authored package's row; both readers read the same layer, so refusal = served still holds. Code reading only, no door measured; no class a/b/c reach. carrier: none",
"observation: the card body's premise sentence 'Once #30062 passes pairBundle, an overridden object's labels follow the ruled order' does not hold for type object; i18n-resolver.ts translateObject's labelCatalogs use is reachable only from a direct spec-level caller. carrier: seat (card disposition)"
]
}
Release: claim 6663677926 (domain:spec#2), at 2026-11-05T16:52Z. Reason: the dev's premise reading is false; the card goes back to pm:queue with pm:retriage for its disposition.
Report: os-dev-report 6663836042 (premise_still_valid: false, pr: null), read at origin/main2ffdb649.
Seat spot-check, origin/main2ffdb649:packages/spec/src/kernel/customization-package.tsOVERRIDE_TYPE_CLASSIFICATION row object: { disposition: 'extend_only', basis: 'adr_0146_d10_extended' }; packages/metadata-protocol/src/served-override.tsservedOverrideBaseOf answers undefined for a type isOverridableMetadataType refuses, and its table names object as "extended, never replaced (D10)". So no served object read carries a pair input, and an object's labels and its validations[].message read one catalog by one rule today: refusal text = served text holds.
Question for triage (pm:retriage): the dev recommends (A) close as not planned, premise false: the ruled pair-first order covers declared overrides and an object cannot be one. (B), a product question whether an EXTENDED object should rank its .custom pair first, only on a maintainer ask. The seat concurs with A; the disposition is triage's.
Resume-from: none (nothing to resume: no diff was written; branch claude/project-thread-8h5swn carries no commit for this card)
The maintainer ruled A in the triage session (2026-11-06, 「同意关」). The premise is false: an object is never a declared override, so its labels and its validation messages both read the deployment catalog, consistently (os-dev-report 6663836042). If an extended object should rank its .custom pair's translations first, that is a new decision card that widens ruling 6631250894.
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p3 kept ·
domain:speckept · 2026-11-05T04:04Z⛔ Not a claim.
validations[].messagedoes not take its.custompair's translation, while its labels do (i18n: an override's own label loses to the base's translation on page, dataset, app and book, and a translation in the .custom pair never wins on any type #30193's accepted deviation). p3 kept.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
Claim: PM loop round 1 · 2026-11-05T16:44Z
Session:
session_01L6bhgVugtCCwBs98FeR27P(seat); the dev is a card thread the project's coordinator opensAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, named in a claim amendment once the thread reports itWorktree:
objectstack-issue-30245Domain:
domain:specSeat:
domain:spec#2File surface:
packages/spec/src/system/i18n-resolver.ts, thetranslateObjectValidationsregion and its use oflabelCatalogs/catalogForonly (⛔resolveBundleLocale: #30538,domain:spec#3, same file, other region; the later lander mergesmain);packages/objectql/src/validation/rule-validator.ts, the validation-message lookup only; the wiring that hands the evaluator the override'spackagedBaseand its pair's translations (where #30062 hands them to the read door:packages/rest/src/meta-item-read-gate.ts/packages/metadata-protocol/src/protocol.ts, read first, edit only if the evaluator cannot reach them otherwise); their tests; one changeset. ⛔packages/services/service-i18n/**merge order (report, do not fix),ui/**,skills/**,docs/adr/**,.claude/**,AGENTS.md(stop on breach; explain in the report)Container & model:
M,mode:thread(cross-package: spec resolver plus the objectql evaluator),model: default(no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
packages/objectql(the rule evaluator answers a validation message fromi18n.t()with no pair input) andpackages/spec(translateObjectValidationsstays on the merged bundle to match it) | no platform path: labels already follow the ruled order throughlabelCatalogs, validation messages do not | every reader of an overridden object whose.custompair ships a translated validation message, now that #30062 serves pair inputThread-read: 6649374709
Premise: holds, read at
origin/main27b0bc79:Blocked-by: #30062is closed (completed);i18n-resolver.tsL449labelCatalogs, L473catalogFor;pairBundlereaches the read door (meta-item-read-gate.tsL3112). The evaluator's lookup line is to be re-located by the dev (the card citesrule-validator.ts:3785at an older commit). No maintainer ruling on this card; the ruled label order is #30193's.Serial constraints cleared: #30538 (
domain:spec#3) sharesi18n-resolver.ts, other region; no openpm:dispatchedclaim namesrule-validator.ts,meta-item-read-gate.tsorservice-i18n(read of every open claim'sFile surface:)objectstack-fleet commentedon Nov 5, 2026
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
席位核对 · Amendment · 2026-11-05T16:46Z · claim 6663677926 branch named
Branch:
claude/project-thread-8h5swnThe card thread's harness-designated branch (a project-thread platform exception to the
claude/issue-<n>-<slug>convention); the dev thread is sessionsession_01L6bhgVKekJZDrM66tV6mbB. Everything else in claim 6663677926 stands.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
os-dev-report
{ "issue": 30245, "status": "done", "branch": "claude/project-thread-8h5swn", "pr": null, "session": "session_01L6bhgVKekJZDrM66tV6mbB (card thread; seat session_01L6bhgVugtCCwBs98FeR27P)", "premise_still_valid": false, "summary": "Premise falsified at origin/main 2ffdb649: an object is never a declared override, so no served object read ever carries a pair input, and its labels and its validations[].message both read the deployment catalog today; the asymmetry the card describes exists only for a direct caller that hands translateObject a pairBundle, which no production caller does. Evidence: OVERRIDE_TYPE_CLASSIFICATION gives object disposition extend_only, basis adr_0146_d10_extended (packages/spec/src/kernel/customization-package.ts:341); the write gate refuses overrides on an object (protocol.packaged-base-refusal.test.ts:763, message 'extended, never replaced', points at objectExtensions); the read side's servedOverrideBaseOf returns undefined for type object (packages/metadata-protocol/src/served-override.ts:132, isOverridableMetadataType), and both pair hand-offs at the read door go through it (packages/rest/src/meta-item-read-gate.ts:3112 list, :3180 item, via pairTranslationBundleOf :3021). git grep pairBundle across packages and ui: the only non-test production sites are those two, the protocol accessor and the spec resolver. So 'refusal text = served text' holds today for every object: the evaluator (rule-validator.ts:3779 authoredRuleMessage, lookup at :3785, i18n.t of objectValidationMessageKey, D9.2a rewrite test via scalarOverridesPackagedBase) and translateObjectValidations (i18n-resolver.ts:4668, the merged bundle) read one catalog by one rule. The merge-order concern does not bite either reader for a pair translation: a pair ships only authored translation rows (protocol getPairTranslationBundle docblock), and FileI18nAdapter lays the authored layer over every code bundle in both getTranslations (file-i18n-adapter.ts:189, the bundle the resolver reads) and t() (resolveFromLocale :367, what the evaluator asks), so an unchanged message takes the pair's authored row over the base's code-bundle translation in both. Moving the two readers onto labelCatalogs/catalogFor would wire a pair input no object can have; writing it is speculative code against an unreachable state, so no PR.", "tests": "Reading, no code change. Closure build turbo --filter=@objectstack/metadata-protocol^... under os-verify-lock VERDICT command-exit 0 (12/12 tasks). Existing pins run at 2ffdb649: vitest metadata-protocol served-override.test.ts 'names none for a type that takes no declared override' (object row) + protocol.packaged-base-refusal.test.ts 'overrides on an object is refused' under lock VERDICT command-exit 0, Test Files 2 passed (2), Tests 2 passed, 99 skipped. No ablation: nothing tested changed.", "gates": "n/a — no diff, no PR; dispatch-gates --commands derives nothing from an empty change set.", "dogfood_battery": "n/a — no PR", "mcp_calls": "2 — reads only: issue_read get x1, issue_read get_comments x1 (issue 30245). No MCP write tool.", "api_writes": "1 — post-stamped comment on issue 30245 (this report). Two REST reads via gh api GET of comments 6648581045 and 6506001053.", "open_questions": [ { "question": "Should a .custom pair's translations rank first on an EXTENDED object (objectExtensions, ADR-0146 D10), as ruling 6631250894 ranks them on a declared override? Today neither labels nor validation messages of any object take pair-first order, consistently, because no object read carries a pair input.", "options": ["A close #30245 as premise-false: the ruled order covers declared overrides, an object cannot be one, both readers agree today", "B file a product question card for pair-first order on extended objects; if ruled yes, one card moves the object read door, translateObject and the evaluator together"], "recommendation": "A, because the ruling text scopes the order to an overridden item and D10 says an object is never one; B only on a maintainer ask" } ], "security_findings": "none", "out_of_scope_findings": [ "observation, not filed, carried from the card body and not re-measured: two AUTHORED translation rows from different packages for one key and locale merge in readAuthoredTranslationLayer by item name, so a pair's authored row can lose to another authored package's row; both readers read the same layer, so refusal = served still holds. Code reading only, no door measured; no class a/b/c reach. carrier: none", "observation: the card body's premise sentence 'Once #30062 passes pairBundle, an overridden object's labels follow the ruled order' does not hold for type object; i18n-resolver.ts translateObject's labelCatalogs use is reachable only from a direct spec-level caller. carrier: seat (card disposition)" ] }objectstack-fleet commentedon Nov 5, 2026
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
Release: claim 6663677926 (domain:spec#2), at 2026-11-05T16:52Z. Reason: the dev's premise reading is false; the card goes back to
pm:queuewithpm:retriagefor its disposition.premise_still_valid: false,pr: null), read atorigin/main2ffdb649.origin/main2ffdb649:packages/spec/src/kernel/customization-package.tsOVERRIDE_TYPE_CLASSIFICATIONrowobject: { disposition: 'extend_only', basis: 'adr_0146_d10_extended' };packages/metadata-protocol/src/served-override.tsservedOverrideBaseOfanswersundefinedfor a typeisOverridableMetadataTyperefuses, and its table namesobjectas "extended, never replaced (D10)". So no served object read carries a pair input, and an object's labels and itsvalidations[].messageread one catalog by one rule today: refusal text = served text holds.pm:retriage): the dev recommends (A) close as not planned, premise false: the ruled pair-first order covers declared overrides and an object cannot be one. (B), a product question whether an EXTENDED object should rank its.custompair first, only on a maintainer ask. The seat concurs with A; the disposition is triage's.Resume-from: none (nothing to resume: no diff was written; branch
claude/project-thread-8h5swncarries no commit for this card)objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: retriage answered · to the maintainer's decision box · 2026-11-06T12:17Z
⛔ Not a claim.
维护者速读
事由: spec#2 的开发实测,本卡前提不成立。对象不会是声明式覆盖(
OVERRIDE_TYPE_CLASSIFICATION对object是extend_only),所以对象的标签和校验消息都读部署目录,两者一致,不存在卡上说的不对称(os-dev-report 6663836042)。选项:
objectExtensions扩展的对象,是否也应让.custom配对包的翻译优先?推荐:A。 现状自洽。若要 B,等于扩展 ruling 6631250894 的范围,应另立决策卡,不在本卡上改。
按是非关卡属维护者决裁,分诊不代关。已去掉
pm:retriage和pm:queue,加上needs-user-decision。Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Closed as not planned · 2026-11-06T12:18Z
The maintainer ruled A in the triage session (2026-11-06, 「同意关」). The premise is false: an object is never a declared override, so its labels and its validation messages both read the deployment catalog, consistently (os-dev-report 6663836042). If an extended object should rank its
.custompair's translations first, that is a new decision card that widens ruling 6631250894.Generated by Claude Code