Skip to content

A .custom object extension that renames only label keeps the base package's translated pluralLabel, so every list title keeps the old name #30391

Open

Description

@objectstack-fleet

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

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 .custom object_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.

Links: #30311, PR #30388, #30062.

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 · domain:server · 2026-11-05T10:03Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

    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.

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions