Repository navigation
fix(metadata): revertPackage finds a code-shipped package's members by the _packageId stamp - #22132
Conversation
…packageId stamp too One helper, collectPackageMembers, reads packageId, package and the _packageId provenance stamp applyProtection writes on every code-shipped item, so a code-shipped package's members are found by the two package-wide writes the way every protocol read finds them. Claude-Session: https://claude.ai/code/session_01EUBvqtauTDmHi2ZgY759p2 Co-authored-by: Claude <noreply@anthropic.com>
…Package keeps its two keys Measured on a showcase boot: with the stamp read by publishPackage, the publish door publishes a read-only platform package's objects (ADR-0070 D2), so publish keeps its two-key lookup pending a decision. Unit pins for the helper's key set and the publish non-change; door pins for the bridge-stamped and applyProtection-stamped revert and the controls. Claude-Session: https://claude.ai/code/session_01EUBvqtauTDmHi2ZgY759p2 Co-authored-by: Claude <noreply@anthropic.com>
…rship Claude-Session: https://claude.ai/code/session_01EUBvqtauTDmHi2ZgY759p2 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 36 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 21587bd0bc0c3c876258d536a3334756bd8f0624 && git checkout 21587bd0bc0c3c876258d536a3334756bd8f0624
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ef1fcb26a24ff3842e00226186477db8f4850fbf 434d0745e722e5a88d8cce92435c5edaefab842e && git checkout -B drift-repro ef1fcb26a24ff3842e00226186477db8f4850fbf && git merge --no-ff 434d0745e722e5a88d8cce92435c5edaefab842e
node scripts/docs-audit/affected-docs.mjs --json ef1fcb26a24ff3842e00226186477db8f4850fbf
|
…with 422 WRITABLE_PACKAGE_REQUIRED Triage's answer on the card: the publish door refuses a non-writable package before publishPackage, and the revert door refuses one after the protocol's stored-row answer, both through requireWritablePackage (ADR-0070 D2). With the publish door guarded, publishPackage reads the same membership as revertPackage (collectPackageMembers). Pins flipped to the new answers; controls for a writable package, an unknown id and a stored row bound to a code package. Claude-Session: https://claude.ai/code/session_01EUBvqtauTDmHi2ZgY759p2 Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22113
Clause-②: no
What changes
ADR-0070 D2 makes a code or installed package read-only. The two package-wide doors now refuse one with the door's existing
422 WRITABLE_PACKAGE_REQUIRED, through the samerequireWritablePackagepredicate thatPATCH /packages/:id/disableandDELETE /packages/:idalready ask. This follows triage's answer on the card: Q1 is A, Q2 is B.POST /api/v1/packages/:id/publishrefuses a non-writable package beforeMetadataManager.publishPackageruns.POST /api/v1/packages/:id/revertrefuses a non-writable package after the protocol's stored-row answer (revertStoredPackage). A stored row bound to a code package, such as an organization overlay draft, keeps the protocol's answer.MetadataManager.publishPackageandrevertPackagefind a package's members through one private helper,collectPackageMembers. It readspackageId,packageand the private_packageIdstamp.applyProtection, and the ObjectQL object bridge copiesgetAllObjects()'s owner tag onto every object it registers.publishPackagetakes the helper only because the publish door now refuses a read-only package in front of it.MetadataManagerhas no notion of package kind. Before the guard existed, the helper inpublishPackagewas measured publishing a platform package's objects (0edb299), which is whycffca05held it back. The only in-repo caller ofpublishPackageis this door.Measured at the door
These readings come from an
objectstack dev --freshboot ofexamples/app-showcase. Base is51290bc; head is434d074.GET /packageslists (com.objectstack.setupand 6 other registry-only packages, 18 platform packages that ship objects,com.example.showcase)WRITABLE_PACKAGE_REQUIREDcom.objectstack.setup,…platform-objects,…service.jobsuccess: false, "No metadata items found"WRITABLE_PACKAGE_REQUIREDcom.example.showcasesuccess: true,itemsPublished: 2(writes onto two read-only capabilities)WRITABLE_PACKAGE_REQUIREDcom.example.no_such_packagesuccess: falsecom.example.repairs(created throughPOST /packages, one view draft-saved and published throughpublish-drafts)success: false, "No metadata items found"; revert 200Overlay drafts (triage's premise for Q1), measured at both base and head. An organization overlay draft of
showcase_task.grid, bound tocom.example.showcase, gave the same sequence on both builds except for the first step:/publishof the showcase.itemsPublished: 2, and the overlay draft was still pending afterwards. So this door never published overlay drafts./revertwhile the draft is pending: 409RESOURCE_CONFLICT"Package 'com.example.showcase' has never been published, so there is no published version", the protocol's stored-row answer (packages: POST /packages/{id}/revert answers 404 "No metadata items found" for a Studio-authored package that has published items #22090)./publish-drafts: 200,publishedCount: 1. The draft is gone and the overlay label serves./revertwith the overlay published and no draft pending: 200.Overlay drafts publish through
publish-draftsand the per-item publish door, and neither passes the guarded branch.Pins
packages/runtime/src/package-revert-code-shipped-members.integration.test.ts. This uses a real ObjectQL over better-sqlite3, the real protocol, the realMetadataManagerandHttpDispatcher, and the real producers of the stamp. Each refusal asserts422andWRITABLE_PACKAGE_REQUIREDplus the sentence's head.scope: 'system'package that ships objects: revert and publish both answer 422 (flipped from the 409 this PR first pinned), and nothing is snapshotted.scope: 'system'package the metadata service never holds, the setup shape: revert and publish both answer 422.packageIdcapability: publish and revert both answer 422, and the capability is not snapshotted.success: false;packageIdmembers: publish, edit and revert answer 200, and the snapshot is restored;itemsPublished: 1), then revert 200;packages/runtime/src/package-revert-stored-members.integration.test.ts. Its two showcase (d) cases are flipped. Each now boots the showcase manifest and asserts 422WRITABLE_PACKAGE_REQUIRED; the "published then edited" case also asserts that nothing is restored.packages/metadata/src/metadata-service.test.ts.Verification (head
434d074)pnpm --filter @objectstack/metadata typecheck: exit 0.pnpm --filter @objectstack/metadata test: 58 files, 870 tests passed.pnpm --filter @objectstack/runtime typecheck, which includescheck:test-typecheck: exit 0, debt ledger held at 27 files / 190 errors / 68 signatures.pnpm --filter @objectstack/runtime test(thelocalproject): 334 files, 4717 passed, 19 skipped.scripts/ablation-replace.mjswith the restore proved by blob:publishPackageback on two keys (metadata rebuilt, dist preflight hit in 4 files), the flipped unit pin and the stamped writable-base door pin went red.git diff HEADwas empty and the tree was clean; after a rebuild the suites read 6/6 unit and 16/16 door.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) derived 64 families, and all 64 exited 0.--ranreconciliation: 64 derived, 64 run, 0 NOT-MEASURED (a derived zero). The new family since the revert-only head ischeck:route-envelope.eslint --no-inline-config --format jsonover the 5 touched.tsfiles found 5 files, 0 errors, 0 warnings.eslint.config.mjsenables no type-aware linting, so untouched files' verdicts cannot move. The fullpnpm lintis CI's.Acceptance notes
metadata: publishedDefinition, a nested copy of the whole item, because publish snapshotsdata.metadata ?? data. It is reachable only for a writable package's flat items now. Carrier: the claimant; no card.MetadataManager.unregisterPackageandquery({ packageId })still match on the old keys.unregisterPackagehas no production caller in this repository. Carrier: the claimant; no card.PackagesPagecalls both doors. On a code package it now receives a 422 with a worded message instead of a 200 or a 404/409. The response shapes are unchanged, so the Console pin is not affected.Generated by Claude Code