Skip to content

Prune orphaned Extension contract spaces when a pack is dropped from db.extensions #1192

Description

@borisno2

Problem

opensaas generate seeds an Extension contract space under migrations/<spaceId>/ for every pack declared in db.extensions (#1135, ADR-0065). The seeding tracks the declaration in the grow direction only.

Dropping a pack from db.extensions rewrites the other three emissions correctly — prisma.config.ts goes to extensions: [], contract.ts to extensions: {} — but leaves migrations/<pack>/** on disk untouched. Because nothing was rewritten, CI's generate-determinism gate stays green, and generate exits 0. The failure surfaces later, out of the toolchain, on the next db init / db update:

Orphan contract-space directory migrations/<id>/ for an extension not in extensions; remove the directory or re-add the extension.

(kind: "orphanSpaceDir" — the mirror of the declaredButUnmigrated check ADR-0065 records.) The user's remedy today is to delete the directory by hand.

This was raised in review of #1188 and deliberately deferred: pruning is riskier than it looks, so it needed its own ticket rather than a rushed implementation.

Why this is not a one-liner

migrations/ is the project's own directory, not a framework-owned one. A directory under it may be app-authored — a hand-written migration space, or anything a user put there. There is no manifest of what the seeder created, so "not in db.extensions" is not sufficient grounds to delete a directory.

Safe pruning needs a positive identification that a given directory is a seeder-created pack space before anything is removed. The most likely marker is the space's own refs/head.json (written by runContractSpaceSeedPhase, and carrying the descriptor's hash + invariants), possibly combined with the snapshot store entry it points at. Any design has to answer:

  • What exactly identifies a directory as seeder-created, and how does that survive a toolchain rc bump that changes the marker's shape?
  • What happens to the content-addressed snapshots under migrations/snapshots/<hash>/ once the last space referencing them is gone — pruned, or left (they are shared and cheap)?
  • Does a prune ever run without an explicit signal, given generate runs on every build? An opt-out, a prompt, or a report-only default are all plausible.
  • Refuse-and-report is a legitimate outcome: generate naming the orphan and telling the user to remove it is strictly better than today's silence, and may be the whole fix.

Acceptance

  • Removing a pack from db.extensions and re-running generate no longer leaves the project in a state where generate succeeds and db update refuses.
  • Whatever the mechanism (prune or refuse-and-report), a directory under migrations/ that the seeder did not create is never removed, and there is a test proving it.
  • The Known limits block on seedExtensionContractSpaces (packages/cli/src/generator/extension-spaces.ts) is updated when this lands.

References

Activity

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

    ready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions