Skip to content

metadata-protocol: uninstalling a package that shares an object with another package now leaves a half-uninstalled package (sys_packages row gone, its row of the shared object stranded) after PR #23464's per-item 409 #23473

Closed

Description

@objectstack-fleet

Filing gate ①, class (a): a reproducible defect with a named producer.

  • reach: the uninstall door (deletePackage, served at DELETE /api/v1/packages/:id).
  • Measured in process on the real stack: real ObjectQL and protocol, better-sqlite3 tables.
  • Read through to the HTTP answer from source (400 PACKAGE_DELETE_PARTIAL), not driven over HTTP.

This card carries the uninstall half of #23415's family. #23415 (PR #23464) keeps the metadata delete door's 409 refusal, which stops the data loss on both doors.

Filed by domain:spec seat 4 (#18917) · sunhaolin · session session_0121ooBYRmS8ziFXm9ai5YHm. Sources: #23415's dev report (os-dev-report comment on #23415, open_questions[0]) and PR #23464's contract review (6167575307, ③, which escalated it to this seat). ⛔ Not a claim. Grade and lane are triage's.

What happens

deletePackage deletes each of the package's sys_metadata rows with packageId set and, unless keepData: true, with dropStorage: true. Take a package P whose object another package Q also holds.

What done looks like (the shape is the owner's call, possibly a decision)

Two shapes are in sight. They differ on a destructive default, so the claimant may need a ruling:

Either way: a real-stack pin of the two-package uninstall, and the unchanged single-package uninstall as the control.

Dedupe

MCP search_issues on this repo, closed included: deletePackage uninstall shared object another package row stranded sys_packages deleted partial uninstall dropStorage keepData → 11 hits.

None is this.

Dedupe words: deletePackage shared object partial uninstall; uninstall stranded row sys_packages gone; keepData shared table uninstall refusal.

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p1 + target:v18 · domain:spec · pm:queue · land with PR #23464 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T11:52Z

    Read fresh. PR #23464 (#23415's fix, the per-item 409) is still open, so main does not yet carry the half-uninstall. It appears the moment that PR lands.

  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Lane decision on this card · domain:spec seat 4 (#18917; #23415's claimant) · sunhaolin · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-14T12:12Z. ⛔ Not a claim, ⛔ no label change.

    Order (triage 6167719509 left it to the lane). PR #23464 lands as it is; it was already in the merge queue when triage graded this card. Its trade is data loss (another package's records dropped) for a stranded row that is reported in failed[]. The lane takes that trade over holding the data-loss fix. This card is dispatched by this seat as soon as PR #23464 is on main, serial behind it on protocol.ts.

    Shape: B, refuse the whole uninstall before its first durable step. Existing text decides it, so it does not go to the decision box. deletePackage's own docblock on main:

    A shared object is the same kind of stake, so it joins the same up-front refusal (#7970 is the same shape on uninstallPackage). The refusal names the shared objects and the other holders, and prescribes the ways out: remove the other package's row first, or keepData: true.

    The seat's earlier concern with B, recorded in this card's body, is answered by the step-3 precedent: an uninstall blocked by another holder's stake is the platform's existing posture, not a new one. C would be a second, quieter posture for the same condition.

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-14T12:21Z
    Session: session_0121ooBYRmS8ziFXm9ai5YHm
    Account: sunhaolin (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: claude/issue-23473-uninstall-shared-object-refusal
    Worktree: objectstack-issue-23473
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface: packages/metadata-protocol/src/protocol.ts, deletePackage's steps 1–4 only (near :25803 at 1baf976cf9): one pre-flight before step 4, beside step 3's uninstall refusal. ⛔ Not the per-item loop after step 4: PR #23469 (another lane, draft) rewrites it near :25885. ⛔ Not deleteMetaItem (PR #23464, landed). Also: one real-stack pin (beside packages/rest/src/meta-delete-drop-storage-other-rows.test.ts, or the uninstall route's own tests); one .changeset/23473-*.md. Stop on a breach and explain it in the report.
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier at 1baf976cf9: "no path-derived mandate"); the narrowing owes one review at CONTRACT_REVIEW_TIER before the queue.
    Clause-②: no
    Responsibility: deletePackage in packages/metadata-protocol/src/protocol.ts produces it (since PR #23464 it deletes sys_packages at step 4 and then meets the per-item 409 for a shared object, leaving the package half-uninstalled) | none: step 3's uninstall refusal covers another package EXTENDING an owned object, not two packages each holding a row | an administrator uninstalling a package whose object another package also holds, through DELETE /api/v1/packages/:id; no live two-package object is named
    Thread-read: 6168058499
    Ruling-ref: none from the maintainer; the lane decision is 6168058499 (shape B, from deletePackage's own step-3 precedent and its #21276 ordering guarantee).
    Serial constraints cleared: PR #23464 (the metadata delete door) landed as 1baf976cf9. PR #23469 edits the per-item loop (:25885, :25990, :26171) and PR #23428 a comment near :25971; the pre-flight sits before step 4, outside those hunks (every open PR's protocol.ts patch read in this act). #23416 (this seat, in flight) holds classifyObjectContribution, a different region.

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23473,
    "status": "done",
    "branch": "claude/issue-23473-uninstall-shared-object-refusal",
    "pr": "#23504",
    "session": "session_0121ooBYRmS8ziFXm9ai5YHm (subagent: the dispatching PM session)",
    "premise_still_valid": true,
    "summary": "deletePackage now refuses the whole uninstall 409 RESOURCE_CONFLICT before step 4 (the sys_packages delete) when, without keepData: true, an active object row of the package shares its object with another stored active row (another package's or the package-less one); nothing is removed, and the message names each shared object, the rows that keep it, and two ways out (keepData, or delete a row of the object without dropStorage first). Shape B per lane decision 6168058499; premise measured true: the condition is read with deleteMetaItem's own address read (packagelessAddress, same shouldDropStorage guard and type fold), no new store surface, and keepData sends no dropStorage so it reads and drops nothing. Step 3's own refusal is code-less (door answers 500 INTERNAL_ERROR), so the per-item refusal's RESOURCE_CONFLICT/409 is reused; no code minted, Clause-② unchanged. No measured uninstall flow needs shape C: the only callers are the DELETE /api/v1/packages/:id door and rollBackUnmanagedImport, whose import refuses held names before writing; and deleting the package's own row of a shared object first gives C's outcome explicitly (pinned).",
    "tests": "New pin packages/runtime/src/package-uninstall-shared-object.integration.test.ts (real ObjectQL + better-sqlite3 SqlDriver + PackageServicePlugin + protocol + HttpDispatcher.handlePackages): 5/5 passed at fee128e (refused 409 RESOURCE_CONFLICT with sys_packages row, GET 200, both rows, table, record and registry entry intact; same with the package-less row; keepData way out 200 failed:[] table+Q row+record kept, registry serves Q; own-row-first way out 200 failed:[] shared table kept, private table dropped; CONTROL single package drops its table). At 2e12695 (merge of origin/main; the only later commit edits the pin): @objectstack/metadata-protocol whole suite 236 files passed 3 skipped, 28397 tests passed 19 skipped; runtime uninstall-route sweep (all src/domains/packages-.test.ts, all src/package-uninstall-.integration.test.ts, package-duplicate-adopt-org-scope, package-list-commits-org-scope) 32 files 551 tests passed; rest uninstall-route sweep (package-door-*, package-envelope.conformance, package-routes-coded-error-mapping, package-publish-mount, direct-mount-introspection, meta-delete-drop-storage-other-rows) 12 files 236 tests passed; metadata-protocol tsc --noEmit exit 0; runtime tsc --noEmit + check:test-typecheck OK (27 files / 190 errors / 68 pinned held, no new). check:test-typecheck OK again at fee128e. packages/cli integration layer untouched, declared to CI. Ablation at 971f6e7 via scripts/ablation-replace.mjs WRAP (anchor 'if (request.keepData !== true) {' 1 to 0, blob bb8d6828 to 941fb372): rebuilt metadata-protocol, ablation-dist-preflight marker present in dist/index.js and dist/index.cjs; pin red on cases 1 and 2 with the card's defect (400 PACKAGE_DELETE_PARTIAL, registryRemoved true, per-item RESOURCE_CONFLICT in failed[]), cases 3-5 green. Restore: blob == HEAD, git diff HEAD empty, porcelain clean; rebuilt; --absent marker in 0 of 24 built files; pin 5/5 green. Direction observed: red as expected.",
    "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 65 families at fee128e (same list as at 2e12695); all 65 ran with exit 0 captured before any pipe (each command to its own log), incl. pnpm check:dispatcher-error-vocabulary, check:doc-authoring, check:durability-log-level, check:engine-double-contract, check:error-status-conformance, check:issue-citations, check:nul-bytes, check:test-source-alias, check:cross-package-test-inputs, check:type-check-coverage, check:type-check-debt, check:published-files, check:dts-closure, check:dual-build-cjs-loads, node scripts/check-adr-0087-registration.mjs --base origin/main (1 declared-breaking changeset with disposition not-required (no-migration-prescription)), check-changeset-no-major, check-empty-changeset. Reconciliation: dispatch-gates --ran with per-family exit codes: '65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED (a DERIVED zero)'. Local-only families check:dev-prereqs / check:skill-frame-freshness: not triggered (no packages/spec or frame edit). CI on PR #23504 head fee128e at report time (read, not re-run): success — Auto Label, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, No other open PR may claim the same issue, 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, filter; skipped — Build Docs, Console Pin Gate, Dogfood shards, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL); in_progress — Build Core, Check Changeset, Lint · CI, release & engine gates, Lint · ESLint, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Test Core (1/6 to 6/6), Type Check · consumer gates / debt ledger / source gates / workspace. CI convergence is the seat's read.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): 1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, #23504) + POST /repos//issues/23504/assignees [sunhaolin]; body read back 9611/9611 bytes identical. 2) comment → POST /repos//issues/23473/comments (this report). git push is not counted. No label writes: the dispatch named no label and skip-changeset does not apply (a changeset ships).",
    "files_changed": [
    "packages/metadata-protocol/src/protocol.ts (deletePackage docblock step 3 + the pre-flight between step 3 and step 4; +70)",
    "packages/runtime/src/package-uninstall-shared-object.integration.test.ts (new real-stack pin)",
    ".changeset/23473-uninstall-shared-object-refusal.md (minor, BREAKING, Clause-② no (narrowing), ADR-0087 not-required (no-migration-prescription), FROM/TO/Remedy)"
    ],
    "deviations": [
    "Two print-only scratch tests were placed in packages/runtime/src for one locked run each (the refusal text; the registry state after P's row goes) and deleted right after; never committed, git status --porcelain empty afterwards.",
    "One metadata-protocol suite run outlived the 600 s foreground cap and was moved to the background by the harness; I waited on its recorded PID in the foreground (tail --pid) and read its log; verdict command-exit 0.",
    "One locked call joined a runtime and a rest sweep with ; (lock verdict batch-last-exit); each part wrote its own exit (RT_EXIT=0, RS_EXIT=0) to its log, and the post-merge rerun joined them with &&.",
    "ablation-dist-preflight was once invoked on a plain rebuild (not an ablation leg) and refused on the tree reading because of an untracked scratch file; read as no measurement, not a gate result."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: domain:spec seat 4 (sunhaolin, #23415's claimant) · noted, not filed — .changeset/23415-drop-storage-other-rows.md (on main, unreleased) says an uninstall 'reports the refusal for that object in its failed[] list ... and leaves the object's row and table in place'; this PR makes that sentence false before it ships. Outside this claim's file surface, so not edited. Suggested replacement: 'An uninstall of a package holding such an object is refused whole before anything is removed; see the 23473 changeset.' Also in PR #23504's Acceptance notes.",
    "noted, not filed (measured, no defect): after a keepData uninstall of P, or a metadata-door delete of P's row without dropStorage, the running registry serves Q's definition of the shared object (label of Q's row) and a data read returns the record; pinned in cases 3 and 4."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Collected · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-14T14:45Z · ⛔ Not ACCEPT yet.

    The dev report 6170924033 is in: PR #23504, head fee128e3da, status: done, premise held (shape B).

    The seat read the diff: the pre-flight in deletePackage before step 4, the real-stack pin, and the changeset. One patch round is owed before the seat record:

    1. .changeset/23415-drop-storage-other-rows.md is on main and unreleased. Its TO says an uninstall "reports the refusal for that object in its failed[] list … and leaves the object's row and table in place". PR fix(metadata-protocol)!: an uninstall that would drop a table another stored row still serves is refused 409 before anything is removed #23504 makes that false in the same release, so the sentence must move to the whole-uninstall refusal. This file was outside the claim's file surface. The seat extends the surface to that one changeset; it is this seat's own landing, metadata-protocol: a delete naming one package with dropStorage=true drops the object table while another package row of the object remains — the remaining package object loses its data #23415.
    2. .changeset/23473-*.md states FROM as 400 PACKAGE_DELETE_PARTIAL. That is main's unreleased intermediate. The last release dropped the shared table instead, as metadata-protocol: a delete naming one package with dropStorage=true drops the object table while another package row of the object remains — the remaining package object loses its data #23415's own FROM says. FROM must state what a reader upgrading from the last release saw.

    Then comes the narrowing's review at CONTRACT_REVIEW_TIER on the landing head. Both wait for a free subagent slot: #23416, the #23455 family and #23493 (main red) are in flight.

    Filed from the PR's measurement: #23505 (the ADR-0029 extension refusal on the same verb answers 500 INTERNAL_ERROR).

  6. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23473,
    "addendum_to": "6170924033",
    "round": "patch round per 6171068654 (text only)",
    "status": "done",
    "pr": "#23504",
    "head": "7d36e8d205d783c9171b952d66dc06b16fa15ae0",
    "edits": [
    "Merged origin/main (10 commits; none touch packages/metadata-protocol/src/protocol.ts or the runtime packages domain, so no source re-test per AGENTS.md §10 scoped re-check).",
    ".changeset/23415-drop-storage-other-rows.md: the TO sentence about an uninstall now reads 'An uninstall of a package holding such an object, without keepData, is refused whole, 409 RESOURCE_CONFLICT, before anything is removed; its message names the object, the rows that keep it, and the ways out.' No tracker number in that shipped text (CHANGELOG readers never see changeset file names). FROM's 'The same held for ... an uninstall' kept.",
    ".changeset/23473-uninstall-shared-object-refusal.md: FROM rewritten to the released behaviour: the uninstall removed the package and its rows and dropped the shared object's table with the package's row of it, while the other package's row kept the object defined and served with its records gone; its next data read answered 500. No mention of the unreleased 400 PACKAGE_DELETE_PARTIAL. The summary line's 'instead of half-uninstalling the package' became 'instead of dropping the object's table while the other row still serves it'. TO, Unchanged and Remedy re-checked against the new FROM: unchanged.",
    "Docs: no edit. grep -n -i -E 'uninstall|PACKAGE_DELETE_PARTIAL|failed\[\]|keepData|dropStorage|another package|shared|drop' on content/docs/deployment/install-modes.mdx and content/docs/concepts/metadata-lifecycle.mdx: no hit says what an uninstall does to a shared object or names failed[] / PACKAGE_DELETE_PARTIAL (the hits are unmanaged-copy lock drops, objectExtensions, the overlay whitelist, the hash's dropped undefined values, and the cluster shared-database note)."
    ],
    "gates": "dispatch-gates --commands at 7d36e8d: 65 families, the same list as before. 64 exit 0, including check-adr-0087-registration --base origin/main (1 declared-breaking changeset with a disposition), check-changeset-no-major (no major bump), check:doc-authoring, check:issue-citations (every citation this change adds resolves), and the five dist readers after a closure rebuild. 1 exit 1: check-empty-changeset --base origin/main, the DELIBERATE CORRECTION class, because this PR changes .changeset/23415-drop-storage-other-rows.md, which it did not add. That red is by design and stays: the gate and pr-automation.yml's route 0 say do NOT restore and do NOT apply skip-changeset. Instead, write a confirmation on the PR naming the note and what changed under it. 'Check Changeset' is not a required context. --ran: 65 derived, 65 run, 0 NOT-MEASURED.",
    "needs_from_seat": "A written confirmation on PR #23504 (my write budget has no PR comment), for example: 'Confirmed: this PR deliberately corrects the pending note .changeset/23415-drop-storage-other-rows.md (this seat's own landing). Its uninstall sentence said a shared object is reported in failed[] and left in place; this PR refuses that uninstall whole with 409 RESOURCE_CONFLICT before anything is removed.'",
    "api_writes": "1 relay stroke: comment → POST /repos//issues/23473/comments (this addendum). git push not counted.",
    "mcp_calls": "0"
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23504 comment 6172664218 · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-14T16:04Z

    The review at CONTRACT_REVIEW_TIER passed on 7d36e8d205 (6172644842). Landing waits on one thing: the maintainer's confirmation of the pending-note correction (.changeset/23415-drop-storage-other-rows.md), per ruling D on #18375. It was requested in the seat's session at 2026-10-14T15:20Z. Waiting on: the maintainer, since then.

  8. 5 remaining items

  9. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: maintainer 「确认」 · letter A · 2026-10-15T04:15Z · said to domain:spec seat 4's session session_0121ooBYRmS8ziFXm9ai5YHm (sunhaolin) in its chat, in answer to the seat's explanation of PR #23504. Carried by that session with provenance under ruling D on #18375; not a director batch.

  10. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop, temporary re-seat · 2026-10-15T04:30Z · landing only: PR #23504, already reviewed and confirmed
    Session: session_0121ooBYRmS8ziFXm9ai5YHm
    Account: sunhaolin (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: claude/issue-23473-uninstall-shared-object-refusal (head 7d36e8d205, unchanged since the seat record)
    Worktree: none. No dev is dispatched: the work is done.
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917; re-seat marker 6184406254, on the maintainer's word 「临时回座把 #23504 落地」)
    File surface: none. This claim lands the reviewed head as it is. The merge queue builds the PR on current main and runs the full PR battery there. The seat reads deletePackage's pre-flight on that merged tree before arming. If that reading finds a break, the seat stops and the card goes back with the reading.
    Container & model: n/a. No dev and no reviewer is needed: the narrowing's CONTRACT_REVIEW_TIER review PASSed on this exact head (6172644842), and a base merge would move the head off that record.
    Clause-②: no
    Responsibility: unchanged from the first claim 6168221878.
    Thread-read: 6184273013
    Ruling-ref: 6184273013 (the maintainer's 「确认」, carried; on PR #23504 6184266311) under ruling D on #18375; lane decision 6168058499 (shape B).
    Serial constraints cleared: I read every open PR's file list in this act. Only PR #23469 (another lane, draft, marchtian; already named in the first claim 6168221878) touches packages/metadata-protocol/src/protocol.ts. Inside deletePackage it changes the step-2 row read (engine.find → findEnvironmentRows) and the per-item loop after step 4. Neither is the pre-flight that PR #23504 adds between steps 3 and 4, and git merge-tree of main + PR #23504 + PR #23469 is clean. PR #23504 lands first; PR #23469 merges main after it. The landing reading also covers the 9 main commits to that file since this PR's merge base.
    Resume-from: none (the newest Release:, 6183361784, carries none).

  11. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amended · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · claim 6184449232 · 2026-10-15T05:05Z

    What happened. PR #23504 left the merge queue on a CI failure.

    • Run 38682506920 on the queue commit 5e97584201, which is main 8d05456cbe plus this PR.
    • Test Core (6/6) failed in packages/runtime (pnpm run test exited 1).
    • On 8d05456cbe alone the same shard is green, so the failure comes from this PR meeting main.
    • The job log cannot be read from a seat container (ci-failure.mjs: annotations only), so the failing test is not named yet.

    The landing claim becomes a dev round. Changes to 6184449232:

    • Worktree: objectstack-issue-23473.
    • File surface: deletePackage steps 1–4 in packages/metadata-protocol/src/protocol.ts, this PR's pin (packages/runtime/src/package-uninstall-shared-object.integration.test.ts), the two changesets, and a merge of main into the branch (merge commit, no rebase, no force). A test that main added is changed only if it asserts behaviour this PR deliberately replaces, and the report must name it.
    • Container & model: M, mode:subagent, default tier.

    Prime suspect. It is a reading, not yet a measurement. package-uninstall-store-refusal.integration.test.ts changed on main with PR #23522 (#23505). Its CONTROL case uninstalls the extending package through the same door. The new pre-flight may read the extending package's row of the extended object as a second holder, and refuse.

    Reviews owed. The merge moves the head, so the CONTRACT_REVIEW_TIER review and the seat record are re-done on the new head before the queue.

  12. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23473,
    "round": "merge round after merge-queue ejection (run 38682506920); claim 6184449232 amended by 6184860304",
    "status": "done",
    "branch": "claude/issue-23473-uninstall-shared-object-refusal",
    "pr": "#23504",
    "head": "addcca0cf5fc63ba01412d211c8c93dc8dbf8941",
    "merge_commit": "192d2051daaf74b40f9f5727449000f60c99646c (git merge origin/main at 8d05456; no conflict, protocol.ts auto-merged; no rebase, no amend, no force)",
    "session": "session_0121ooBYRmS8ziFXm9ai5YHm (subagent: the dispatching PM session)",
    "premise_still_valid": true,
    "summary": "The queue failure was the PR's own pin meeting main's #23510 (222293f, ADR-0029 D9.9), not the pre-flight: on the merge commit 192d205 the three P-and-Q cases of package-uninstall-shared-object.integration.test.ts ('without keepData: refused 409 ...', 'the way out, keepData: ...', 'the way out, P's own row first: ...') failed in setup at h.publish(Q, SHARED) with "Error: Cannot layer object 'probe_ticket': the overlay is bound to package 'com.probe.q', but the object is owned by package 'com.probe.p'. An object has exactly one registry entry ..." (OBJECT_OVERLAY_PACKAGE_MISMATCH / 422 at the save door). The prime suspect is falsified: package-uninstall-store-refusal.integration.test.ts, CONTROL included, passes on the merged tree. Premise measured: the shared state still arises, through today's doors (a draft saved into Q before P's row exists and promoted after it, by publishMetaItem or POST /packages/Q/publish-drafts: rows [P, Q], registry P; a package-less save first, then P and Q: rows [P, Q, package-less]) and as rows stored before D9.9, which boot leaves at rest; on each door-reached state the uninstall of P without keepData answered 409 RESOURCE_CONFLICT and removed nothing. Fix, inside the claim's surface: the pin's twoPackageObject saves P's row through the door and seeds Q's row straight into the store as a row stored before ADR-0029 D9.9 (the save's shape, checksum by hashSpec), as #23510 re-seeded its two sibling fixtures; every case assertion, the package-less case and the CONTROL are unchanged. protocol.ts and both changesets are byte-identical to the reviewed head's delta (the 23415 TO sentence untouched). I seeded rather than use the draft-promotion door because that door is a D9.9 gap and a fixture built on it would break again when the gap closes; the seat may prefer the door path.",
    "fix": "packages/runtime/src/package-uninstall-shared-object.integration.test.ts only (commit addcca0): new seedStoredRow(name, packageId) helper; seedUnboundRow now calls it (same id, label and body as before); twoPackageObject seeds Q's row instead of publish(Q). No protocol.ts change, no new error code, no route change, no Clause-② change.",
    "producers_measured": [
    "save door, P then Q: Q refused 422 OBJECT_OVERLAY_PACKAGE_MISMATCH; rows [P]",
    "save door, P then a save naming no package: addressed to P's row; rows [P]",
    "save door, a save naming no package first, then P, then Q: all succeed, registry owner the sys_metadata sentinel; rows [P, Q, package-less]; uninstall P without keepData 409, nothing removed",
    "Q draft saved before P's row, P publishes, Q promotes via publishMetaItem (package Q): success; rows [P, Q], registry P; uninstall P 409, nothing removed",
    "same, promoted via POST /api/v1/packages/com.probe.q/publish-drafts at HttpDispatcher.handlePackages: 200, outcome published, failed []; rows [P, Q], registry serves P",
    "same, via publishPackageDrafts in process: outcome published; rows [P, Q]",
    "duplicatePackage P to Q with targetNamespace qq: copy renamed qq_probe_ticket; rows [P] (the kept-name case is #23510's reading: failed with the D9.9 text; not re-measured here)",
    "installPackage writes no object sys_metadata row (code reading); importUnmanagedPackage refuses a name an environment row holds before writing (409, code reading); loadMetaFromDb counts a mismatched stored row in errors and leaves it stored (code reading)"
    ],
    "tests": "All at addcca0 unless named, --maxWorkers=2, each under scripts/pm/os-verify-lock.sh, verdict from its VERDICT command-exit line. RED BEFORE at 192d205: pin + two sibling uninstall integration files: 'Tests 3 failed | 13 passed (16)', VERDICT command-exit 1 (the three P-and-Q cases, setup error quoted in summary). AFTER: pin 'Tests 5 passed (5)' exit 0. Runtime uninstall sweep (every src/package-uninstall-.integration.test.ts + every src/domains/packages-.test.ts): 'Test Files 30 passed (30) / Tests 542 passed (542)' exit 0. Runtime local project whole: one-shot run hit the ~10 min foreground cap (exit 124, no verdict, not a measurement), then 4 vitest shards: 96/96/96/96 files passed, tests 1517 + 1234 (4 skipped) + 1694 (15 skipped) + 1332, all exit 0. Runtime typecheck (tsc --noEmit + check:test-typecheck) exit 0, 'OK — ... 27 file(s) / 190 error(s) / 68 pinned signature(s) held'. Metadata-protocol whole suite (3 shards): 80 + 78 (2 skipped) + 78 (1 skipped) files passed; tests 1483 + 25547 (12 skipped) + 1391 (7 skipped), all exit 0; typecheck exit 0. CLI (for the Test Core 2/6 reading): unit tier 2 shards 145 + 145 files, 2416 + 1908 tests, exit 0; integration tier 131 files in 9 slices, all exit 0 (one slice first killed by my own 290 s timeout, exit 124, re-run green); CI's exact slice OS_TEST_SHARD=1/3 both tiers: 'Test Files 141 passed (141) / Tests 1956 passed | 1 skipped', exit 0. ABLATION at addcca0 via scripts/ablation-replace.mjs WRAP mode: anchor 'if (others.length === 0 && !at.unbound) continue;' 1 -> 0, blob 04eee5b790 -> e70c5d7193, replacement adds a marker condition so no row refuses; metadata-protocol rebuilt; ablation-dist-preflight: marker in dist/index.js and dist/index.cjs; predicted 2 red / 3 green, observed 'Tests 2 failed | 3 passed (5)': cases 1 and 2 answered 400 PACKAGE_DELETE_PARTIAL, registryRemoved true, the object in failed[] with the per-item RESOURCE_CONFLICT text (so the pre-flight refuses the same rows the per-item delete refuses). Restore: blob == HEAD 04eee5b7906ebe40733101d3f8815a5b5ececa30, git diff HEAD empty, whole-tree git status --porcelain empty, rebuilt, --absent: marker in none of 24 built files, pin 5/5 green. A first mutation spelling was refused by the tool (replacement contained the anchor, count could not drop) and restored at once: a no-op, nothing measured. The red-before leg of 'revert the fix' is the merge-commit reading above, since the fix is fixture-only.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at addcca0: 65 commands (stderr: tree of objectstack-ai/objectstack at addcca0; 4 paths vs merge base 8d05456). Each run with its exit captured before any pipe: 63 exit 0; 'node scripts/check-empty-changeset.mjs --base origin/main' exit 1 on .changeset/23415-drop-storage-other-rows.md, the maintainer-confirmed deliberate correction (6184266311), red by design; 'pnpm check:dual-build-cjs-loads' first exit 3 (PREREQUISITE NOT MET, packages without dist/), re-run exit 0 ('110 published require entry point(s) across 68 package(s) load'). --ran: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. CI check-runs on addcca0: 31 success, 6 skipped, 3 failure. Green: Build Core, TypeScript Type Check (+ four Type Check lanes), Lint & Repo Gates (+ its lint lanes), Governed Surface Queue Guard, Dogfood Regression Gate, Test Core (1/6), (3/6), (4/6), (5/6), (6/6) (6/6 is runtime + metadata-protocol, the queue's failing shard). Failure: Check Changeset (same 23415 note, red on 7d36e8d too, expected); Test Core (2/6) (CLI slice 1/3, run 38686154595) and the Test Core rollup. Test Core (2/6) NOT REPRODUCED: the job log is unreadable from this container (gh run view --log-failed: run in progress then; the log download host productionresultssa10.blob.core.windows.net is refused by the egress proxy; ci-failure.mjs: annotations only, 'command (.../packages/cli) pnpm run test exited (1)'), so the failing test is not named. Evidence it is not this PR: CI's exact slice passes locally (above); the whole CLI suite passes locally; the same shard was green on 7d36e8d; the queue run on 5e97584 had Test Core (2/6) success, and 5e97584 differs from addcca0 only in the runtime pin file, which no CLI test reads (git grep: no hit); the locally computed PR partition puts '@objectstack/cli 1/3' on shard 2/6. Nothing was changed for it. Rerunning that failed job is a write outside my budget: left to the seat.",
    "mergeable_state": "blocked (read after the push: draft=false, mergeable=true, head addcca0)",
    "pr_body": "Not PATCHed. The current body is stale (its first acceptance note says the 23415 note is not edited; it is, with the maintainer's confirmation) and predates this round. Replacement body: /tmp/claude-0/-home-user/a2360ed0-e03c-558a-9d23-40c33afc0152/scratchpad/issue-23473/pr-body-replacement.md",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos//issues/23473/comments (this report, via scripts/pm/post-stamped.mjs). Plus two git pushes (not REST): 192d205, addcca0. No label write: the PR's labels were not in this dispatch's scope.",
    "deviations": [
    "Ablation target: the dispatch asked to revert the fix and see red. The fix is fixture-only, so that red is the merge-commit reproduction; the one ablation instead disabled the pre-flight to prove the re-seeded fixture still bites (os-dev: ablate only tested behaviour).",
    "origin/main moved to be48c5e after the merge; not merged again (AGENTS.md §10: merge only on conflict, main-caused red or queue removal).",
    "The re-review owed on the new head (claim amendment 6184860304) is the seat's; nothing was readied, queued or labelled."
    ],
    "open_questions": [
    {
    "question": "Fixture shape for the P-and-Q cases: seeded legacy row (as landed) or the door path that still reaches the state today (Q's draft saved before P's row, promoted after)?",
    "options": [
    "A keep the seeded row: robust to the D9.9 gap being closed, same as #23510's two sibling fixtures",
    "B build Q's row through draft-then-promote: a real door today, but the fixture breaks again when D9.9 closes that gap"
    ],
    "recommendation": "A, because the pin's subject is the uninstall, not how the row got in, and the door-reach is recorded in the PR body as a measurement"
    }
    ],
    "out_of_scope_findings": [
    "class: b · reach: public door — POST /api/v1/packages/com.probe.q/publish-drafts at HttpDispatcher.handlePackages answered 200 (outcome published, failed []) and stored an active row bound to com.probe.q of an object a runtime-authored row of com.probe.p owns; registry still serves P · evidence: draft saved into Q (saveMetaItem mode draft) before P's row existed, then P saved, then Q promoted; publishMetaItem with package Q does the same; the save door refuses that same row 422 OBJECT_OVERLAY_PACKAGE_MISMATCH · Seam: spec:ADR-0029 D9.9 overlayPackageMismatchError → runtime:promoteDraftForPublish / publishPackageDrafts (packages/metadata-protocol/src/protocol.ts), registry write-through then refused in applyObjectRegistryMutation · same family as the D9.9 close-out named in #23510's acceptance notes: fold into it, not a single-point card · dedupe words: D9.9 draft promote second package; publish-drafts OBJECT_OVERLAY_PACKAGE_MISMATCH; runtime-authored owner draft; promoteDraftForPublish classifyObjectContribution",
    "carrier: #23510's acceptance notes (D9.9 family close-out, the seat) · the package-less-owner case re-measured on 192d205: a save naming no package, then saves into P and Q, all succeed, rows [P, Q, package-less], registry owner sys_metadata · noted, not filed"
    ]
    }

  13. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23504 comment 6187493234 · domain:spec seat 4 (temporary re-seat) · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-15T08:06Z

    This round was the queue failure. The PR's own fixture met main's ADR-0029 D9.9 refusal. The pre-flight did not cause it.

    The seat arms the PR now.

  14. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23473,
    "round": "second merge round after merge-queue ejection (queue run 38703520759, queue commit 75e7900); claim 6184449232 amended by 6184860304",
    "addendum_to": "6187145964",
    "status": "done",
    "branch": "claude/issue-23473-uninstall-shared-object-refusal",
    "pr": "#23504",
    "head": "4f639e56b06075f522098437d04b08dc06075c11",
    "merge_commit": "ce7a65df2c (git merge origin/main at 23e689d; no conflict; protocol.ts auto-merged; this PR's delta to protocol.ts byte-identical before and after the merge; no rebase, no amend, no force)",
    "session": "session_0121ooBYRmS8ziFXm9ai5YHm (subagent: the dispatching PM session)",
    "premise_still_valid": true,
    "summary": "Reproduced on merge commit ce7a65d: 4 of the pin's 5 cases failed in setup. These are every case that seeds a stored row: 'without keepData: refused 409 ...', 'a package-less row of the object counts as another holder', 'the way out, keepData: ...' and 'the way out, P's own row first: ...'. The error was "Error: Unknown field 'organization_id' on object 'sys_metadata'" at seedStoredRow (package-uninstall-shared-object.integration.test.ts:156). The CONTROL passed. Cause: #23469 (dd29bbe, ADR-0131 D6/D7) dropped the organization column from the sys_metadata family, and with it organization_id: null from the sibling pin's seed. Fix in commit 4f639e5: the pin's seed follows main's row shape (meta-delete-drop-storage-other-rows.test.ts on main) and no longer writes the column. Nothing else changed: the pre-flight, protocol.ts and both changesets are as reviewed, and the 23415 TO sentence is untouched. Address-read comparison on the merged tree: the pre-flight and deleteMetaItem both still call the one packagelessAddress. The pre-flight passes (type, name, undefined, 'active'); deleteMetaItem passes (singularTypeForRepo, name, member, targetState). #23469 changed only that function's body: its where clause was { type, name, state, organization_id: null, ...member } via engine.find, and is now { type, name, state, ...member } via findEnvironmentRows, the seam that drops a legacy organization's rows. Both callers therefore moved together. deletePackage's step-2 read now goes through the same seam, and its per-item loop lost the legacy-organization branch, so the pre-flight's input rows and the per-item delete's rows are the same set.",
    "fix": "packages/runtime/src/package-uninstall-shared-object.integration.test.ts only (commit 4f639e5): organization_id: null removed from seedStoredRow's insert. No protocol.ts change, no new error code, no route change, no Clause-② change.",
    "tests": "All at 4f639e5 unless named, --maxWorkers=2, each under scripts/pm/os-verify-lock.sh, verdict from its VERDICT command-exit line. RED BEFORE at ce7a65d: pin 'Tests 4 failed | 1 passed (5)', VERDICT command-exit 1 (error quoted in summary). AFTER: pin 'Tests 5 passed (5)', exit 0. Runtime uninstall sweep (every src/package-uninstall-.integration.test.ts + every src/domains/packages-.test.ts): 'Test Files 30 passed (30) / Tests 542 passed (542)', exit 0. Runtime local project, whole, in 4 vitest shards (the queue's failing package): 97/97/97/96 files passed; tests 1568 + 1247 (4 skipped) + 1696 (15 skipped) + 1332; all exit 0. Runtime typecheck (tsc --noEmit + check:test-typecheck) exit 0, 'OK — ... 27 file(s) / 190 error(s) / 68 pinned signature(s) held'. Metadata-protocol whole suite, 3 shards: 80 + 78 (2 skipped) + 78 (1 skipped) files passed; tests 1330 + 17832 (12 skipped) + 1388 (7 skipped); all exit 0. Metadata-protocol typecheck exit 0. ABLATION at 4f639e5, via scripts/ablation-replace.mjs WRAP mode: anchor 'if (others.length === 0 && !at.unbound) continue;' 1 -> 0, blob 498517e78a -> 4bee71e85b. A marker condition was added so no row refuses. Metadata-protocol was rebuilt; ablation-dist-preflight found the marker in dist/index.js and dist/index.cjs. Predicted 2 red / 3 green; observed 'Tests 2 failed | 3 passed (5)'. Cases 1 and 2 answered 400 PACKAGE_DELETE_PARTIAL, with the object in failed[] carrying the per-item RESOURCE_CONFLICT text. Restore: HEAD blob = disk blob = 498517e78a682bcb5a82432490f55bd39c91661c, git diff HEAD empty, whole-tree git status --porcelain empty; rebuilt; --absent found the marker in none of 24 built files; pin 5/5 green.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 4f639e5: 65 commands, the same 65 as at addcca0 (stderr: tree of objectstack-ai/objectstack at 4f639e5; 4 paths vs merge base 23e689d). Each ran with its exit code captured before any pipe; 63 exited 0. 'node scripts/check-empty-changeset.mjs --base origin/main' exited 1 on .changeset/23415-drop-storage-other-rows.md: the maintainer-confirmed deliberate correction (6184266311), red by design. 'pnpm check:dual-build-cjs-loads' first exited 3 (PREREQUISITE NOT MET: 40 packages without dist/ at that moment); after later gates had built them it re-ran with exit 0 ('110 published require entry point(s) across 68 package(s) load'). --ran: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. CI check-runs on 4f639e5, read once after the push: 33 success, 6 skipped, 1 failure. All five required contexts are green: Build Core, TypeScript Type Check, Lint & Repo Gates, Governed Surface Queue Guard, and Test Core with all six shards. That includes (6/6), the queue's failing shard, and (2/6), the CLI slice that failed once on addcca0. The one failure is Check Changeset (the 23415 note; expected, the same on every head of this PR).",
    "mergeable_state": "unstable (re-read after the push: draft=false, mergeable=true, auto_merge off, head 4f639e5; the first read right after the push answered unknown)",
    "pr_body": "Not PATCHed. Replacement body with a 'Second merge round' section, readings at 4f639e5, the address-read comparison and the new ablation: /tmp/claude-0/-home-user/a2360ed0-e03c-558a-9d23-40c33afc0152/scratchpad/issue-23473/pr-body-replacement.md",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos//issues/23473/comments (this report, via scripts/pm/post-stamped.mjs over the fleet-write relay). Also two git pushes, which are not REST writes: ce7a65d and 4f639e5.",
    "deviations": [
    "Beyond the dispatch's list, I also ran the whole runtime local project (4 shards), because the queue's failing package was packages/runtime.",
    "The re-review owed on the new head belongs to the seat. Nothing was readied, queued, armed or labelled."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "unchanged from report 6187145964: the D9.9 draft-promotion gap (class b, public door POST /packages/:id/publish-drafts) and the package-less-owner case, both for the D9.9 family close-out; not re-measured on this head"
    ]
    }

  15. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23504 comment 6189155982 · domain:spec seat 4 (temporary re-seat) · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-15T09:43Z

    The second queue ejection came from the PR's own pin meeting main's #23469: its seed still wrote the retired organization_id column. The dev's second round fixed it.

    • main was merged in, and the pin's seed now follows main's row shape; the pre-flight is unchanged.
    • The new head is 4f639e56b0. The CONTRACT_REVIEW_TIER review PASSed on it (6189137865).
    • Every check is green, skipped or red by design.

    #23641, the queue's anchor issue for this file, carries the diagnosis (6187991639). The seat arms the PR now.

  16. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23504 · domain:spec seat 4 (temporary re-seat 6184406254) · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-15T10:10Z

    reading result
    PR #23504 merged 2026-10-15T10:09:53Z through the merge queue (armed by this seat), merge commit 7cb33cfefe, on the third enqueue
    origin/main tip 7cb33cfefe. deletePackage's shared-object pre-flight is in packages/metadata-protocol/src/protocol.ts there, and .changeset/23415-drop-storage-other-rows.md carries the confirmed TO sentence ("refused whole, 409 RESOURCE_CONFLICT")
    queue branches no gh-readonly-queue/main/pr-23504-* ref remains
    reviews CONTRACT_REVIEW_TIER PASS on the landed head 4f639e56b0 (6189137865); seat record 6189155982

    State change: Fixes #23473 closes this card as completed, and pm:dispatched is removed in this act.

    The two queue ejections, both the PR's own pin meeting a newer main, never the pre-flight:

    1. main's fix(metadata-protocol)!: a save into a second package of an object a runtime-authored row owns is refused (ADR-0029 D9.9) #23510 (ADR-0029 D9.9) refused the pin's door-saved second-package row. The row is now seeded as one stored before D9.9.
    2. main's feat(metadata-core,metadata-protocol)!: the sys_metadata family declares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) #23469 retired organization_id from sys_metadata, and the pin's seed still wrote it. The seed now follows main's row shape.

    Diagnosis for the queue's anchor issue #23641: 6187991639. With the pin on main, that anchor has nothing left to count.

    Carried elsewhere:

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