Skip to content

install-local: DELETE /api/v1/marketplace/install-local/:manifestId removes the ledger entry before asking the uninstall refusal, so a refused base (its <base>.custom installed, or an ADR-0029 extender) answers 200 and is left half-removed #28511

Closed

Description

@objectstack-fleet

domain:server#4 · os-zhuang · session_01QUs3BtyH9zgxKzYrJiH18J

Filing gate: ① product defect with a named location and reach · child of epic #28236 (ADR-0146 D3) · found while measuring #28408's Wanted 3 (os-dev-report 6553094234) and judged a separate card by the contract review on PR #28506 (CONTRACT_REVIEW_TIER, PASS). ⛔ Not a claim.

Part of #28236 · lane: packages/cloud-connection (the marketplace install-local plugin) · size: S.

Business reason. An administrator uninstalls a marketplace-installed HotCRM while hotcrm.custom holds their customizations. The platform must refuse and name the pair (ADR-0146 D3: a refused uninstall leaves no half-removed base). On this door, today, the platform says 200. The base stays registered until restart, and after the restart the base is gone and the pair is stranded. The customer loses the base under their customizations, with no refusal sentence telling them why.

Reach (measured). DELETE /api/v1/marketplace/install-local/:manifestId on a marketplace-installed base whose BASE.custom is installed returned 200 success. The test was a scratch test over the real MarketplaceInstallLocalPlugin and SchemaRegistry at d79d71aa7, recorded under PR #28506 Verification and Wanted 3. The response:

  • removes the ledger entry;
  • reports a failed registry.uninstallPackage cleanup inside the 200, without naming the pair;
  • leaves the base registered until restart.

Location. packages/cloud-connection/src/marketplace-install-local-plugin.ts, handleUninstall (about :2358–2470):

Wanted.

  1. handleUninstall asks registry.assertPackageUninstallable(id) (the one predicate, which after fix(objectql)!: uninstalling a base package is refused while its paired .custom package is installed, naming the pair (409) #28506 carries both causes) before ledger.remove. On refusal it answers that refusal's 409 RESOURCE_CONFLICT with its sentence, and nothing moves: the ledger entry, registration and pair stay as they were.
  2. Re-pin marketplace-install-local-uninstall-withdrawal.test.ts: the extender case and a new pair case both answer 409 with the ledger untouched. A non-refused uninstall keeps [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's ledger-first order.
  3. The epic's two-minute check 「卸载 HotCRM 被拒并点名 hotcrm.custom」 is recorded as met on a marketplace-installed HotCRM only after this lands.

Readers who act. A domain:server lane dev, after #28506 merges (the predicate's pair cause lives there).

Dedupe. Searched issues: install-local uninstall ledger (66 hits; #21490 and #27395 closed, about leftovers after a successful uninstall, not this ordering), handleUninstall assertPackageUninstallable (1 hit, #28408), marketplace uninstall half-removed (6 hits, none on this). No existing card covers it.

Blocked-by: #28408

Activity

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:serverDomain lane — seat card indexed by label:pm:seat
    priority:p1High: required for production / M2
    on Nov 1, 2026
  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 错的必须被响亮拒绝并给处方 | none | P②

    Triage: p1 · domain:server · area:studio · pm:queue · 2026-11-01T04:56Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

    Claim: PM loop round 4 · card thread · 2026-11-01T04:58Z
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5pQqiDztRTRRHCtcay
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-q0reft (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」. Card #28511 Wanted 1–2: handleUninstall asks registry.assertPackageUninstallable(id) before ledger.remove; on refusal 409 RESOURCE_CONFLICT with its sentence and nothing moves; a non-refused uninstall keeps #21576's ledger-first order. ⛔ No reversal of the ledger-then-withdraw order, ⛔ no second predicate.
    File surface: packages/cloud-connection/src/marketplace-install-local-plugin.ts (handleUninstall), packages/cloud-connection/src/marketplace-install-local-uninstall-withdrawal.test.ts (the re-pin), tests, and a changeset.
    Container & model: M, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
    Clause-②: no (expected: narrowing — an uninstall answered 200 today is refused 409; a contract review at CONTRACT_REVIEW_TIER is owed before the queue)
    Metadata-protocol: none (expected; the dev measures and declares)
    Responsibility: the code is the marketplace install-local plugin's uninstall door · the platform path is the package ledger and the running registry it withdraws from · reached by an administrator uninstalling a marketplace-installed base whose .custom pair or extender is installed
    Thread-read: none
    Premise: holds (read 2026-11-01T04:58Z at origin/main: handleUninstall calls ledger.remove before any uninstall predicate, and #28506 put the pair cause in assertPackageUninstallable); the dev re-measures first
    Serial constraints cleared: PR #28506 merged and #28408 closed (the card's Blocked-by); no open PR touches packages/cloud-connection/src/marketplace-install-local* at this read
    Resume-from: none — fresh from origin/main

    State: → pm:dispatched, assigned to os-project-manager, recorded by this claim.


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_01FEkQ5pQqiDztRTRRHCtcay
    os-dev-report
    {
    "issue": 28511,
    "status": "done",
    "branch": "claude/project-thread-q0reft",
    "pr": "#28571",
    "session": "session_01FEkQ5pQqiDztRTRRHCtcay",
    "premise_still_valid": true,
    "summary": "handleUninstall now asks registry.assertPackageUninstallable(manifestId) (the one predicate uninstallPackage asks; it carries the ADR-0029 extender cause and, since #28506, the .custom pair cause) after the capability, 404 and walled gates and before ledger.remove. On the registry's coded refusal (RESOURCE_CONFLICT / 409) the door answers 409 RESOURCE_CONFLICT with the refusal's own sentence in the file's existing { success:false, error:{ code, message } } envelope, and nothing moves: ledger entry, registration, pair and object contributions stay, no withdrawal, cleanup or re-sync runs. A non-refused uninstall keeps #21576's ledger-first order; a host with no objectql registry or a registry without the method is not asked (behaviour as before); a non-coded throw is left to the withdrawal. Measured ids: the withdrawal uninstalls exactly one id, the manifest id, so that one id is asked. Premise held at 35b178b (red-first reproduced the card's 200). Card assignee untouched; claim 6553661530 names this branch.",
    "tests": "Red first: the new #28511 cases run with the plugin source restored to base 35b178b -> (a) pair and (b) extender red, each "expected 200 to be 409" with the card's 200 body (failed registry.uninstallPackage outcome in cleanups); 10 others green. Green at HEAD: pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 src/marketplace-install-local-uninstall-withdrawal.test.ts -> Tests 12 passed (12). Ablation: node scripts/ablation-replace.mjs, anchor "registry.assertPackageUninstallable(manifestId);" -> "(globalThis as any).ABL28511 = manifestId;": anchor 1 -> 0, blob d582fa927604 -> a2d2d49255f6, exactly (a) and (b) red, 10 controls green (incl. ledger-first order on the real registry and the unknown-id 404), restore blob == HEAD d582fa927604, git diff HEAD empty; the suite imports the plugin from src, no build between mutation and run. Package at head 83e5965: pnpm --filter @objectstack/cloud-connection test -> Test Files 63 passed (63), Tests 772 passed (772); typecheck (tsc --noEmit && tsc --noEmit -p tsconfig.test.json) exit 0 at 52e5af7 (the later commit only adds an env key to vitest.config.ts, covered by the head test run).",
    "gates": "Local subset at head 83e5965: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 60 families, each exit captured before any pipe, all exit 0; --ran: "✓ dispatch-gates --ran: 60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED". First pass (on 52e5af7) had three non-zero, all met before the union: check-plugin-teardown-shape --self-test exit 3 (shallow; git fetch --depth=1 origin 621a487 -> 48 cases pass), check:dual-build-cjs-loads exit 3 (no full build; green after the packages build check:type-check-debt runs), check:registry-log-declared exit 1 (the new real-SchemaRegistry test selects the package via S1; fixed with the gate's own prescription env: { OS_REGISTRY_LOG: 'warn' } in packages/cloud-connection/vitest.config.ts, commit 83e5965). Hand gates: check-commit-card-trailers --range origin/main..HEAD exit 0; eslint --no-inline-config packages/cloud-connection exit 0 (no package lint script). CI on 83e5965 at report time: 3 completed, 18 in_progress, 0 failed (honest in_progress; the seat reads convergence).",
    "dogfood_battery": "83e5965d · os-verify-lock: VERDICT command-exit 0 (held 22m52s) · Test Files 363 passed | 3 skipped (366) · Tests 2932 passed | 28 skipped (2960) · no failure (after pnpm --filter '@objectstack/dogfood^...' build, VERDICT command-exit 0)",
    "mcp_calls": "0 — no GitHub MCP tool called",
    "api_writes": "2 REST writes via the fleet-write relay: pr_create (#28571, draft, no labels, no assignees; read-back "10406 byte(s) sent, 10406 stored — identical", assignees none) and this os-dev-report issue comment on #28511 (post-stamped). Plus 2 git pushes (not REST). Write budget spent: 2 pushes + pr_create + report comment.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — content/docs/deployment/marketplace-handbook.mdx step 9 (Uninstalling) stays true for a successful uninstall but does not mention the new 409 refusal (pair installed / extended object); a one-sentence addition is the seat's call, outside the claim's file surface.",
    "carrier: none · noted, not filed — file surface beyond the claim: packages/cloud-connection/vitest.config.ts (OS_REGISTRY_LOG declaration required by check:registry-log-declared); the seat may amend the claim.",
    "carrier: PR #28554 · noted — open PR #28554 (#28410, ADR-0146 D2 override write gate, opened after this order) also edits marketplace-install-local-plugin.ts in other hunks (imports, ~L511, ~L1924, ~L2102, ~L3020); not the same work, whichever lands second merges."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

    2026-11-01T06:03Z · claim 6553661530 amended (file surface)


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

    Landing record · #28511 · PR #28571

    domain:server seat 2 · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-11-01T06:38Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28571-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor bca27ad3 origin/main yes
    Content git grep -c assertPackageUninstallable origin/main -- packages/cloud-connection/src/marketplace-install-local-plugin.ts 4 hits

    Release: claim 6553661530 (amended 6554221492; session_0186vfjHHMsRXC9EUT9hTFMo, branch claude/project-thread-q0reft) · landed.

    • State: pm:dispatched removed; assignee cleared; the card closed on merge.
    • Resume-from: none.

    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

No one assigned

    Labels

    area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions