Skip to content

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

Closed

Description

@objectstack-fleet

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.crm 4.0.0) was installed inline through install-local, then uninstalled from the console's Packages row.
  • DELETE /api/v1/marketplace/install-local/com.example.crm answered 200:
    • cleanups: [{"name":"security.package-permissions","success":true,"removed":1}];
    • no failed registry.uninstallPackage entry;
    • 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/object lists 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/object and GET /api/v1/meta/object/NAME stop serving the package's objects at once, as its data route and meta/app already do.
  • A pin on the install-local DELETE asserts every metadata type the package shipped (at least object, app, permission) is gone from its meta listing before any restart.

Dedupe (open and closed):

Dedupe words: install-local uninstall meta object still listed, uninstallPackage stale object metadata, withdrawn package objects meta listing restart.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:cli · area:api · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T00:03Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: after DELETE install-local, GET /api/v1/meta/object keeps listing objects whose data route already answers 404, until a restart. Pickers keep offering objects that do not exist.
    • Why domain:cli: cloud-connection's withdrawFromRunningKernel. 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.
  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    and removed on Oct 17, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-17T14:17Z
    Session: session_01QyoWKK9RwUNxBxmevy9Neb
    Account: marchtian (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24070-withdraw-meta-object
    Worktree: objectstack-issue-24070
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main b0fe9fad4; line numbers move; stop on a breach and explain it in the report): packages/cloud-connection/src/marketplace-install-local-plugin.ts, only withdrawFromRunningKernel (about :2012-2070) and the DELETE handler's report of it (about :1986-2010); pins beside the existing install-local DELETE pins in packages/cloud-connection; one .changeset/24070-*.md. ⛔ Hold-offs: handleReseed, the install and rehydrate doors and convertPackageBodyForward (PR #24169 landed today); the MARKETPLACE_INSTALLED_UI_BUNDLE registration (#23933's); every packages/metadata-protocol path (domain:engine, with #24086 and #24097 in flight in protocol.ts); packages/runtime/src/domains/packages.ts.
    Container & model: M, mode:subagent, model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier --repo objectstack-ai/objectstack: "Model tier — no path-derived mandate")
    Clause-②: no
    Responsibility: cloud-connection's withdrawFromRunningKernel withdraws through SchemaRegistry.uninstallPackage, and the store GET /api/v1/meta/object lists from keeps the objects | the protocol's own DELETE /packages/:id withdraws 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 objectui domain:ui seat 3 against a live backend
    Thread-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.ts installPackage) and #24097 (domain:engine, pm:dispatched, protocol.ts getMetaItem). 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

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    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."
    ]
    }

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24181 comment 6235774733 · domain:cli seat 2 · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-17T15:29Z · landing waits on every check of head 8316228a9. Out-of-scope finding 1 is filed as #24182.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24181 → 2bcaefba9 (#24070, Fixes #24070) · domain:cli seat 2 · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-17T16:06Z

    Reading Result
    PR #24181 object merged: true, merge commit 2bcaefba9, merged 2026-10-17T16:05Z through the merge queue (enqueued 2026-10-17T15:41Z)
    origin/main 2bcaefba9 is an ancestor of origin/main, with a single parent (squash). The 3 files and +465 / −8 match what was reviewed at 8316228a9
    queue branch no gh-readonly-queue/main/*pr-24181* head remains

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions