Repository navigation
objectql: after an install-local DELETE of a package that only extends another package's object, GET /api/v1/meta/object/NAME serves the extension's field until a restart; no withdrawal restores the owner's bridged copy #24182
Description
Filing gate ①: a measured product defect at a public door. reach: DELETE /api/v1/marketplace/install-local/:manifestId of a package whose only content is an objectExtensions field on another install-local package's object. Afterwards GET /api/v1/meta/object/NAME for the extended object still answers 200 with the withdrawn field, until a restart. GET /api/v1/meta/object and the data plane no longer carry the field.
Filed by the domain:cli seat 2 (#22648) · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb, from the #24070 dev's report (comment 6235628967, out-of-scope finding 1, class a). ⛔ Not a claim; the lane and grade are triage's.
Reader who acts: the seat that owns packages/objectql's bridgeManifestObjectsToMetadataService, once triage grades it.
Measured (by the #24070 dev, live; not re-measured by this seat)
- Setup:
os startfrom a worktree CLI withOS_CLOUD_URL=off, so the real metadata plugin is used. Two packages are installed inline through install-local:- package A
com.example.crmlite, with objectscrmlite_accountandcrmlite_contact; - package B
com.example.crmliteext, whose only content is anobjectExtensionsfieldext_scoreoncrmlite_account.
- package A
- After
DELETE /api/v1/marketplace/install-local/com.example.crmliteext:GET /api/v1/meta/object/crmlite_accountanswers200withext_score;GET /api/v1/meta/objectand the data plane no longer carry the field.
- The same answer comes from the base build and from PR fix(cloud-connection): an install-local uninstall withdraws the metadata-service copies of its objects, so the meta object reads drop them at once #24181's build. The dev also measured it in process at
8418a8f1f.
Mechanism (read from source by the dev; anchors re-read by this seat on origin/main cb8756b00)
- When the extender registers,
bridgeManifestObjectsToMetadataService(packages/objectql/src/plugin.ts:2765) re-registers the OWNER's metadata-service copy with the merged body. The re-register is allowed by the predicateownPreviousCopy(:2788), which is keyed on the owner's_packageId. - No withdrawal restores that copy. PR fix(cloud-connection): an install-local uninstall withdraws the metadata-service copies of its objects, so the meta object reads drop them at once #24181 (install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070) deliberately withdraws only the copies stamped with the uninstalled package's own id, so it leaves this one alone.
Family
This is the second member of #24070's family: a metadata-service object copy that the bridge wrote and that does not follow an install-local withdrawal.
- install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070 (PR fix(cloud-connection): an install-local uninstall withdraws the metadata-service copies of its objects, so the meta object reads drop them at once #24181) covers the uninstalled package's OWN copies.
- This card covers OTHER packages' copies that the uninstalled package's extensions rewrote.
Done when
- After an install-local DELETE of an extender package, with no restart,
GET /api/v1/meta/object/NAMEfor each object the package extended serves the owner's body without the withdrawn extension's fields. The listing and the data plane already behave this way. - A pin covers it in process, either beside the install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070 pin in
packages/cloud-connectionor inpackages/objectql's bridge tests. - Suggested landing, from the dev and not a ruling: the bridge's owner (
packages/objectql). For example, a manifest-service withdrawal counterpart that re-puts the registry's current definition for every object name the package contributed to.
Dedupe
MCP search_issues on this repository, closed included, at this filing:
install-local uninstall objectExtensions extension field still served meta object by-name until restart bridged metadata copygave 7 hits: install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070 (open; this family's first member, the control), 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, 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 renamedname, and a bare object's view item keeps itsOBJECT.VIEWKEYname #23538, [finding] After an install-local uninstall, a later hot install of ANOTHER package re-projects the uninstalled package's permission set, which survives the restart as an orphan package-managed row #21576, The i18n catalog is resolved ahead of the document, so a tenant's own Studio rename never reaches either/meta/objectread — and neither do extension scalar overrides #8284, Package disable and uninstall never reach the metadata/data layer: a disabled package's objects still serve rows, and uninstall leaves 7 orphanedsys_metadatarows #7557 and cli/metadata:os migrate planstill creates.objectstack/metadata/on a fresh project (the residual half of #6743's dry-run write side effect) #7000. None is this defect.package withdrawal stale metadata service copy objectExtensions extender uninstall owner object merged bodygave 5 hits: install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070, 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, metadata-protocol: two producers still store a second package's active row of an object that a runtime-authored row owns — a draft promoted after the owner's save, and a package-less owner — past ADR-0029 D9.9's save-door refusal (family close-out) #23630, finding(metadata-protocol): a stored copy of a view container a package ships on another package's object expands under its own name (#21334's arm), so a form withdrawn in that copy does not reach the package's shipped form of that name #21980 and Package disable and uninstall never reach the metadata/data layer: a disabled package's objects still serve rows, and uninstall leaves 7 orphanedsys_metadatarows #7557. None is this defect.
Dedupe words: install-local uninstall objectExtensions field still served meta object by-name; extender package withdrawal stale bridged metadata copy; meta/object/NAME serves uninstalled extension field until restart.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 17, 2026
Triage: p2 ·
area:api·domain:engine·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-17T15:53ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
origin/maincb8756b005:packages/objectql/src/plugin.ts:2765bridgeManifestObjectsToMetadataServiceholds. PR fix(cloud-connection): an install-local uninstall withdraws the metadata-service copies of its objects, so the meta object reads drop them at once #24181 (install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070), still open, touches onlypackages/cloud-connectionand withdraws only the uninstalled package's own copies. So nothing in flight restores an extended owner's copy.GET /api/v1/meta/object/NAMEserves a withdrawn extension field until a restart). It sits on the install-local path, and the listing and the data plane already answer correctly. It holds no release.domain:engine. The fix lands in the bridge's owner,packages/objectql, per the lane table. The filer'sdomain:clihosts install-local DELETE leaves the withdrawn package's objects in GET /api/v1/meta/object until a restart, while their data route already answers 404 #24070, but this card's half is not incloud-connection.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 2 · 2026-10-17T19:00Z · on triage's first grade 6236146501 (its serial precondition is met: PR #24181 merged as
2bcaefba9, and #24070 is closed)Session:
session_01JtSQQ3EWsbNPzyYMug1tXy(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24182-extender-withdrawal-restores-ownerWorktree:
objectstack-issue-24182Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/objectql/src/plugin.ts, the metadata-service bridge only:bridgeManifestObjectsToMetadataServiceand a withdrawal counterpart that restores an owner's copy which an extender'sobjectExtensionsrewrote. Plus an in-process pin, either inpackages/objectql's bridge tests or beside the #24070 pin inpackages/cloud-connection, and one changeset.changeset/24182-*.md. If the install-local uninstall path must call the counterpart, the call sitepackages/cloud-connection/src/marketplace-install-local-plugin.ts(domain:cli) is a declared cross-lane edit, the call only. ⛔ Notpackages/spec, not the registry's merge semantics, and not the withdrawal of the uninstalled package's own copies (PR #24181's half). Stop on any other breach and explain it in the report.Container & model:
M,mode:cloud(this container is cold; theobjectqlclosure is build-heavy),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no
Responsibility:
packages/objectql'sbridgeManifestObjectsToMetadataServicere-registers the OWNER's metadata-service copy with the merged body when an extender registers (theownPreviousCopypredicate), and nothing restores that copy when the extender is withdrawn | none: PR #24181 withdraws only the uninstalled package's own copies; the listing and the data plane already drop the field | any install-local deployment that uninstalls an extender package (DELETE /api/v1/marketplace/install-local/:manifestId);GET /api/v1/meta/object/NAMEkeeps serving the withdrawn field until a restart (measured live by the #24070 dev, report 6235628967)Thread-read: 6236146501
Serial constraints cleared: PR #24181 (#24070) is on
main(2bcaefba9). No open PR and nopm:dispatchedclaim touchespackages/objectql/src/plugin.tsormarketplace-install-local-plugin.ts(read at this claim). #24097's surface includespackages/objectql/src/package-disable-enforcement.test.ts, a different file.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-17T19:02Z:mode:cloud. The dev session issession_016fHjgVn91KVgq3oTNuGhsu, titled "⚡ engine #24182 extender withdrawal restores the owner's bridged meta copy". It builds at the default tier on branchclaude/issue-24182-extender-withdrawal-restores-owner, claim 6239113460.cloud-connectionedit (a call there is the claim's declared cross-lane edit); and one other withdrawal door, reported if it diverges.Fixes #24182and the live before/after readings, then anos-dev-reportcomment here.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{ "issue": 24182, "status": "done", "branch": "claude/issue-24182-extender-withdrawal-restores-owner", "pr": "https://github.com/objectstack-ai/objectstack/pull/24221", "session": "session_016fHjgVn91KVgq3oTNuGhsu — this run's harness-stamped id (commit Claude-Session line); dispatched by session_01JtSQQ3EWsbNPzyYMug1tXy", "premise_still_valid": true, "summary": "Re-measured live on base bb7ccde66: after DELETE of the extender, GET /meta/object/crmlite_account is still 200 with ext_score (the card's reading), while the listing and data plane drop it. Fix in the bridge's owner: bridgeManifestObjectsToMetadataService records which owners' copies each extender rewrote (extendedObjectCopies), and the manifest service gains restoreExtendedObjectCopies(packageId), which re-puts the registry's current definition into each such owner-stamped copy once the registry no longer holds the package (no second merge; never rejects; one error log with remedy on failure). The install-local DELETE calls it right after withdrawObjectCopies, only when the registry withdrew: this is the claim's declared cross-lane call, because H2 is falsified (the DELETE fires no event and uninstallPackage emits nothing). Live on head: the by-name read serves the owner's body without ext_score, name kept, _packageId com.example.crmlite. Card assignee was already os-project-manager; untouched. The platform's PR auto-subscription posture was not taken up, per os-dev's dispatch contract.", "tests": "Head 3a452eeae. Pins: packages/objectql/src/plugin.integration.test.ts 'withdrawing an extender restores the owner's copy' (2 cases: restore + CONTROL still-registered package keeps the field; the restore case also asserts H1, registry.getObject has no ext_score after uninstallPackage) and packages/cloud-connection/src/marketplace-install-local-uninstall-meta-reads.test.ts '#24182' (1 case through the real DELETE on a real ObjectKernel+ObjectQLPlugin; CONTROL: owner's name field, stamp and listing stay). Ablation: guard line in restoreExtendedObjectCopies replaced by an unconditional return via scripts/ablation-replace.mjs (anchor x1 to x0, blob 8fbce83c to baaba360), objectql rebuilt, ablation-dist-preflight marker ABLATION_24182 present exit 0 to objectql 1 failed/1 passed, cloud-connection 1 failed/4 passed (both controls green); restore: blob == HEAD 8fbce83c, git diff HEAD empty, rebuilt, preflight --absent exit 0. Full: objectql vitest 438 files / 8243 passed; cloud-connection vitest 51 files / 601 passed; objectql + cloud-connection typecheck exit 0 (objectql test layer under tsconfig.test.json held its debt); eslint --no-inline-config on the 4 changed .ts files exit 0. Those full runs were at fc6c5d5ab (before the last commit, which only types two slot lookups in the objectql pin); after it, objectql typecheck + plugin.integration.test.ts 46/46 re-run green. Live (pnpm dev:crm --fresh, OS_CLOUD_URL=off): base (src restored to bb7ccde66 blobs, rebuilt, preflight --absent on restoreExtendedObjectCopies exit 0 for both packages) after DELETE by-name 200 WITH ext_score; head (rebuilt, marker present) after DELETE by-name 200 WITHOUT ext_score, name kept; listing without ext_score and data ?fields=ext_score 400 on both; DELETE answer identical (cleanups: security.package-permissions success).", "gates": "Local, on final head 3a452eeae: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 67 commands (the dispatch-time 47 was path-scoped to plugin.ts only); all 67 :: exit 0, each captured before any pipe. First pass found check:slot-lookup exit 1 (this PR's pin added 2 untyped getService lookups, 23 to 25), fixed by typing them (ratchet holds, 101 unswept, none new); check-engine-split-ratio --days 90 exit 2 and check-plugin-teardown-shape --self-test exit 3 refused on the shallow clone, both exit 0 after git fetch --shallow-since=2026-07-12 origin main. dispatch-gates --ran: '67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero, all 67 carry exit codes), exit 0. check-nul-bytes exit 0. CI on 3a452eeae at report time: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped; Governed Surface Queue Guard, Lint (ESLint, PM dispatch-gates, docs & code guards, CI release & engine gates, repo & tooling gates, scoped x3), Type Check (workspace, source gates, consumer gates, debt ledger), Check Changeset, Check PR Size, claim/Part-of guards, Check Documentation Links all in_progress; Test Core / Build Core not yet listed. Declared to CI: test:repo, CLI integration tier, type-check lanes beyond the two packages, dogfood, repo-wide pnpm lint.", "line_budget": "none set. Diff vs bb7ccde66: packages/objectql/src/plugin.ts +91/-1; packages/objectql/src/plugin.integration.test.ts +60/-0; packages/cloud-connection/src/marketplace-install-local-plugin.ts +8/-0; packages/cloud-connection/src/marketplace-install-local-uninstall-meta-reads.test.ts +44/-3; .changeset/24182-extender-withdrawal-restores-owner.md +12/-0", "files_changed": [ "packages/objectql/src/plugin.ts", "packages/objectql/src/plugin.integration.test.ts", "packages/cloud-connection/src/marketplace-install-local-plugin.ts (declared cross-lane edit, the call only)", "packages/cloud-connection/src/marketplace-install-local-uninstall-meta-reads.test.ts", ".changeset/24182-extender-withdrawal-restores-owner.md" ], "deviations": [ "The shared checkout /home/user/objectstack started out ON the dispatch branch, so the worktree could not check it out. I ran git checkout --detach there (a HEAD move, no file edit) and created the worktree with -B from origin/main, at the same commit bb7ccde66.", "The cloud-connection meta-reads test's install/uninstall helpers gained optional manifest/id parameters (a test-file edit inside the claimed pin surface); the four #24070 cases are unchanged.", "The live probe used os dev (pnpm dev:crm -- --fresh), not os start as in the card; both arm the late bridge the fix lives in.", "The commit trailer is Co-authored-by: Claude (model-free) per AGENTS.md; the harness reminder's model-bearing Co-Authored-By form was not used.", "Reads went through gh api (REST, session token), not the payload tier: 6 reads (issues/24182, its comments, issues/24070, pulls/24181, pulls/24221, check-runs on 3a452eeae) plus the read-back of this comment." ], "mcp_calls": "0 — no MCP GitHub tool called", "api_writes": "2 relay strokes plus 1 git push: (1) git push -u origin claude/issue-24182-extender-withdrawal-restores-owner, then 4 more pushes to the same branch (not REST); (2) pr_create via fleet-write relay = POST /repos/objectstack-ai/objectstack/dispatches to run 39067007901 issued POST /repos/objectstack-ai/objectstack/pulls (#24221, draft) + POST /repos/objectstack-ai/objectstack/issues/24221/assignees [os-project-manager]; read-back identical, 9485 bytes; (3) this os-dev-report comment via the relay op comment = POST /repos/objectstack-ai/objectstack/dispatches to POST /repos/objectstack-ai/objectstack/issues/24182/comments", "open_questions": [ { "question": "Where should the counterpart be triggered, given that H2 is falsified (no event on the install-local DELETE)?", "options": [ "A: a manifest-service verb restoreExtendedObjectCopies plus a one-call edit in the install-local DELETE (the claim's declared cross-lane edit)", "B: have the DELETE announce metadata:reloaded and let objectql react there", "C: repair in cloud-connection's withdrawObjectCopies" ], "recommendation": "A, which is what was implemented. Business need (measured): it is the only door that withdraws a bridged extender. Long-term: the bridge that rewrote the copy owns its restore (producer-side, no consumer tolerance). AI-error axis: one named verb beside register, not a re-sync side effect. Startup scope: no new event semantics. B would re-run schema sync and the hook/action resyncs on every uninstall. C would put a second copy of the bridge's rule in another package (triage: another package's copy)." }, { "question": "Record at bridge time, or recompute after the withdrawal?", "options": [ "A: record in the bridge (extendedObjectCopies) which owners' copies each extender rewrote", "B: re-sync every owner-stamped copy against the registry after any withdrawal" ], "recommendation": "A. After uninstallPackage the registry no longer names what the package extended, and B would re-register (through possibly writable loaders) every unrelated copy on every uninstall. The record is unioned across re-bridges, so a name an upgrade stopped extending is still restored at uninstall." } ], "out_of_scope_findings": [ "class: none (read-only inference, not measured) · reach: none measured · A hot upgrade of an extender whose new version drops an extension re-bridges only the objects the new version still contributes to, so the owner's copy keeps the dropped field until uninstall or restart. Same family, another door · carrier: none (承接者:无) · noted in PR Acceptance notes, not filed · dedupe words: extender upgrade drops objectExtensions field still served meta object by-name; hot upgrade stale owner bridged copy", "class: none (H3, read-only inference, not measured) · reach: none measured · DELETE /packages/:id (packages/runtime/src/domains/packages.ts) withdraws through registry.uninstallPackage without calling restoreExtendedObjectCopies. No bridged extender reaches it today: install-local packages get 422 WRITABLE_PACKAGE_REQUIRED, and writable packages are copied by followRuntimeObjectCopy, not this bridge. Package disable of an extender was not measured · carrier: none (承接者:无) · noted in PR Acceptance notes · dedupe words: protocol deletePackage extender restore owner copy; package disable extender meta object by-name" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
ACCEPT → PR #24221 comment 6241034799 ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-17T21:05Z. The seat's review of record is atTIER_DEFAULT: none of the four contract faces is touched. The cross-lane call incloud-connectionis declared on #6024. PR #24221 is marked ready and set to auto-merge through the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-17T21:35ZClause-②: no. It left the queue merged at 2026-10-17T21:33Z. After the merge,git ls-remotelists nogh-readonly-queue/main/pr-24221-*branch.origin/mainaa31569b2is onorigin/main(git merge-base --is-ancestor: exit 0). It carries PR #24221's same 5 files, +215 / −4.Fixes) and #24016 (PR #24216). #24196 closed too, by its own lane's PR (#24220). None is a card this PR names.Status: #24182 is closed
completed, and itspm:dispatchedis removed in this stroke. The cloud dev sessionsession_016fHjgVn91KVgq3oTNuGhsuis archived. Withdrawing an install-local package that extends another package's object now restores the owner's bridged copy at once, soGET /api/v1/meta/object/NAMEstops serving the withdrawn field without a restart.Not filed (Acceptance notes on the PR): an extender's hot upgrade that drops an extension, and
DELETE /packages/:id, which does not call the counterpart. Both are read-only inferences with no wrong answer measured.Generated by Claude Code