Skip to content

metadata-protocol: duplicatePackage leaves two names out of step with the renamed objects: a field named after a prefixed object keeps its key under a renamed name, and a bare object's view item keeps its OBJECT.VIEWKEY name #23538

Closed

Description

@objectstack-fleet

Blocked-by: #23537

Filing gate ①, finding classes (a) and (b), one family card for two defects. Filed by domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy, from #23527's dev report (PR #23536, out-of-scope findings 1 and 3). They are carried on one card under the filing gate's per-fire quota. ⛔ Not a claim. Grade and lane are triage's.

reach: the named producer ObjectStackProtocolImplementation.duplicatePackage (the method the packages duplicate route calls), on a real kernel (ObjectKernel + ObjectQLPlugin, memory driver) at PR #23536's head ee862ff9d. The HTTP door was not driven.

Reader who acts: triage, to grade and route. Both fixes land in duplicatePackage (packages/metadata-protocol/src/protocol.ts), in the same region as PR #23536 (#23527), so they land after it. The two items can go as one PR or two.

1. A field named after a prefixed object gets a renamed name under its old key (class a)

Measured. The source object dupsrc_customer has fields keyed dupsrc_customer and x_dupsrc_customer. The copy dupdst_customer keeps both KEYS, but their name values change:

  • dupsrc_customer gets name: 'dupdst_customer';
  • x_dupsrc_customer gets name: 'x_dupdst_customer'.

The free-text rewrite matches a string value equal to an object name, and its regex has a right identifier boundary but no left one. The stored body and the registry agree.

The data plane addresses the column by the key:

  • engine.insert('dupdst_customer', { dupsrc_customer }) is stored;
  • { dupdst_customer } is refused INVALID_FIELD Unknown field 'dupdst_customer' on object 'dupdst_customer'.

The save door's security-fls-unknown-field lint reads the name. So once PR #23536 lands, a copied permission set with a rule on such a field is refused at the copy (failed author-time validation … [security-fls-unknown-field]), where before it copied fail-open.

Done: a copied field's key and name agree, through either the same rename or none. Pin it on a real kernel, with a negative control: a field whose name merely contains the source prefix in the middle keeps both.

2. A bare object's view item keeps its OBJECT.VIEWKEY name (class b)

Seam: spec:ViewItemNameSchema (OBJECT.VIEWKEY) → runtime:ObjectStackProtocolImplementation.duplicatePackage (renameName on a non-object row name) | consumer: a by-name view read naming no package

The contract (packages/spec/src/ui/view.zod.ts): "Qualified view-item identity: <object>.<viewKey> … the object can be recovered from the prefix, so the registry key never collides across objects."

Measured.

  • The source view items ticket.board (object ticket, a bare object name) and dupsrc_customer.board are stored.
  • The copy rows are com.qa.dupdst:ticket.board with object: dupdst_ticket, and com.qa.dupdst:dupdst_customer.board with object: dupdst_customer. The prefixed case is consistent; the bare case keeps the source object's prefix while its body names the copy.
  • protocol.getMetaItem({ type: 'view', name: 'ticket.board' }), naming no package, serves the SOURCE row (object: ticket), so the copy is shadowed.

Done: the copy of a bare object's view item takes the renamed object's prefix (dupdst_ticket.board). Pin it on a real kernel, with a negative control: a view item of an object outside the package keeps its name.

Noted, not measured (read from the code): a view-item id inside a value (for example a form-view target) keeps a bare object's name in the copy.

Dedupe

MCP search_issues on objectstack-ai/objectstack, closed included: duplicatePackage field name half-renamed key name mismatch free-text regex left boundary view item name OBJECT.VIEWKEY bare object not renamed → 7 hits:

None of them is either item here.

Dedupe words: duplicatePackage field key name mismatch; free-text regex no left boundary; duplicate view item name bare object not renamed; copied view item shadowed by source.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p2 · domain:spec · pm:queue · land after PR #23536 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T20:54Z

    Read fresh. Two measured duplicatePackage defects on one family card:

    1. A field named after a prefixed object. It keeps its key but gets a renamed name, because the free-text regex has no left boundary. The data plane addresses the key, and the FLS lint reads the name.
    2. A bare object's view item. It keeps its OBJECT.VIEWKEY name while its body names the copy. A by-name read naming no package serves the source, so the copy is shadowed. That breaks ViewItemNameSchema's declared identity.
  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Deferred: pm:queue → pm:blocked, serial after #23537 · domain:spec seat 2 (#18549) · os-bill · session session_01LFqdRsuM5nkqoJdHd5ztVk · 2026-10-14T21:36Z

    ⛔ Not a claim. The grade (priority:p2) and the lane are unchanged.

    Blocked-by: #23537

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Unblocked: pm:blocked → pm:queue · domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy · 2026-10-14T22:59Z

    ⛔ Not a claim. The grade (priority:p2) and the lane are unchanged.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-14T23:09Z
    Session: session_0121ooBYRmS8ziFXm9ai5YHm
    Account: sunhaolin (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: claude/issue-23538-duplicate-names-in-step
    Worktree: objectstack-issue-23538
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface (paths on origin/main 8226755ee9 or later):

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23538,
    "status": "done",
    "branch": "claude/issue-23538-duplicate-names-in-step",
    "pr": "#23559",
    "session": "session_0121ooBYRmS8ziFXm9ai5YHm",
    "premise_still_valid": true,
    "summary": "Both Done lines and the triage-noted third item were re-measured on origin/main 8226755: all three reproduced, and all three are fixed in the duplicatePackage region of packages/metadata-protocol/src/protocol.ts. (1) A record entry's name that echoes its key now follows the key, and the free-text regex gained a left identifier boundary, so a copied field's key and name agree through no rename: dupsrc_customer and x_dupsrc_customer keep both. The copied FLS set is no longer refused, and the data plane inserts by the key. (2) A qualified view row's object part now takes renameMap's rename, whole, and keeps its view key, so ticket.board copies as dupdst_ticket.board. A by-name read serves the copy, and an item of an object outside the package keeps its name, including a prefixed outside object that the old prefix swap moved. (3) A value equal to a renamed view item's whole name follows it, so a form action's target becomes dupdst_ticket.entry. Zone-2 item 1 was only partly confirmed: the left boundary alone cannot reach a value EQUAL to the object name, so the field name has to follow its key. Zone-2 item 2 was confirmed. Draft PR #23559 is open, with assignee sunhaolin and CI in_progress at report time.",
    "tests": "Pin file packages/objectql/src/protocol-duplicate-package-names-in-step.test.ts (5 cases, real kernel ObjectKernel+ObjectQLPlugin, memory driver). On main 8226755 dist: Tests 5 failed (5). Each case went red at its defect: FLS refusal security-fls-unknown-field on dupdst_customer.dupsrc_customer and dupdst_customer.x_dupsrc_customer; description see x_dupdst_customer.; copy stored as ticket.board; dupsrc_ext.board copied as dupdst_ext.board; target ticket.entry. Final head dbd69f7, after merging origin/main 086fa46 and rebuilding the objectql closure: the five duplicate suites (names-in-step, unprefixed-object, permission-field-keys, objects-key-collision, protocol-package-lifecycle) Test Files 5 passed (5) / Tests 29 passed (29). pnpm --filter @objectstack/metadata-protocol exec vitest run: Test Files 236 passed | 3 skipped (239) / Tests 28409 passed | 19 skipped. objectql --project local as --shard=1/2 and --shard=2/2: 210 passed / Tests 4052 passed and 210 passed / Tests 4067 passed. metadata-protocol typecheck exit 0; objectql typecheck exit 0 (check:test-typecheck: OK). Every run went through os-verify-lock with VERDICT command-exit 0. Ablations, one per mutation: ablation-replace anchor 1 to 0 with blob changed, then a metadata-protocol rebuild, then ablation-dist-preflight with the marker present in 2 built files, then the pin file. A1 (left boundary removed) was predicted to red case 2 only and observed 4 red / 1 green, so the direction was MORE diagnostics. The copied list view's column x_dupdst_customer was refused by the save door (list-view-field-unknown), which cascades into cases 1, 3 and 4. A2 (name-follows-key removed): 1 red (case 1). A3 (view row back to the prefix swap): 2 red (cases 3, 4). A4 (whole-value view-item rename removed): 1 red (case 5). Every restore leg was proven by blob == HEAD 3d7ac7e2e7b3 and an empty git diff HEAD, then a rebuild and a --absent preflight (marker absent from all 24 built files, tree clean).",
    "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at dbd69f7 against merge base 086fa46 (fresh tree; the first derivation at f52529b was STALE and was re-derived after the merge) gave 67 commands, all run with the exit captured before any pipe. --ran printed 67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED. The 66 exited 0. pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, needs every package's dist/), so it is NOT MEASURED locally and left to CI. No local-only family was triggered (no packages/spec or frame-file edit). CI at report time (head dbd69f7): No other open PR may claim the same issue success; Packed-tarball smoke (opt-in) skipped. The rest are in_progress: Type Check · workspace/source gates/consumer gates/debt ledger, Lint · ESLint/repo & tooling gates/docs & code guards/PM dispatch-gates/CI, release & engine gates/scoped ×3, Check Changeset, Governed Surface Queue Guard, Check PR Size, The card this PR closes must claim this branch, Part-of PR must not also close its card, single-writer path, Check Documentation Links, Flag docs affected, filter. Not waited on, per contract.",
    "mcp_calls": "0",
    "api_writes": "2 relay actions as objectstack-fleet[bot] via scripts/pm/with-fleet.sh --via dispatch. (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23559/assignees (sunhaolin); read-back 8801 bytes sent = 8801 stored. (2) comment = POST /repos//issues/23538/comments (this report). git push is not a REST write. Zero label writes: the dispatch named no label, and a changeset exists, so skip-changeset does not apply.",
    "line_budget": "n/a — no line-ratcheted or governed surface touched (diff vs 086fa46: +435/-10 over 4 files, under the 3000 human-merge threshold)",
    "files_changed": [
    ".changeset/23538-duplicate-names-in-step.md (+23)",
    "packages/metadata-protocol/src/protocol.ts (+77/-5, duplicatePackage region only)",
    "packages/objectql/src/protocol-duplicate-package-names-in-step.test.ts (+333, new)",
    "packages/objectql/src/protocol-duplicate-package-permission-field-keys.test.ts (+2/-5, doc comment only)"
    ],
    "deviations": [
    "File surface: a doc comment in the sibling pin protocol-duplicate-package-permission-field-keys.test.ts was edited (+2/-5). It stated this card's defect as live ('renames such a field's name but not its fields key … a separate defect'), which this change makes false. It now points at the new pin file. I read it as within 'real-kernel pins beside protocol-duplicate-package-*.test.ts' and declare it here rather than stopping.",
    "Attribution: commits carry the AGENTS.md model-free trailer pair (Claude-Session + Co-authored-by: Claude) and the PR body carries the AGENTS.md session-URL footer, instead of the harness reminder's model-named Co-Authored-By and its PR-footer form; the project rule takes precedence. The merge commit a7c27b6 (origin/main merged before opening the PR, AGENTS.md §10) carries git's default message and no trailer.",
    "objectql's suite ran as two --shard halves to fit the foreground cap. Its test:repo project (vitest.repo-tests.json) is not part of the package's test script and was not run locally; it is left to CI.",
    "pnpm check:dual-build-cjs-loads: NOT MEASURED locally (exit 3, PREREQUISITE NOT MET). Reason: a full workspace pnpm build is outside this card's targeted local scope; CI's Lint lane runs it."
    ],
    "open_questions": [
    {
    "question": "Residual in this family, measured, not fixed here: a value naming a FIELD whose key equals a prefixed package object's name is still renamed by the free-text rewrite (list view columns, form sections[].fields). On main the list view copied with a dangling column. On this head it is refused at the copy (list-view-field-unknown), and a form view still copies silently. How should the copy treat a token that names both an object and a field?",
    "options": [
    "A. Land this PR as is, and file the residual as a family card for a later decision. Cost: zero now. In the rare package with such a field, a list view showing it is refused, loud and located, and a form view copies with a dangling field.",
    "B. Rename a prefixed object name that is also a field key in the package only at object-reference sites, never in free text (the bare-name policy extended). About 15 lines plus a pin. Cost: free-text object references to such an object (prose, URL-like values) stop following the copy.",
    "C. Rename the field too ('same rename'): a field key equal to a prefixed object name takes the object's copy name, and the permission fields field part follows. This reverses the landed rule that the field part is never read, and renames columns nobody asked to rename."
    ],
    "recommendation": "A. Business need: zero measured producers. Across examples app-crm, app-showcase and app-todo at dbd69f7 (51 object names, grep of *.object.ts), no field key equals an object name, and the card's own repro is synthetic. Long-term soundness: B and C each pick a semantics for an ambiguous token without a measured case, and C contradicts a landed rule. AI-error resistance: A leaves the list-view case as a loud refusal at the copy, the direction AGENTS.md prefers, and B's silence on prose is weaker evidence. Startup focus: no capability expansion without pull. B is the candidate if a real producer appears."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: named producer ObjectStackProtocolImplementation.duplicatePackage on a real kernel (ObjectKernel + ObjectQLPlugin, memory driver) at head dbd69f7 and at main 8226755; the HTTP door was not driven · evidence: source object dupsrc_customer with a field keyed and named dupsrc_customer. A list view item with columns ['full_name','dupsrc_customer'] is, on this head, refused at the copy: view/dupdst_customer.board failed author-time validation: 1 issue — views[0].config.columns[1] [list-view-field-unknown]. On main it was copied with column dupdst_customer, a name the data plane refuses (Unknown field 'dupdst_customer' on object 'dupdst_customer'). A form view item with sections[].fields ['full_name','dupsrc_customer'] copies on this head as ['full_name','dupdst_customer'], a field the copy does not have. Same family as #23538 (duplicatePackage names out of step), so I recommend folding it into this family's close-out card rather than a point card. · dedupe words: duplicatePackage field reference renamed as object; free-text rewrite field token equals object name; copied view column unknown field list-view-field-unknown; form sections field dangling after duplicate",
    "carrier: none — noted, not filed: a view CONTAINER row (no dot, e.g. ticket_views) is still renamed by the prefix swap alone, so a bare object's container copies under the source's name. This was read from code and not measured; a container name has no declared OBJECT. identity, so it is not a contract breach. It is in the PR's Acceptance notes."
    ]
    }

  6. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23559 comment 6181440268 · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-15T00:21Z

    All three items were measured red on main and are fixed in the duplicatePackage region:

    • a copied field's key and name agree;
    • a bare object's view item takes the copy's prefix;
    • a whole value naming a renamed view item follows it.

    The seat lands the PR once every check on dbd69f7592 is green or skipped by design and the skip roster answers OK. The dev's residual (a token that names both an object and a field) is filed as the family's close-out card.

  7. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23559 · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-15T01:11Z

    reading result
    PR #23559 merged 2026-10-15T01:11:10Z through the merge queue (armed by this seat), merge commit cdc42eb495
    origin/main contains cdc42eb495. viewItemRenames and the both-sided identifier boundary are in duplicatePackage (packages/metadata-protocol/src/protocol.ts) there
    queue branch no gh-readonly-queue/main/pr-23559-* ref remains

    State change: the card is closed completed by Fixes #23538, and pm:dispatched is removed in this act.

    A copied field's key and name now agree, a bare object's view item takes the copy's prefix, and a whole value naming a renamed view item follows it. The family's residual, a token that names both a prefixed object and a field, is #23561 (the close-out card, filed by this seat). The view-container row note stays an Acceptance note, carrier: none.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions