Files
rfc-app/docs/superpowers/specs/2026-06-03-m3-design.md
Ben Stull 7703fa233a docs(design): M3 implementation design — decomposition, decisions, test strategy (§22)
Brainstormed design for §22 slice M3 (registry mirror + routing + runtime
branding). Decomposes M3 into a §10.3 Tier-1 test/local-env foundation
(M3-0) plus four sequential feature sub-plans (M3a migration+restamp, M3b
registry mirror, M3c routing+redirects+branding, M3d landing-state/review).
Records resolved decisions: hard-cut branding, §4.1 reconciler pattern,
Stage-1 forward-only migration latitude with a future expand/contract note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:02:40 -07:00

148 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# M3 — Registry mirror + routing + runtime branding — design
**Date:** 2026-06-03
**Spec basis:** `docs/design/multi-project-spec.md` §22, Part C slice **M3**
**Status:** design, pending plan
## Goal
Carry §22 slice **M3** end-to-end: turn the framework from single-project
(the N=1 default at the legacy corpus root) into genuinely multi-project —
projects declared in a registry, reachable at `/p/<project>/`, branded at
runtime. M3 is the slice that makes a *second, named* project reachable by
URL at all.
**Driving objective:** stand up the deployment's `bdd` "planner" project,
reachable at `https://rfc.wiggleverse.org/p/bdd/` as an **`unlisted`**
project (readable by direct link; not in the directory). After **M3c** the
planner is navigable; after **M3d** its entries land `active` (the behavior a
planner wants). The planner's `bdd`-specific scenario/coverage *surface* is
**M5**, out of scope here.
> Note on the original URL ask `/bdd/{PLANNER_TOKEN}/`: resolved to the
> existing §22 model — `bdd` is an `unlisted` project at `/p/bdd/`; the
> "token" is just the shareable link (the unguessable slug). No token-gate
> feature is built; `unlisted` visibility already exists (gated in M2).
## Decisions
### Already resolved upstream (commit `ad2ece1`, §22.13/§22.10/§22.4)
1. **Default project `id`** = config-derived slug
(`DEFAULT_PROJECT_ID` > slug(deployment name) > `default`). M1's `default`
bootstrap id is **re-stamped** to it in M3 before any `/p/` URL is public.
2. **Entry segment** = generic `/p/<project>/e/<slug>` for every type; the
noun (RFC/Spec/Feature) is a type-driven UI label, not in the path.
3. **Legacy `RFC-NNNN`** = frozen, read-only display label in frontmatter;
never used for routing/lookup; never assigned to new entries.
### Resolved in this brainstorm
4. **Plan slicing** — M3 is delivered as a **foundation plan (M3-0) + four
sequential feature sub-plans (M3aM3d)**, each its own branch off `main`,
its own review checkpoint, its own merge. Lower risk for the live OHM
deployment than one long-running branch.
5. **Branding cutover****hard cut + loud failure.** Frontend reads the
deployment/project name only from `GET /api/deployment`; `VITE_APP_NAME`
is removed from the frontend; `REGISTRY_REPO` becomes **required** at
startup (per CLAUDE.md's loud-failure rule).
6. **Registry mirror mechanism** — follow the **existing §4.1 cache pattern**
(`Reconciler.sweep()` + webhook writer, reading via the Gitea API), not a
new mechanism (no local clone, no separate service).
7. **Test strategy** — adopt the handbook's **§10.3 two-tier testing**
standard. Build the **Tier-1 local-Docker suite first** as M3-0; each
feature sub-plan adds unit/integration/functional/e2e coverage on top.
Tier-2 (PPE) is a separate flotilla/Stage-2 task (below).
8. **Deployment stage** — OHM is **Stage 1** (pre-v1, single prod VM,
forward-only migrations). So M3a's table-rebuild migration is allowed as
**one forward-only migration**. The §10.2 expand/contract rule is recorded
as a **future Stage-2 obligation**, not a constraint on M3a today.
## Decomposition
Order is dependency-driven: **M3-0 → M3a → M3b → M3c → M3d.**
### M3-0 — Test & local-env foundation (the §10.3 Tier-1 suite)
Greenfield; lands before feature work so every sub-plan can be verified at
all levels. Reuses the existing in-process `app_with_fake_gitea` fixture seam
where possible.
- **Docker local stack** (`docker compose`): backend (FastAPI) + built
frontend + **fresh SQLite** + **Mailpit** (mail sink for OTC/invite flows)
+ a disposable/stub Gitea for content **and** registry repos.
- *Open design point for the M3-0 plan:* promote the in-process
`fake_gitea` double into a small standalone HTTP stub vs. run real Gitea
in a container. Decide in M3-0's own plan.
- **Frontend unit:** add **Vitest** + testing-library to
`frontend/package.json` (none today).
- **E2E:** add **Playwright**, suite **environment-agnostic**
parameterized by `BASE_URL` + the **mail-sink API URL** (§10.3). Default
target = localhost Docker; PPE host when `BASE_URL` is set to `ppe.<host>`.
One smoke spec: load `/` directory → open a project → view an entry.
- **Tier-2 / PPE is NOT built here.** Standing up `rfc-app-ppe.<base>` (its
own micro VM, own gcloud project, own isolated Gitea, always-pass Turnstile
keys) is a **flotilla operator task** in the deployment tooling, tracked
separately. The suite is written to point at it once it exists.
### M3a — Schema migration + default-id restamp *(foundation; runs against live OHM data)*
- **Migration `027`** (next number after `026`): create-copy-drop-rename for
the 12 tables enumerated in the `026_projects.sql` header, folding
`project_id` into each PK/UNIQUE key and adding the `project_id` FK →
`projects(id)`. (`cached_prs` needs no rebuild — already globally unique.)
- Additive `type` + `initial_state` columns on `projects`.
- **Restamp** (§22.13 step 1): default project `id` `default`
config-derived slug, cascaded across every `project_id` FK. Idempotent.
- **Live-data safety (Stage 1):** back up prod SQLite; rehearse on a copy of
prod; assert per-table row-count parity; FK enforcement on. Forward-only;
brief restart blip acceptable per §9.
- *Gate: until this lands, a second project can collide with the first.*
### M3b — Registry mirror
- `REGISTRY_REPO` **required** at startup (loud failure if unset).
- `refresh_registry()` in `cache.py`: read `projects.yaml` from
`REGISTRY_REPO` via the Gitea API; upsert `projects` rows (`id`, `name`,
`content_repo`, `type`, `visibility`, `initial_state`,
`enabled_models`/`theme``config_json`, `registry_sha`); cache deployment
`name`/`tagline`. Rows never written from user actions.
- Webhook branch in `webhooks.py` on push to `REGISTRY_REPO`;
`Reconciler.sweep()` calls `refresh_registry()`.
### M3c — Routing + redirects + runtime branding *(planner becomes navigable)*
- Backend: `GET /api/deployment` (name, tagline, visible projects per §22.5),
`GET /api/projects/:id` (name, tagline, type, philosophy pointer, theme);
`/p/<project>` scoping on RFC routes.
- Frontend: `/p/<project>/` prefix + `/e/<slug>` segment; **308 redirects**
off old corpus-root URLs (`/rfc/<slug>``/p/<default-id>/e/<slug>`, etc.);
**hard cut** off `VITE_APP_NAME` to runtime branding; per-project `theme`
token overlay; deployment **directory** at `/`; project **switcher**.
### M3d — Landing-state + review behavior
- `initial_state=active` creation path: land a new entry `active`, stamp
`unreviewed`, skip the graduate gate when the project says so.
- `unreviewed` frontmatter fields mirrored into `cached_rfcs`; owner/admin
**mark-reviewed** action; §7 catalog **unreviewed filter** querying the
cached column; type-driven entry noun in chrome.
## Test coverage per sub-plan (all four levels)
- **M3a** — migration unit/integration: fresh DB **and** copy-of-prod
fixture; row-count parity; FK enforcement; restamp idempotence.
- **M3b** — reconciler unit (yaml parse, upsert, `registry_sha`) +
integration (webhook → `projects` rows) on the Docker stub Gitea.
- **M3c** — backend functional (`/api/deployment`, `/api/projects/:id`, 308
redirects, `/p` scoping) + frontend unit (branding/theme from API) + **e2e**
(directory → project → entry nav; old-URL redirect resolves).
- **M3d** — backend functional (active landing, `unreviewed`, mark-reviewed,
filter) + **e2e** (unreviewed filter, mark-reviewed action).
## Out of scope / dependencies
- **M4** (second-project acceptance pass), **M5** (`bdd` scenario/coverage
surface — the planner's type-specific UI), **M6/M7**.
- **PPE standup** (`rfc-app-ppe.<base>`) — flotilla operator task; prerequisite
to running the suite's Tier-2 gate, not framework code.
- **Org-wide testing-standard activation** — §10.3 already *is* the canonical
standard; it should not be duplicated into per-repo memory. Recommended
follow-up (separate, in the engineering repo + dev plugin): have
`wgl-coding-session-init` read app.json and, for apps that deploy the §8
standard stack, surface §10.3 + check whether the Tier-1 suite exists. Not
part of M3.