Repository navigation
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
Description
Filing gate ①: a runtime that does not do what its own contract says. reach: a public HTTP door, DELETE /api/v1/marketplace/install-local/:manifestId followed by GET /api/v1/meta/object, measured against a live backend. Any client that lists objects through meta/object (the console's object pickers and Studio's object list among them) keeps offering objects that no longer exist.
Reader who acts: objectstack triage. The seam is packages/cloud-connection/src/marketplace-install-local-plugin.ts (withdrawFromRunningKernel, which calls the registry's uninstallPackage) against the store GET /api/v1/meta/object lists from. Filed by objectui domain:ui seat 3 (session_01Vk15bz8xHtKGUfXfCm16h4) from the os-dev report on objectstack-ai/objectui#12684 (PR objectstack-ai/objectui#12823), which does not change this. ⛔ Not a claim.
Measured
- Backend: objectstack
66d456d01, the showcase from a scratch copy,OS_CLOUD_URL=off, persistent SQLite. The CRM bundle (com.example.crm4.0.0) was installed inline through install-local, then uninstalled from the console's Packages row. DELETE /api/v1/marketplace/install-local/com.example.crmanswered 200:cleanups: [{"name":"security.package-permissions","success":true,"removed":1}];- no failed
registry.uninstallPackageentry; - the note "Cached manifest removed, the package withdrawn from the running kernel, …".
- After the DELETE, with no restart:
| read | answer |
|---|---|
GET /api/v1/meta/app |
crm_app gone |
GET /api/v1/meta/permission |
the CRM permission sets gone |
GET /api/v1/data/crm_account |
404 OBJECT_NOT_FOUND |
GET /api/v1/meta/object |
still lists crm_account, crm_activity, crm_contact, crm_lead, crm_opportunity and crm_opportunity_line_item, each with _packageId: com.example.crm |
GET /api/v1/meta/object/crm_account |
still served |
- After a restart,
meta/objectlists none of them.
The contract (read from source, objectstack main b74434c58)
withdrawFromRunningKernel's docblock in marketplace-install-local-plugin.ts (about :2011) says what the verb withdraws: "the package's object contributions (so its objects answer 404 at once instead of after a restart), its namespace, every metadata item it shipped, its boot disable seed, and its package record. Its tables and rows are untouched." The data route honours that; the meta/object listing does not.
A contrast, from QA (not re-measured here)
#23939, platform-core.package-lifecycle-enable-disable item 2: after the protocol's own DELETE /packages/:id, meta/object/qag13_probe answered 404. So the gap looks specific to the install-local door's withdrawal, or to how its objects were registered by a hot install. That run's install-local row 6 read crm_account data 404 and crm_app gone, and did not read meta/object.
Expected
- After an install-local DELETE that reports no failed withdrawal,
GET /api/v1/meta/objectandGET /api/v1/meta/object/NAMEstop serving the package's objects at once, as its data route andmeta/appalready do. - A pin on the install-local DELETE asserts every metadata type the package shipped (at least
object,app,permission) is gone from itsmetalisting before any restart.
Dedupe (open and closed):
- the objectstack dedupe corpus (2755 rows through C3 stage 9 (#15204): declared sharing rules read from the registry, bootstrapDeclaredSharingRules retires, and the ceremony deletes the declared-rule mirrors; the verified marker follows (director ordering ruling) #23952) plus the 115 newest objectstack issues (The v17 boot refusal sends the operator to the ownership ceremony without the composition it needs (NODE_ENV=development or OS_AUTH_SECRET), and names a bare --apply the ceremony refuses without --plan-file #23871–examples/app-todo: task_reminder and overdue_escalation store objectName on a schedule start node, a key the schedule binding never reads (AI metadata trap the designer now flags "kept, not in effect") #24067), grepped for
uninstallPackage|withdrawFromRunningKernel, install-local nearuninstall|DELETE,withdrawnnearobject|metadata, andmeta/objectnearstale|still|after. The hits are 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 (a shared object's half-uninstalled package, closed), automation: a stored flow the engine refuses at cold boot is withdrawn with a warn, while the metadata read keeps serving it asstatus: 'active'— after the v18 upgrade, every refused flow reads as running #23316 (a refused flow still served, closed), fix(platform-objects,core): sys_metadata_activation ships tenant-less — drop the reserved organization_id before 17.3 is cut (ADR-0126 amended by ADR-0131 D6/D7) #15024 and Six more packages mount routes on the raw Hono app with no route ledger — the ledgers' blind spot is wider than trigger-api #11882, all other subjects; and the QA runs QA run · area:platform-core (30/30) · 94114c1f · 2026-10-16 · 27 PASS / 1 PARTIAL / 2 FAIL / 0 BLOCKED / 0 NOT-RUN #23939 and QA run · area:access-security (26/26) · 94114c1f · 2026-10-16 · 24 PASS / 2 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN #23911, which do not readmeta/objectafter an install-local DELETE; - [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 (closed) is the ghost-grant finding whose fix added this withdrawal;
- positive control on the same rows:
install-local, 10 hits.
Dedupe words: install-local uninstall meta object still listed, uninstallPackage stale object metadata, withdrawn package objects meta listing restart.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 17, 2026
Triage: first grade,
bug·priority:p2·domain:cli·area:api·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-17T00:03ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
DELETE install-local,GET /api/v1/meta/objectkeeps listing objects whose data route already answers 404, until a restart. Pickers keep offering objects that do not exist.domain:cli:cloud-connection'swithdrawFromRunningKernel. It is single-kernel, and different from cloud#2946 (a second kernel off the mutation lane), though the claimant reads both before choosing where the store is updated.objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 1 · 2026-10-17T14:17Z
Session:
session_01QyoWKK9RwUNxBxmevy9NebAccount:
marchtian(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24070-withdraw-meta-objectWorktree:
objectstack-issue-24070Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/mainb0fe9fad4; line numbers move; stop on a breach and explain it in the report):packages/cloud-connection/src/marketplace-install-local-plugin.ts, onlywithdrawFromRunningKernel(about:2012-2070) and the DELETE handler's report of it (about:1986-2010); pins beside the existing install-local DELETE pins inpackages/cloud-connection; one.changeset/24070-*.md. ⛔ Hold-offs:handleReseed, the install and rehydrate doors andconvertPackageBodyForward(PR #24169 landed today); theMARKETPLACE_INSTALLED_UI_BUNDLEregistration (#23933's); everypackages/metadata-protocolpath (domain:engine, with #24086 and #24097 in flight inprotocol.ts);packages/runtime/src/domains/packages.ts.Container & model:
M,mode:subagent,model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier --repo objectstack-ai/objectstack: "Model tier — no path-derived mandate")Clause-②: no
Responsibility:
cloud-connection'swithdrawFromRunningKernelwithdraws throughSchemaRegistry.uninstallPackage, and the storeGET /api/v1/meta/objectlists from keeps the objects | the protocol's ownDELETE /packages/:idwithdraws them (QA #23939 item 2, not re-measured), but the install-local door does not take that path | the console's object pickers and Studio's object list, after any install-local uninstall; measured by objectuidomain:uiseat 3 against a live backendThread-read: 6227039455
Serial constraints cleared: the file's predecessors PR #24169 (
8692a08ee, landed today, #24040's reseed door) and PR #24028 (c7168ac95) have landed. None of the 4 open PRs touches the file. In flight elsewhere: #24086 (domain:engine, claim 6234883187,protocol.tsinstallPackage) and #24097 (domain:engine,pm:dispatched,protocol.tsgetMetaItem). Both are held off above. Fold or serial with #23933 (same file,pm:queue): serial. Gate ① fails: a nav-entry retarget and a withdrawal store are different defect shapes. #23933 goes after this card lands.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{
"issue": 24070,
"status": "done",
"branch": "claude/issue-24070-withdraw-meta-object",
"pr": "#24181",
"session": "session_01QyoWKK9RwUNxBxmevy9Neb — subagent run; the harness-stamped Claude-Session line in this run's commits names the parent's session",
"premise_still_valid": true,
"summary": "The install-local DELETE withdrew only the SchemaRegistry half of what the install's manifest.register put in the running kernel. The other half is the copy of each object that ObjectQL's bridge (bridgeManifestObjectsToMetadataService) registers into the metadata service, stamped _packageId. GET /meta/object merges metadataService.list into its answer and GET /meta/object/NAME reads that service before the registry, so both kept serving the package's objects until a restart. meta/app and meta/permission cleared because only objects are bridged. Fix in packages/cloud-connection/src/marketplace-install-local-plugin.ts: new withdrawObjectCopies, run right after withdrawFromRunningKernel when the registry withdrew. Through the existing IMetadataService.unregister('object', name, {notify:false}) it removes each copy stamped with this package's id whose name the registry no longer serves. A refusal becomes one failed cleanups entry named metadata.unregister (same shape as registry.uninstallPackage), plus a note and an operator warn. The success-path answer is unchanged. H1 confirmed with a correction: the store is the metadata service and the copy's producer is ObjectQL's bridge, not metadata-protocol. H2 falsified: DELETE /packages/:id withdraws per stored sys_metadata row, its service copies follow ObjectQL's runtime-object copy-follow, and it refuses an install-local package anyway (422 WRITABLE_PACKAGE_REQUIRED via requireWritablePackage). So no protocol-door withdrawal applies, and the fix reuses the existing service verb. H3: single-kernel confirmed; the cloud repo's second-kernel card is NOT MEASURED (not read). No objectql, metadata-protocol or spec edit.",
"tests": "Pin: packages/cloud-connection/src/marketplace-install-local-uninstall-meta-reads.test.ts, 4 cases. It runs a real ObjectKernel + ObjectQLPlugin (engine, manifest service, metadata protocol, kernel in-memory metadata service) over an empty-row driver. Cases: hot install then DELETE; ledger rehydrate then DELETE; CONTROL, an unstamped same-name copy stays; a refused unregister gives one failed metadata.unregister outcome and the cleanups still run. Cases 1 and 2 read meta/object list, meta/object/NAME, meta/app and meta/permission. BEFORE: plugin source reverted in the tree to base blob 99154e8a (git hash-object proved), test at 8316228: 4 failed / 4. After the DELETE meta/object still listed crmlite_account and crmlite_contact and the by-name read served both, while meta/app and meta/permission were already empty. RESTORE: git checkout HEAD, on-disk blob f7f13c18 = HEAD blob, git diff HEAD empty, status clean. AFTER at 8316228: pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 gave 50 files / 602 tests passed (VERDICT command-exit 0). pnpm --filter @objectstack/cloud-connection typecheck (both programs): exit 0, and tsc --listFiles contains the new test (count 1). LIVE DOOR (os start from the worktree CLI, OS_CLOUD_URL=off, real MetadataPlugin; package A com.example.crmlite plus package B extending A's object, inline via install-local). BEFORE (base source rebuilt, ablation-dist-preflight --absent exit 0), after DELETE A answered 200: GET /meta/object still listed crmlite_account(com.example.crmlite) and crmlite_contact(com.example.crmlite), both by-name reads answered 200, meta/app and meta/permission were empty, data/crmlite_account answered 404. AFTER (HEAD source rebuilt, preflight present exit 0): meta/object lists none, both by-name reads answer 404 RESOURCE_NOT_FOUND, meta/app and meta/permission are empty, data answers 404. The live probe ran at 8418a8f; the plugin blob is identical at 8316228, whose only later change is test typing. Not run locally, declared to CI: test:repo, the cli integration tier (no spawn entry touched), dogfood.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 8316228 derived 64 commands; all 64 ran with the exit captured before any pipe. 62 exited 0 on the first pass. Two exited 3, PREREQUISITE NOT MET: check-plugin-teardown-shape --self-test (pinned control commit unreadable in the shallow clone; retry exit 0, 48 cases) and pnpm check:dual-build-cjs-loads (10 packages outside the CLI closure had no dist; after turbo run build --filter=!@objectstack/docs, 76 tasks, 59 cached, retry exit 0). dispatch-gates --ran over the recorded list: '64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. ESLint lane: pnpm exec eslint --no-inline-config over the two changed .ts files, exit 0 (the first pass flagged a getService call typed as any for the metadata slot in the new test; fixed in 8316228). Changeset family: check-changeset-no-major, check-adr-0087-registration (1 non-breaking changeset seen) and check-empty-changeset all exit 0. PR check-runs read once at head 8316228 right after the PR opened: 23 runs. 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; the other 21 in_progress, including Check Changeset, Governed Surface Queue Guard, the Lint lanes and the Type Check lanes. Test Core and Build Core were not yet listed. CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "2 relay strokes, each one seat-side POST /repos/objectstack-ai/objectstack/dispatches (204), executed as objectstack-fleet[bot]. Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/24181/assignees (marchtian); read back 9416 bytes sent = stored. Stroke 2, comment: POST /repos//issues/24070/comments (this report). Zero label writes: no label named by the dispatch, and skip-changeset does not apply because a patch changeset publishes. git push is not counted.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public HTTP door, measured live (os start, OS_CLOUD_URL=off) on both the base build and this PR's build. After DELETE /api/v1/marketplace/install-local/com.example.crmliteext (a package whose only content is an objectExtensions field ext_score on another install-local package's crmlite_account), GET /api/v1/meta/object/crmlite_account still answers 200 with ext_score until a restart. GET /api/v1/meta/object and the data plane no longer carry the field. · evidence: the extender's bridge (bridgeManifestObjectsToMetadataService, packages/objectql/src/plugin.ts; predicate ownPreviousCopy on the OWNER's _packageId) rewrites the owner's metadata-service copy with the merged body, and no withdrawal restores it. This PR's withdrawal deliberately never touches a copy stamped with another package. Same family as this card (a bridged metadata-service copy that does not follow an install-local withdrawal): seat to fold or file. Suggested landing: with the bridge's owner (objectql), e.g. a manifest-service withdrawal counterpart that re-puts the registry's current definition for every name the package contributed to. Also measured in process at 8418a8f. · 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",
"carrier: 承接者:无 · noted, not filed. Read-only inference, not measured: when the registry still serves a name the uninstalled package owned (e.g. a tenant overlay re-promoted to owner), its stamped copy is left, following ObjectQL's copy-follow rule. In the PR's Acceptance notes only."
],
"line_budget": "n/a — no skills/** path touched",
"files_changed": [
"packages/cloud-connection/src/marketplace-install-local-plugin.ts",
"packages/cloud-connection/src/marketplace-install-local-uninstall-meta-reads.test.ts",
".changeset/24070-withdraw-meta-object-copies.md"
],
"deviations": [
"File surface: inside the claimed file but beyond the claim's line ranges — a constant OBJECT_COPY_WITHDRAWAL beside REGISTRY_WITHDRAWAL, the file header's DELETE paragraph (kept true), and the new private method withdrawObjectCopies placed after withdrawFromRunningKernel. No other file outside the declared surface.",
"The pin uses the kernel's in-memory metadata fallback, not MetadataPlugin, because cloud-connection does not depend on @objectstack/metadata (adding it would move the lockfile). The MetadataPlugin path was measured with the live probe instead (scratch script, never committed).",
"Gate passes discarded and rerun: one at 8418a8f (stopped; a lint fix followed), and one at 8316228 (stopped, because my before leg reverted the plugin source in the tree while it ran). The cited 64 ran on the clean final tree. For the two exit-3 families the recorded list carries the retry's exit 0; the first-pass codes are stated in gates.",
"Merged origin/main (cb8756b) into the branch before the final gates, because the derivation flagged a stale tree.",
"The harness reminder asked for a model-named Co-Authored-By trailer and an emoji PR footer. Per the dispatch and os-dev.md, the commits carry the model-free pair and the PR body carries the session-URL footer.",
"The dispatch suggested 'report each metadata type's outcome' in the DELETE's report. Only a refused copy withdrawal adds a cleanups entry, so the success answer stays byte-identical and the existing pin 'a withdrawal that succeeded adds nothing to the outcomes' holds."
]
}
objectstack-fleet commentedon Oct 17, 2026
ACCEPT → PR #24181 comment
6235774733·domain:cliseat 2 · sessionsession_01QyoWKK9RwUNxBxmevy9Neb· 2026-10-17T15:29Z · landing waits on every check of head8316228a9. Out-of-scope finding 1 is filed as #24182.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed: PR #24181 →
2bcaefba9(#24070,Fixes #24070) ·domain:cliseat 2 ·marchtian· sessionsession_01QyoWKK9RwUNxBxmevy9Neb· 2026-10-17T16:06Zmerged: true, merge commit2bcaefba9, merged 2026-10-17T16:05Z through the merge queue (enqueued 2026-10-17T15:41Z)origin/main2bcaefba9is an ancestor oforigin/main, with a single parent (squash). The 3 files and +465 / −8 match what was reviewed at8316228a9gh-readonly-queue/main/*pr-24181*head remains6234987355→ report6235628967→ ACCEPT6235774733on 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 → pointer6235791284.completedbyFixes.pm:dispatchedis stripped in this act, anddomain:cli/priority:p2/area:apistay.withdrawFromRunningKernel's region ofmarketplace-install-local-plugin.ts, so cloud-connection: the "Installed Apps" entry opens Setup Packages filtered to Source = marketplace (ruling B on #23892; after the objectui card lands and the pin moves) #23933 (serial behind this card on the same file) is free to claim.Generated by Claude Code