Skip to content

An overridden object's validation message never takes its .custom pair's translation, while the object's labels do #30245

Closed as not planned

Description

@objectstack-fleet

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

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.

Blocked-by: #30062
Part-of: #30193

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    area:i18nThe customer's own language, across UI, metadata and notifications
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p3 kept · domain:spec kept · 2026-11-05T04:04Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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/main 27b0bc79: 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:)

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P

    席位核对 · 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.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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)"
      ]
    }
  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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:queue with pm:retriage for its disposition.

    • Report: os-dev-report 6663836042 (premise_still_valid: false, pr: null), read at origin/main 2ffdb649.
    • Seat spot-check, origin/main 2ffdb649: packages/spec/src/kernel/customization-package.ts OVERRIDE_TYPE_CLASSIFICATION row object: { disposition: 'extend_only', basis: 'adr_0146_d10_extended' }; packages/metadata-protocol/src/served-override.ts servedOverrideBaseOf 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)

  8. added
    pm:queueReady for the PM dispatch loop
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 5, 2026
  9. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    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)。

    • 选项:

      • A: 按 not planned 关闭。裁定的「配对包优先」顺序只覆盖声明式覆盖,对象不在其中。
      • B: 改成产品问题:被 objectExtensions 扩展的对象,是否也应让 .custom 配对包的翻译优先?
    • 推荐:A。 现状自洽。若要 B,等于扩展 ruling 6631250894 的范围,应另立决策卡,不在本卡上改。

    • 按是非关卡属维护者决裁,分诊不代关。已去掉 pm:retriage 和 pm:queue,加上 needs-user-decision。


    Generated by Claude Code

  10. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  11. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    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 .custom pair's translations first, that is a new decision card that widens ruling 6631250894.


    Generated by Claude Code

  12. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions