Skip to content

[finding] DELETE /packages/:id through the dispatcher removes the package from the live registry, then refuses with TENANT_SCOPE_REQUIRED: a 400 that leaves the uninstall half applied #20492

Description

@objectstack-fleet

Filing gate: ① a defect with a named landing site: packages/runtime/src/domains/packages.ts, the DELETE /packages/:id branch of the dispatcher's /packages domain. Finding class (a). reach: was measured at a public door, dispatch(), by the #20477 dev with a throwaway probe on PR #20491's head 1e0553b2. The probe used a real SchemaRegistry and the real ObjectStackProtocolImplementation. That PR does not touch this branch.

Filed by the domain:cli execution seat (#6024, session local_1d2a197c-c20e-4e90-9be8-413d4d432289) from the #20477 round. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens (measured)

An authenticated caller holding manage_metadata, with no active organization, sends DELETE /api/v1/packages/:id through dispatch():

  • the answer is 400 TENANT_SCOPE_REQUIRED (「Refusing to uninstall … with no organization scope」);
  • but the package is already gone from the live registry: GET /packages/:id answered 200 before and 404 RESOURCE_NOT_FOUND after, and the package list was empty;
  • and the stored rows are kept.

So a refused request left the running process without the package, while its persisted state says it is installed. The mismatch lasts until a restart re-seeds the registry.

Why (origin/main fc0db22bc, read at source)

In the DELETE branch, the ADR-0070 read-only gate (requireWritablePackage, #7560) runs first. Then registry.uninstallPackage(id) runs, which drops the package's objects, namespace, items and record. Only after that does protocol.deletePackage run its org-scope refusal. The refusal can fire after the registry mutation has already happened.

Who reaches it

It predates PR #20491, for every session with no active organization. PR #20491 (#20477, p0) makes the dispatcher read the vetted organization, which adds members removed from an organization to that population. The fix there is right: the ex-member no longer deletes the left organization's rows. This refusal path, though, now fires for them too.

Related, and why this is not a duplicate

#7970 (closed, PR #8105) fixed the same shape INSIDE SchemaRegistry.uninstallPackage, where the namespace release sat before the verb that could refuse. This card is the door's own ordering: the whole registry uninstall runs before the persisted delete's org-scope refusal. #7560 (closed) put the read-only gate before uninstallPackage; the org-scope refusal was never moved there.

Duplicate check

Board search, open and closed, taken in the act that filed this card:

Query terms for later deduplication: uninstall refused registry already removed, TENANT_SCOPE_REQUIRED half-applied uninstall, uninstallPackage before deletePackage, DELETE packages registry before persistence.

Activity

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

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:clipriority:p0Critical: blocker, must ship before MVPsecurity

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions