docs(design): resolve the three M3-blocking decisions (§22 draft)

- Default project id: config-derived slug (DEFAULT_PROJECT_ID > slug of
  deployment name > 'default'); M1's 'default' bootstrap is re-stamped in M3
  before any /p/ URL is public, so it's meaningful (/p/ohm/) and never renamed
  live. §22.13.
- Entry-noun in URLs: generic /p/<project>/e/<slug> for every type; the noun
  (RFC/Spec/Feature) is a type-driven UI label, not in the path. §22.10.
- Existing RFC-NNNN: kept as a frozen, read-only legacy display label in
  frontmatter id (preserves citations); never used for lookup, never assigned
  to new entries. §22.4, §2.3 amended.

Threads through §22.4/22.10/22.13, the §2.3 amendment, and the M3 slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-02 19:03:15 -07:00
parent 57b2fc5205
commit ad2ece18fa
2 changed files with 55 additions and 23 deletions
+34 -12
View File
@@ -115,6 +115,13 @@ removed. The displayed *noun* around a slug ("RFC", "Spec", "Feature") is a
presentation concern driven by the project's type (§22.4a), not part of the
identity.
**Legacy numbers.** Entries graduated *before* this change keep their existing
`id` (`RFC-NNNN`) in frontmatter as a **frozen, non-identity legacy label**
preserved and shown in the UI so external "RFC-0001"-style citations still
resolve, but never used for routing or lookup (the slug is). New entries are
never assigned one, and graduation does not write `id`. The field is read-only
provenance from here on.
### 22.4a Project type
Every project declares a `type` in the registry (§22.2), chosen at creation
@@ -312,12 +319,16 @@ the §8 RFC view, the §14 philosophy — all per project).
### 22.10 Routing and the deployment landing
Every corpus-scoped route gains a project segment: `/p/<project>/…` carries
the §7 catalog, the §8 `/p/<project>/rfc/<slug>`, the §9/§10
`/p/<project>/proposals/<n>`, and the §14 `/p/<project>/philosophy`. The
root `/` is the **deployment landing**: a directory of the projects the
visitor can see (per §22.5), plus sign-in. An anonymous or non-member visitor
sees only `public` projects there. The §8.1 breadcrumb gains a leading
project segment: `OHM / RFC-0042 · Human main`.
the §7 catalog, the §8 entry view at `/p/<project>/e/<slug>`, the §9/§10
`/p/<project>/proposals/<n>`, and the §14 `/p/<project>/philosophy`. The entry
segment is the **generic `/e/`** for every type — the displayed noun ("RFC",
"Spec", "Feature") is a type-driven label (§22.4a), not part of the path, so
routing stays a single type-agnostic path and avoids colliding with the
reserved sibling segments (`proposals`, `philosophy`, …). The root `/` is the
**deployment landing**: a directory of the projects the visitor can see (per
§22.5), plus sign-in. An anonymous or non-member visitor sees only `public`
projects there. The §8.1 breadcrumb gains a leading project segment:
`OHM / Human main` (slug, with the type-driven noun as its label).
### 22.11 Notifications span projects, one inbox
@@ -345,11 +356,17 @@ single **default project** so it keeps running unchanged:
`META_REPO → content_repo`, `VITE_APP_NAME → name`, `visibility = public`
(preserving the deployment's current open-by-default posture),
`type = document` (every pre-multi-project corpus is a document corpus),
and `id` a slug derived from the deployment.
and the `id` a **config-derived slug**: `DEFAULT_PROJECT_ID` if set, else a
slug of the deployment name, falling back to the literal `default`. (M1's
migration 025 seeds the bootstrap id `default`; the §C-M3 step re-stamps it
to the config-derived slug before any `/p/<id>/` route is public, so the id
is meaningful — e.g. `/p/ohm/…` — and never renamed after URLs go live. The
id stays framework-generic: the framework supplies no deployment name.)
2. Every existing RFC-scoped row (§5 amendment list) is stamped with that
`project_id`.
3. Old corpus-root URLs (`/rfc/<slug>`, `/proposals/<n>`) 308-redirect to
their `/p/<default>/…` equivalents, so existing links survive.
their `/p/<default-id>/…` equivalents — the entry view to
`/p/<default-id>/e/<slug>` (§22.10) — so existing links survive.
4. The operator creates the registry repo (§22.2) declaring the default
project; until they add a second project, the deployment is functionally
identical to before, with one extra path segment.
@@ -371,7 +388,9 @@ Applied in place, in the established amendment-note style (cf. §1's
- **§2 Meta schema / §2.3 IDs.** Slugs are unique within a project; entry
filenames are unchanged (per content repo). §2.3's `RFC-NNNN` `max+1`
allocation is **removed** — the slug is the identity (§22.4); there is no
per-project number. The entry frontmatter schema becomes type-dependent
per-project number. Entries graduated before this change keep their `id`
as a frozen, read-only legacy display label (§22.4), never used for lookup.
The entry frontmatter schema becomes type-dependent
(§22.4a): `document` keeps today's fields, `specification` and `bdd` add
their type metadata. New `active`-entry fields: `unreviewed` (bool) and the
`reviewed_at`/`reviewed_by` provenance pair (§22.4c), paralleling
@@ -475,9 +494,12 @@ the gates.
**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,
rows beyond the default); the **re-stamp of the default project's bootstrap
`id`** (`default` → the config-derived slug, §22.13 step 1) which must land
here, before any `/p/<id>/` URL is public; the `/p/<project>/` route prefix
with the generic `/e/<slug>` entry segment (§22.10) and the 308 redirects off
the old corpus-root URLs (`/rfc/<slug>``/p/<default-id>/e/<slug>`); `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. This slice also adds the additive
`type` and `initial_state` columns to `projects` (a small migration — M1
+21 -11
View File
@@ -269,8 +269,10 @@ Two chrome layers result:
## 5. Routing & UX
- RFC routes gain a project prefix: `/p/<project>/rfc/<slug>`,
`/p/<project>/proposals/<n>`, `/p/<project>/philosophy`, etc.
- Entry routes gain a project prefix with a **generic `/e/` segment**:
`/p/<project>/e/<slug>`, `/p/<project>/proposals/<n>`,
`/p/<project>/philosophy`, etc. The segment is the same for every type; the
noun shown around the slug is a type-driven label, not part of the path.
- Root `/` becomes the **deployment landing = project directory** (the
Wiggleverse home). For an anonymous or non-member visitor under gated
default, that's only public/unlisted-by-link projects.
@@ -293,10 +295,13 @@ The N=1 path keeps existing single-project deployments working:
1. Migration creates one **default project** from current config (`META_REPO`
`content_repo`, `VITE_APP_NAME``name`, visibility seeded to match the
deployment's current open posture, likely `public`).
deployment's current open posture, likely `public`). Its `id` is a
config-derived slug (`DEFAULT_PROJECT_ID`, else slug of the deployment
name, else `default`); M1's `default` bootstrap id is re-stamped to it in
M3 before any `/p/` URL is public.
2. Every existing row's `project_id` is stamped to that default project.
3. An optional default-project redirect keeps old `/rfc/<slug>` URLs alive
(308 → `/p/<default>/rfc/<slug>`).
(308 → `/p/<default-id>/e/<slug>`).
This is the SPEC §20 upgrade-steps block.
@@ -346,10 +351,15 @@ This confirms the registry decision and fixes how it integrates:
`bdd` scenario model stay free-form markdown or get a structured
Given/When/Then schema the app parses? Drafted shallow; pin before the
type-surface slice.
- **Entry-noun in URLs/labels.** Slug-only identity means no `RFC-NNNN`. Is
the route segment generic (`/p/<project>/e/<slug>`) with the displayed noun
type-driven, or does the segment itself vary by type? (Leaning: generic
path segment, type-driven label.)
- **Existing graduated numbers.** OHM already has `RFC-0001…` in filenames.
On dropping numeric identity, do those numbers survive as a display/legacy
artifact, or get normalized to slugs? (Migration/back-compat detail.)
- **Entry-noun in URLs/labels** *resolved 2026-06-02:* generic route
segment `/p/<project>/e/<slug>` for every type; the displayed noun
("RFC"/"Spec"/"Feature") is a type-driven label, not part of the path
(§22.10, §22.4a).
- **Existing graduated numbers** *resolved 2026-06-02:* pre-change graduated
entries keep their `RFC-NNNN` `id` in frontmatter as a frozen, read-only
legacy display label (preserves citations); never used for lookup, never
assigned to new entries (§22.4).
- **Default project `id`***resolved 2026-06-02:* a config-derived slug
(`DEFAULT_PROJECT_ID`, else slug of the deployment name, else `default`);
M1's `default` bootstrap id is re-stamped in M3 before any `/p/` URL is
public, so it's meaningful (e.g. `/p/ohm/`) and never renamed live (§22.13).