fix(§22/registry): prune projects absent from the registry on reconcile (v0.55.0)
The registry mirror was additive-only — it upserted the projects/collections a deployment's projects.yaml declares but never removed ones it had dropped. Re-pinning to a different registry stranded the old project's collections + its cached entries as dead rows that, at scale, starved writes (PPE accumulated ~1.2k orphaned entry rows and had to be reset by hand). `registry.refresh_registry(prune=True)` now calls `projects.prune_absent_projects`, deleting a removed project with its collections and every project-scoped row (the 7 project_id-keyed tables + 13 collection_id-keyed entry-corpus tables + the non-keyed thread_messages descendant) in one FK-off transaction with a `PRAGMA foreign_key_check` backstop that rolls back rather than leave a dangling ref — mirroring restamp_default_project / reconcile_default_collection_id. Scoped for safety: whole-project granularity (a present project is untouched); the default project is never pruned; prune runs ONLY on the full-reconcile triggers (startup + the periodic sweep), never on incidental refreshes (collection create, webhook), so an in-app refresh can't wipe a project. Reached only after a successful parse of a non-empty registry (parse_registry rejects empty; the read raises on transport error), so a transient read can't trigger a wipe. Second of the two §9-surfaced framework fragilities. No migration. backend 685 green (+5: prune removes-all/no-op/never-default/empty-rejected/incidental-no-prune with FK-integrity backstop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,48 @@ skip versions are the composition of each intervening adjacent
|
||||
release's steps in order — no A-to-B path is pre-computed beyond
|
||||
that.
|
||||
|
||||
## 0.55.0 — 2026-06-09
|
||||
|
||||
**Minor — the registry reconcile now prunes projects the registry no longer
|
||||
declares; read the upgrade note.**
|
||||
|
||||
The registry mirror was additive-only: it upserted the projects/collections a
|
||||
deployment's `projects.yaml` declares but never removed ones it had dropped.
|
||||
Re-pinning a deployment to a different registry (or editing `projects.yaml` to
|
||||
remove a project) therefore stranded the old project's collections and its
|
||||
cached entries in the database — dead rows that, at scale, starved writes (a
|
||||
deployment's PPE accumulated ~1,200 orphaned entry rows this way and had to be
|
||||
reset by hand).
|
||||
|
||||
- **Prune-on-reconcile.** `registry.refresh_registry(..., prune=True)` now calls
|
||||
`projects.prune_absent_projects`, which deletes any project absent from the
|
||||
freshly-parsed registry together with its collections and every project-scoped
|
||||
row (entries + their satellites, threads + messages, changes, PRs, stars,
|
||||
watches, …) in one transaction, with FK enforcement off for the multi-table
|
||||
delete and a `PRAGMA foreign_key_check` backstop that rolls the whole thing
|
||||
back rather than leave a dangling reference.
|
||||
- **Whole-project granularity, and only the default-safe triggers.** A project
|
||||
still present in the registry is never touched here (pruning individual entries
|
||||
within a present project belongs to the best-effort content cache). The
|
||||
deployment's **default project is never pruned**, whatever the registry says.
|
||||
Pruning runs only on the two full-reconcile triggers — **startup** and the
|
||||
**periodic reconciler sweep** — never on incidental refreshes (a collection
|
||||
create, a registry webhook), so an in-app refresh can't wipe a project.
|
||||
- **Safe against a transient registry read.** The prune is reached only after a
|
||||
successful parse of a non-empty registry: `parse_registry` rejects an empty
|
||||
project list and the registry read raises on a missing file / transport error
|
||||
(the reconciler keeps last-good on that error). A flaky read can't trigger a
|
||||
wipe.
|
||||
|
||||
**Upgrade note (no steps required for a correctly-configured deployment).** On
|
||||
the first startup after upgrading, any project that exists in the database but is
|
||||
**absent from your `projects.yaml`** will be pruned. For a deployment whose
|
||||
database was only ever populated from its current registry this is a no-op (every
|
||||
DB project is in `projects.yaml`). If you have deliberately out-of-band projects
|
||||
in the database (there is no supported way to create one — every project goes
|
||||
through `projects.yaml`), add them to the registry before upgrading. No database
|
||||
migration and no configuration change.
|
||||
|
||||
## 0.54.1 — 2026-06-09
|
||||
|
||||
**Patch — the Gitea-OAuth sign-in no longer 500s when an account was first
|
||||
|
||||
Reference in New Issue
Block a user