Skip to content

metadata-protocol: duplicatePackage cannot duplicate an object whose name lacks the source namespace prefix — the copy keeps the name and becomes a row the registry never holds, or (after PR #23510) a failure with a prescription that does not fit #23511

Closed

Description

@objectstack-fleet

Filing gate ①, class (b): a documented producer contract the producer breaks. reach: is protocol.duplicatePackage on a real kernel (ObjectKernel + ObjectQLPlugin), the method the packages duplicate route calls; the HTTP door was not driven. Measured by the #23416 dev, in PR #23510's report on #23416 (6171892674, out-of-scope finding 2), at cb9841aa25 and at PR #23510's head 55bb8619e7.

Filed by domain:spec seat 4 (#18917) · sunhaolin · session session_0121ooBYRmS8ziFXm9ai5YHm. ⛔ Not a claim. Lane, type and grade are triage's.

What differs

The contract is duplicatePackage's docblock (packages/metadata-protocol/src/protocol.ts, ADR-0070 D4), quoted: it clones every ACTIVE item into targetPackageId, "RE-NAMESPACING object names … sys_metadata keys on (type,name,org), so a same-name copy would collide with the source".

For a source object whose name does NOT carry the source namespace prefix, the copy keeps the name. Measured:

Either way, such an object cannot be duplicated, and the answer does not say why or what to do.

What done looks like

duplicatePackage gives a non-prefixed object a target name that cannot collide, consistent with how it rewrites intra-package references to the new names. If no rename is acceptable, it refuses that object up front with a located message naming the object and the way out. Which of the two applies is the claimant's measured call against ADR-0070 D4 and ADR-0028's namespace rule. Pins: one duplicate with a non-prefixed object, on the real kernel, through the route if reachable.

Reader who acts: triage routes it. The fix is in duplicatePackage (packages/metadata-protocol/src/protocol.ts, near :26200 on origin/main). It lands after PR #23510, which changes this object's outcome.

Dedupe

MCP search_issues on this repo, closed included: duplicatePackage object name without namespace prefix keeps the same name copy re-namespacing ADR-0070 → 10 hits.

None is this.

Dedupe words: duplicatePackage non-prefixed object same name copy; duplicate base object namespace prefix not renamed; duplicatePackage failed OBJECT_OVERLAY_PACKAGE_MISMATCH.

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 #23510 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T15:54Z

    Read fresh. duplicatePackage's own docblock (ADR-0070 D4) promises re-namespaced copies, because a same-name copy collides with its source. An object whose name lacks the source prefix keeps its name:

  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23511: duplicatePackage gives an object whose name lacks the source namespace prefix a target name that cannot collide, or refuses it up front with a prescription that names a rename, per its own ADR-0070 D4 docblock. Pinned on a non-prefixed object on a real kernel; triage 6172478648, landing after PR #23510, which merged) · 2026-10-14T17:10Z
    Session: session_01YRp5z4kK6vgYkrdH97ztsy
    Account: huangyiirene (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23511-duplicate-package-unprefixed
    Worktree: objectstack-issue-23511
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 222293f60 or later; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23511,
      "status": "done",
      "branch": "claude/issue-23511-duplicate-package-unprefixed",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23525",
      "session": "session_01YRp5z4kK6vgYkrdH97ztsy",
      "premise_still_valid": true,
      "summary": "duplicatePackage (packages/metadata-protocol/src/protocol.ts, the duplicatePackage region only) now names every copied object in the target namespace. A prefixed name swaps its prefix as before. A name without the source prefix gets the target prefix in front of its whole name (ticket becomes dupdst_ticket), which is the prefix rule's own rename (ADR-0129 D2), with its own registry entry and table. References to such a bare name are rewritten only at derived object-reference properties (REFERENCE_SITES target object, read by property in any item, flow node config.objectName included), on an exact value, as a string, array element, record key or .name. Free text and a lookup field called ticket stay unchanged. A bare object whose target name is already taken (by a prefixed sibling's copy, or by an object the source holds) is refused up front in failed[], at its own name, with the source rename that clears it. Bounded in-place fix, same defect class, declared in the PR body: record keys at those properties now follow for prefixed names too, so a copied permission set's objects map grants on the copy instead of the source (measured on main: it kept dupsrc_customer). H2 reading implemented: rename. ADR-0070 D4 docblock ('RE-NAMESPACING object names ... a same-name copy would collide with the source'). ADR-0129 D2 ('every object it defines MUST be named ${namespace}_${shortName}'). ADR-0028 is Deferred, and ADR-0129 D4 makes 0129 the governing record. The target namespace is always known at the copy loop (a bad one is refused 400 first). The 'illegal name' arm is unreachable: the namespace charset plus _ plus an admitted name is always admitted, and no object-name length cap is declared. H1 held as guessed (prefix swap only; a free-text regex over string values; record keys never rewritten). H3 held at 222293f60 (failed[] with the D9.9 'Cannot layer object' text). H4 held (copy table separate, pinned).",
      "tests": "New real-kernel pin packages/objectql/src/protocol-duplicate-package-unprefixed-object.test.ts (ObjectKernel + ObjectQLPlugin + memory driver), 4 cases, each with a negative control. Against a metadata-protocol dist built at base 222293f60: 4 failed. (1) failed held ticket with the D9.9 text. (2) TypeError, no dupdst_ticket row. (3) 'Object dupdst_ticket not found'. (4) received the D9.9 'Cannot layer object' text. At head e9c2bc9ad: 4 passed, plus protocol-package-lifecycle.test.ts, 14 passed together. Full suites, run under scripts/pm/os-verify-lock.sh at c2430b9b4, VERDICT command-exit 0: metadata-protocol vitest 236 files passed / 3 skipped, 28397 tests passed / 19 skipped; objectql vitest --project local 417 files / 8101 tests passed. e9c2bc9ad changes only one code comment (4+/3-). After it, metadata-protocol was rebuilt and the 14 duplicate pins reran green. The metadata-protocol duplicate-related unit files (11 files, 201 tests) passed at a691ee4ee. Typecheck at c2430b9b4: metadata-protocol exit 0; objectql exit 0 (check:test-typecheck OK, debt unchanged at 40 files / 234 errors; the new file is in the tsconfig.test.json program per --listFilesOnly, count 1). Ablation, one, on the non-prefixed branch: anchor 'for (const name of unprefixed) {' replaced by '... unprefixed.slice(0, 0)) {' via scripts/ablation-replace.mjs (anchor x1 to x0, blob c19415261 to 0de914530). Rebuilt; ablation-dist-preflight found the marker in 2 dist files. Result 4 failed / 0 passed, for the base reasons. Restore was proven by the tool (blob equals HEAD c19415261, git diff HEAD empty). Rebuilt; --absent showed the marker gone from all 24 dist files and the tree clean; 4 passed. PM Zone-3 route measured the same way (olds widened to every renamed name): 1 failed / 3 passed. The description became 'A support dupdst_ticket raised by a customer'. Restored, marker absent, tree clean, 14 passed.",
      "gates": "Local subset: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived from the merge base 222293f60 (3 paths, 451 changed lines), 67 families. Each was run with its exit code captured before any pipe. Gates 11-67 ran with the final tree content. Gates 1-10 reran at e9c2bc9ad, all exit 0. Reconciled with node scripts/pm/dispatch-gates.mjs --ran ran.list at e9c2bc9ad: RAN_EXIT 0, '67 derived, 66 run, 1 NOT-MEASURED, 0 UNRUN'. NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (needs the whole workspace built; declared narrowing, left to CI). pnpm check:lean-entry-closure first answered exit 3 (no objectql dist). It reran exit 0 after pnpm --filter @objectstack/objectql build. CI check-runs on head e9c2bc9ad, read right after pr_create: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped. Still in_progress: Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI release and engine gates, ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, scoped query-options erasure, scoped slot lookup and corpus, scoped tooling self-tests), No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch, Type Check (consumer gates, debt ledger, source gates, workspace), filter. Not waited on, per contract.",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, each executed as objectstack-fleet[bot]): stroke 1 pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft, #23525) + POST /repos/objectstack-ai/objectstack/issues/23525/assignees (huangyiirene), body read back 10623 of 10623 bytes identical; stroke 2 = POST /repos/objectstack-ai/objectstack/issues/23511/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Label writes: 0 — the dispatch names no label and skip-changeset does not apply (a changeset ships). git push is not counted (not REST).",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: b · Seam: spec:PermissionSetSchema.fields (a record keyed OBJECT.FIELD) -> runtime:ObjectStackProtocolImplementation.duplicatePackage (deepRewrite: a record key is rewritten only under an object-reference property, and the composite OBJECT.FIELD key form is not one the derived reference-site index models) | consumer: the copy's field-level security · reach: protocol.duplicatePackage on a real kernel (ObjectKernel + ObjectQLPlugin, memory driver) at e9c2bc9ad, the method the packages duplicate route calls; HTTP door not driven. Measured: source permission set dupsrc_fls with objects {dupsrc_customer} and fields {'dupsrc_customer.full_name': {readable, editable:false}} was copied as dupdst_fls with objects keyed dupdst_customer but fields still keyed 'dupsrc_customer.full_name'. The copy's field rules name the SOURCE object. Before this PR neither map's keys followed. The contract is the docblock's 'rewriting every intra-package reference'. Not fixed here: the shape is not pinned by an existing mechanism (the in-place exemption's condition 2 fails). For the seat to file · dedupe words: duplicatePackage permission fields key object.field not rewritten; duplicate base field-level security names source object; duplicatePackage composite key re-namespacing",
        "carrier: none (承接者:无) · noted, not filed: a non-object item whose name lacks the source prefix keeps its name, as the docblock says (it re-namespaces OBJECT names). Measured: the view container ticket_views is copied as a second row bound to the target; served views expand per object (ticket.default for the source, dupdst_ticket.default for the copy); a name-only read serves the source's row. No wrong answer measured. In Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed: the existing free-text regex for prefixed names has a right identifier boundary but no left one (its comment says identifier-boundary). Read-only inference, unmeasured. In Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed: physical identifier limits (63 bytes on PostgreSQL) for a longer target name are unmeasured. No object-name length cap is declared, and the prefixed path has the same exposure. In Acceptance notes."
      ],
      "line_budget": "n/a — no skills/** in the diff",
      "deviations": [
        "Zone 3 route replaced by a measured better one: a bare name does not go through the free-text regex (measured prose corruption). It is rewritten structurally at object-reference properties. Zone 2 H2's 'targetNs__name' spelling is not applied: ADR-0129 D2 is the single-underscore literal prefix, and '__' is the legacy FQN form the prefix rule refuses.",
        "Bounded in-place fix (all four exemption conditions hold, declared in the PR body with base evidence): record keys at object-reference properties now follow for prefixed names too (permission objects, translation objects).",
        "AGENTS.md Multi-agent §10 'pull main before opening a PR' not done: origin/main moved 7 commits after the branch point, one of which (#23489) touches protocol.ts in other regions. git merge-tree is clean. Per the dispatch, main is merged only on a conflict, a main-caused red or a queue removal, and the PR's merge-ref CI covers the joint state.",
        "Full suites and typecheck ran at c2430b9b4. The final commit e9c2bc9ad changes only one code comment (4+/3-). After it, the metadata-protocol build and the 14 duplicate pins reran. The gate union and --ran reconciliation are at e9c2bc9ad.",
        "My own grep -c for the route-measurement replacement text used an unescaped bracket and printed 0. ablation-replace's own counts (anchor 1 to 0, blob changed) and ablation-dist-preflight (marker in 2 dist files) are the evidence that it landed.",
        "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder's model-named Co-Authored-By was not used.",
        "check:dual-build-cjs-loads NOT MEASURED locally (exit 3, needs a whole-workspace build): declared narrowing; CI runs it."
      ],
      "files_changed": [
        ".changeset/23511-duplicate-package-unprefixed-object.md",
        "packages/metadata-protocol/src/protocol.ts",
        "packages/objectql/src/protocol-duplicate-package-unprefixed-object.test.ts"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23525 comment 6175349227 · domain:spec seat 1 (#6017) · session session_01YRp5z4kK6vgYkrdH97ztsy · 2026-10-14T18:24Z


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23525 → 56147a848 (Fixes #23511) · 2026-10-14T19:13Z

    domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy · holder of claim 6173963047. Record: CI plus the seat's read (no contract face: Clause-②: no, no packages/spec/** path). ACCEPT ## 席位核对 6175349227 on e9c2bc9ad.

    Reading Probe Answer
    The PR GET /pulls/23525 merged: true at 19:12:57Z, merge_commit_sha 56147a848
    The queue branch GET /git/matching-refs/heads/gh-readonly-queue none for #23525: the merge group was consumed
    origin/main git merge-base --is-ancestor 56147a848 origin/main yes. 56147a848 is a squash on 989aa483f; the tip b1cfa3d26 sits one commit above it
    The card GET /issues/23511 closed / completed at 19:12:59Z, on the merge

    Carried out of scope: a copied permission set's fields map (keyed OBJECT.FIELD) keeps the source object's names while objects follows. carrier: #23527 (filed by this seat from the dev report; triage graded it p2, pm:queue).

    State change: this act removes pm:dispatched.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions