feat(projects): M1 — the multi-project schema spine (§22)

Lays the additive foundation for hosting N projects per deployment, with
today's single corpus as the N=1 case. No behavior change: the app runs
exactly as before, single project, with the spine underneath.

- migration 025: `projects` + `project_members` tables; seed the `default`
  project (visibility=public, preserving open-by-default); thread
  `project_id NOT NULL DEFAULT 'default'` onto all 19 slug-bearing tables,
  backfilling existing rows. Additive — no table rebuilds; the slug-keyed
  uniqueness/PK rework is enumerated in the migration header and deferred to
  the slice that activates project #2.
- app/projects.py: §22.13 startup backfill of the default project's
  content_repo from META_REPO (idempotent — never clobbers a value the
  future registry mirror sets).
- config: REGISTRY_REPO wired (optional through M1; consumed by the M3
  mirror), documented in .env.example as META_REPO's successor.
- tests: 6 vertical assertions on the spine (seed, backfill, column shape,
  role CHECK, idempotency, config). Full suite 381 passed.
- docs: align Part C M1/M3 boundaries with the landed code (registry mirror
  + redirect move to M3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-01 18:14:53 -07:00
parent 7aba89655d
commit 6f901e3e2c
7 changed files with 318 additions and 13 deletions
+21 -13
View File
@@ -315,13 +315,17 @@ first, the surfaces that consume them after, hardening last. Each slice is
shippable: a deployment can stop at any slice boundary and still run (the
default project keeps the N=1 case whole throughout).
**M1 — The project spine (schema + registry + default-project migration).**
The `projects` and `project_members` tables; the §4 registry mirror (webhook
+ reconciler over the registry repo); `project_id` threaded through every §5
table per the Part B list; the §22.13 migration that generates the default
project, backfills `project_id`, and stands up the redirect. No UI yet — the
app runs exactly as before, single project, with the spine underneath. This
is the slice that must land atomically; everything after is additive.
**M1 — The project spine (schema + default-project migration).** *(landed)*
The `projects` and `project_members` tables; `project_id` threaded
additively onto every slug-bearing §5 table (migration 025); the §22.13
default project generated and every existing row backfilled to it; the
startup backfill that fills the default project's `content_repo` from
`META_REPO`; `REGISTRY_REPO` wired into config (consumed in M3). No UI, no
routing change, no registry mirror yet — the app runs exactly as before,
single project, with the spine underneath. Additive only: no table rebuilds
(the slug-keyed uniqueness/PK rework is deferred to the slice that activates
project #2, enumerated in migration 025's header). This is the foundation
everything after builds on.
**M2 — Project-scoped authorization + the §22.7 resolver.** The three-tier
composition: `project_members` roles, the most-permissive union with
@@ -331,12 +335,16 @@ re-checked under the project axis. Still single visible project; this slice
is verifiable by granting/revoking roles on the default project and asserting
the gates.
**M3 — Routing + runtime branding.** The `/p/<project>/` route prefix, the
308 redirects, `GET /api/deployment` and `GET /api/projects/:id`, the
frontend cut from `VITE_APP_NAME` to runtime config, per-project `theme`
token overlay. The deployment directory at `/` and the project switcher in
deployment chrome. After M3 a deployment with two registry projects is fully
navigable.
**M3 — Registry mirror + routing + runtime branding.** The §4 registry
mirror (webhook + reconciler over the `REGISTRY_REPO`, populating `projects`
rows beyond `default`); the `/p/<project>/` route prefix and the 308
redirects off the old corpus-root URLs; `GET /api/deployment` and `GET
/api/projects/:id`; the frontend cut from `VITE_APP_NAME` to runtime config,
per-project `theme` token overlay. The deployment directory at `/` and the
project switcher in deployment chrome. After M3 a deployment with two
registry projects is fully navigable — which makes this the slice that must
also land the deferred slug-keyed uniqueness/PK rebuilds (migration 025
header) before a second project can collide with the first.
**M4 — Per-project corpus surfaces.** The §7 catalog, §8 RFC view, §9/§10
proposal/PR flows, §13 graduation, and §14 philosophy all confirmed working