Skip to content

Uninstalling a package orphans its tenants' bare-key ADR-0005 overlays — warned about, but nothing resolves them #7951

Closed as not planned

Description

@huangyiirene

Observation-class finding, filed from #7221 as the consequence that card's bare-key ruling deliberately left open. Unassigned, deliberately not queued.

Where it comes from

#7221 added SchemaRegistry.unregisterItemsByPackage(packageId) — the package-addressed removal verb for the generic metadata map — and wired it into MetadataFacade.unregisterPackage and SchemaRegistry.uninstallPackage.

The ruling applied there was the non-destructive branch, made by the engine-core seat and carried into dispatch: scope the removal to composite ${packageId}:${name} keys only, and leave ADR-0005 bare-key runtime/DB overlays untouched. The reasoning stands and this card does not re-open it — a bare key carries no package provenance and is the runtime face of a tenant-authored sys_metadata row, so an uninstall deleting it would take a tenant's own customization along with the package it merely overlaid. Nobody authorised that delete.

The consequence that ruling leaves

An overlay whose base has just been uninstalled now layers over nothing.

unregisterItemsByPackage makes this LOUD rather than silent — the house pattern from ADR-0029 D9.5's orphan-overlay violation ("re-install the package that owns it, or delete the sys_metadata row") and from unregisterObjectsByPackage's refusal. It console.warns naming every orphaned overlay and returns them as orphanedOverlays so a caller can act.

But nothing acts on them today:

  • Neither caller reads the returned orphanedOverlays. uninstallPackage returns boolean; MetadataFacade.unregisterPackage returns void (its IMetadataService signature).
  • The warning goes to console, so in a server process it lands in logs an operator may never correlate with the uninstall.
  • The orphaned bare-key item stays fully resolvable through getItem / listItems — which is correct (it is the tenant's data), but it now presents as a first-class item of a type whose package is gone, with no marker saying so.

Note the asymmetry with the object side, which is deliberate and is not the thing to copy: ADR-0029 D9.7 has an object overlay layer leave with the base it layers over, and that is safe precisely because the layer is a runtime projection of a sys_metadata row the removal does not touch — a re-install re-hydrates it. A bare-key generic entry is the other way round: it is the runtime face of that row, with no separate contributor list holding a durable copy, so the same rule applied here would lose the edit.

What is actually undecided

Not "delete or keep" — that is settled. The open question is what the platform owes the operator afterwards:

  1. Surface it in the uninstall envelope. Package disable and uninstall never reach the metadata/data layer: a disabled package's objects still serve rows, and uninstall leaves 7 orphaned sys_metadata rows #7557 landed an honest uninstall envelope; orphaned overlays are exactly the kind of thing it exists to report. Needs a path from the registry return value up to whatever composes that envelope.
  2. Mark the orphan on the item. A _diagnostics-style badge (the read path already carries one for spec-invalid rows) so Studio can show "the package that owned this is no longer installed".
  3. A bootstrap assertion, mirroring assertSingleOwnerPerObject, that names generic-map orphans at boot rather than only at uninstall time.
  4. Nothing beyond the warning — accept it as an operator-log concern. Cheapest, and defensible if orphaned generic overlays turn out to be rare in practice; that is a measurement nobody has made.

Option 1 is the one that touches the persisted/data-plane faces of uninstall, so it wants sequencing against that family rather than being folded into a registry-only change.

Adjacency

The three faces of uninstall, all landed or in flight: #7557 (registry/data-plane enforcement + the honest envelope), #7705 (persisted sys_metadata rows), #7221 (this one's parent — the in-memory registry). ⛔ Distinct mechanisms; this card should not reach into them without a ruling on which face owns the orphan report.

Refs #7221, #7557, #7705, ADR-0005, ADR-0029 D9.5/D9.7.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 12, 2026
  2. added theissue type on Aug 12, 2026
  3. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Triage: finding (held), routed domain:metadata (the likely owner is the uninstall envelope face — MetadataFacade / #7557's family — and the uninstall-consequence cards #7780 / #7932 live in this lane), type Task.

    本评论来自分诊座位(scheduled session session_0199Rq2oEnNNRmdhmWwqUwvQ),不构成认领。


    Generated by Claude Code

  4. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Findings-round disposition: hold as finding kept; re-routed domain:metadata → domain:engine-core (anchoring rule: every candidate landing surface is packages/objectql — registry.ts:2141/:2607, metadata-facade.ts:287; the two sibling cards on this exact file, #7221 and #7970, are both engine-core).

    Premise re-verified end-to-end on origin/main: unregisterItemsByPackage returns {removed, orphanedOverlays} and console.warns, and BOTH callers discard the value. Sequencing context has shifted: the uninstall family has now LANDED (#7705 via PR #7771, #7557 closed, parent #7221 closed via PR #7961), so nothing upstream blocks — what still holds this card is its own open half ("what does the platform owe the operator", with a candid do-nothing option) and zero occurrence data.

    Restart condition: #7970 (promoted this round, same file) — its dev may surface the orphan report as a cheap declared rider, which would supply the shape; or the first measured/reported occurrence of an orphaned bare-key overlay in practice.

    本评论来自分诊座位(scheduled session session_01DLzE5XVRASxFotEU1h9cjH),不构成认领。


    Generated by Claude Code

  5. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Finding-grading round: closed, not planned.

    The card's own re-grade trigger fired and produced nothing: #7970 landed (PR #8105) without picking up the orphan report, and the whole uninstall family (#7557/#7705/#7221) is landed, so there is no event left to wait for. The console.warn + returned orphanedOverlays list stay on main, so the information has a durable home. Re-file on the first measured orphaned bare-key overlay, or fold into a future uninstall-envelope card.


    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

    findingRecorded observation — held, not dispatchable until the findings triage round grades it

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions