From 57b2fc5205957789e659fe6ba9b587811b2f623f Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Tue, 2 Jun 2026 18:26:52 -0700 Subject: [PATCH] =?UTF-8?q?docs(design):=20project=20types,=20slug-only=20?= =?UTF-8?q?identity,=20initial=5Fstate=20+=20unreviewed=20flag=20(=C2=A722?= =?UTF-8?q?=20draft)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the §22 multi-project draft along four axes decided this session: - Project type — each project declares document | specification | bdd (immutable, registry-set). One shared propose→branch→PR→graduate engine; type only layers entry schema, terminology, and type-specific surfaces (spec release-planning, BDD scenarios/coverage). §22.4a. - Slug-only identity — an entry is (project_id, slug); the per-project RFC-NNNN numbering and its allocator are retired. §22.4, §2.3/§13 amended. - initial_state — per-project landing state for a new entry: super-draft (document/spec default) or active (bdd default). §22.4b. - unreviewed flag — entries that skip straight to active land unreviewed; graduation implies review so the normal path is never flagged. Owner mark-reviewed clears it; §7 catalog gains an unreviewed filter; mirrored into cached_rfcs. §22.4c. Slicing plan re-sliced six→seven: type/initial_state/unreviewed config ride M3, type surfaces become M5, membership→M6, hardening→M7. Co-Authored-By: Claude Opus 4.8 --- docs/design/multi-project-spec.md | 245 +++++++++++++++++++++++++----- docs/design/multi-project.md | 126 +++++++++++++-- 2 files changed, 317 insertions(+), 54 deletions(-) diff --git a/docs/design/multi-project-spec.md b/docs/design/multi-project-spec.md index 2e9ed08..d6faa46 100644 --- a/docs/design/multi-project-spec.md +++ b/docs/design/multi-project-spec.md @@ -14,8 +14,9 @@ ## 22. Projects: multiple corpora per deployment A **deployment** hosts one or more **projects**. A project is a single -corpus: one content repository (§1) holding RFC entries under `rfcs/`, with -its own slug and `RFC-NNNN` namespaces, catalog, philosophy, branding, +corpus: one content repository (§1) holding entries under `rfcs/`, with +its own per-project slug namespace (the slug is the identity — §22.4), a +declared **type** (§22.4a), catalog, philosophy, branding, member roster, and model universe. The deployment is the substrate the projects share — one Gitea org, one bot, one account system, one inbox, one running process — and the surface a visitor first lands on. @@ -73,8 +74,11 @@ deployment: projects: - id: ohm # url-stable slug, unique within the deployment name: Open Human Model + type: document # document | specification | bdd — immutable (§22.4a) content_repo: ohm-content # repo under the deployment's Gitea org (§22.3) visibility: gated # gated | public | unlisted (§22.5) + initial_state: super-draft # super-draft | active — landing state of a new + # entry; defaults from type (§22.4b) enabled_models: [claude, gemini] # optional; falls back to deployment ENABLED_MODELS theme: { accent: "#5b5bd6" } # optional per-project token overrides (§22.9) ``` @@ -94,14 +98,116 @@ about the bot, the §6 app-owned authorization, or the "app is the only contribution surface" stance changes. There are no per-project Gitea orgs and no per-project bot accounts. -### 22.4 Slug and ID namespaces are per-project +### 22.4 The slug namespace is per-project; the slug is the identity -An RFC's slug (§2) is unique **within its project**, not across the -deployment: `ohm` and `specs` may each have an `intro`. `RFC-NNNN` integer -IDs are assigned per project at graduation as `max(existing integer IDs in -this project) + 1` (§13, §2.3 amended) — each project numbers from `RFC-0001` -independently. A fully qualified RFC reference is therefore -`(project_id, slug)`, and after graduation `(project_id, RFC-NNNN)`. +An entry's slug (§2) is unique **within its project**, not across the +deployment: `ohm` and `specs` may each have an `intro`. The slug **is** the +identity — a fully qualified reference is `(project_id, slug)`, and nothing +more. There is no type prefix and **no per-project numeric ID**: within a +project the slug alone is unambiguous, and the project context (its `/p//` +URL prefix and chrome) carries everything the old `RFC-NNNN` label used to. + +This retires the per-project numbering of earlier drafts. The §13 graduation +flip still happens — it moves an entry from proposal to graduated state and +into the content repo — but it no longer allocates a number; the +`max(integer IDs)+1` allocator (§2.3, the old `api_graduation.py` path) is +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. + +### 22.4a Project type + +Every project declares a `type` in the registry (§22.2), chosen at creation +and **immutable**: one of `document`, `specification`, or `bdd`. Type does not +change the engine — every type uses the same content repo (§22.3), the same +propose→branch→PR→discuss→graduate lifecycle (§§9–13), the same threads, +flags, and chat. Type selects exactly three things: + +1. the **entry frontmatter schema** the project validates entries against (§2); +2. the **terminology** the chrome uses for an entry (the §8.1 noun, catalog + labels); +3. the set of **type-specific surfaces** layered on top of the shared §7 + catalog. + +Type-specific behavior is implemented as a per-type module the framework +selects on `project.type`; the engine itself treats every entry as markdown + +frontmatter regardless of type. `type` is an **open set** in shape — a future +type is a new module plus a new allowed enum value, no schema rebuild — and +the three below are what is defined now. The type names and their behavior are +framework concepts (like role names), not deployment content: a deployment +picks which type each project is, but does not define or rename types. + +- **`document`** — long-form normative prose (OHM: a model of principles and + definitions). Frontmatter is the §2 baseline (title, status, + owners/arbiters, tags). No type-specific surfaces. The §22.13 generated + default project is a `document` project, so the N=1 case is unchanged. +- **`specification`** — a versioned technical specification (this framework's + own `SPEC.md` is the archetype: numbered normative sections, upgrade steps). + Frontmatter adds spec metadata (`version`, lifecycle `status` of + draft/active/superseded, `supersedes`). **Type-specific surface — release + planning:** group entries/changes into versioned releases, carry a + changelog + §20-style upgrade-steps per release, and surface "what is in the + next release." +- **`bdd`** — behavior-driven feature specs: each entry states a feature as + Given/When/Then scenarios with acceptance criteria. Frontmatter adds feature + metadata and an optional link to the `specification` entries a feature + verifies. **Type-specific surface:** a scenario/acceptance view, and — where + a deployment runs a BDD project alongside a specification project — a + coverage view mapping features to the spec sections they exercise. + +> **Draft note.** The shared-engine boundary is locked; the per-type +> *schemas and surfaces* above (notably the specification release-planning +> data model and whether BDD scenarios are free-form markdown or a parsed +> Given/When/Then structure) are first proposals, to be pinned in the +> type-surface slice (Part C, M5). + +### 22.4b Initial state of a new entry + +A project sets the **landing state** a new entry takes when its creating +idea-PR merges (§2.4) — the `initial_state` registry field, one of the §2.4 +super-draft entry-states: + +- **`super-draft`** (default for `document` and `specification`) — a new entry + lands as a super-draft and must be explicitly graduated (§13) to reach + `active`. This is today's flow: propose → super-draft → review → graduate. +- **`active`** (default for `bdd`) — a new entry lands `active` on the idea-PR + merge, with the **`unreviewed` flag set** (§22.4c). The §13 graduate gate is + not surfaced — the entry is already active — but because nothing reviewed + it, the flag marks it as not-yet-vetted until an owner clears it. A behavior + spec is captured fact, not a proposal under deliberation, so it skips the + review-then-promote ceremony; branches, PRs, and discussion work afterward + exactly as on any `active` entry. + +The default comes from the project's **type** (§22.4a) — each type module +supplies it — but `initial_state` is an independent registry knob: a +deployment may run a `bdd` project with `super-draft` if it wants a review +gate, or (less commonly) a `document` project that auto-actives. It changes +only the *landing state and whether graduation is required*; the underlying +propose→branch→PR→discuss engine is unchanged (shared-engine rule, §22.4a). +The §22.13 default project keeps `super-draft`, preserving the N=1 flow. + +### 22.4c The `unreviewed` flag + +An `active` entry carries an **`unreviewed`** boolean, orthogonal to its +`state`. It records whether a human gate has vetted the entry: + +- An entry that reaches `active` by the normal **graduate** path (§13) is + **never** flagged — the graduate action, taken by an owner/admin, *is* the + review. +- An entry that **skips straight to `active`** via `initial_state: active` + (§22.4b) lands with `unreviewed = true`, because nothing reviewed it. + +A **project owner** — `project_admin`, or a deployment `owner`/`admin` (§22.7) +— clears the flag with a **mark-reviewed** action (§17), the same authority +that graduates an entry. Clearing stamps `reviewed_at`/`reviewed_by` for +provenance, paralleling `graduated_at`/`graduated_by`. The flag is a property +of the entry (frontmatter, §2 amendment), so it is git-truth and survives a +cache rebuild, exactly like `state`. + +The §7 catalog gains an **unreviewed filter** so an owner can find the entries +awaiting review; it is the natural worklist for the mark-reviewed action. +`unreviewed` only applies to `active` entries — a `super-draft` is pre-review +by definition, and `withdrawn` is out of scope. ### 22.5 Project visibility @@ -237,8 +343,9 @@ single **default project** so it keeps running unchanged: 1. The migration generates a `projects` row from current config: `META_REPO → content_repo`, `VITE_APP_NAME → name`, `visibility = public` - (preserving the deployment's current open-by-default posture), `id` a - slug derived from the deployment. + (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. 2. Every existing RFC-scoped row (§5 amendment list) is stamped with that `project_id`. 3. Old corpus-root URLs (`/rfc/`, `/proposals/`) 308-redirect to @@ -262,34 +369,63 @@ Applied in place, in the established amendment-note style (cf. §1's and app-owned-authorization paragraphs are unchanged and now read org-wide. - **§2 Meta schema / §2.3 IDs.** Slugs are unique within a project; entry - filenames are unchanged (per content repo). §2.3's `max+1` is scoped to the - project (§22.4). + 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 + (§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 + `graduated_at`/`graduated_by`. +- **§2.4 State machine.** The `(no entry) ─[idea-PR merged]→` transition now + targets the project's `initial_state` (§22.4b): `super-draft` as today, or + straight to `active` when the project (e.g. a `bdd` project) lands entries + there — in which case the entry is stamped `unreviewed` (§22.4c). A new + `active ─[mark-reviewed, owner/admin]→ active` self-transition clears the + flag. The rest of the machine is unchanged. - **§5 Data model.** Add `project_id` to: `branch_visibility`, `branch_contribute_grants`, `stars`, `threads`, `changes`, `watches`, `notifications`, `rfc_invitations`, `rfc_collaborators`, `contribution_requests`, `funder_consents`, the `*_seen` cursors, `actions`, - and the §4 cache tables `cached_rfcs` (PK → `(project_id, slug)`, `rfc_id` - unique per project), `cached_branches`, `cached_prs`, - `pr_resolution_branches`, `proposed_use_cases`. Add the new tables - `projects` and `project_members` (§22.2, §22.6). `users.role` is annotated - as deployment-scope (§22.7). + and the §4 cache tables `cached_rfcs` (PK → `(project_id, slug)`; + also mirrors the `unreviewed` frontmatter flag, §22.4c, so the §7 catalog + filter can query it without reading every entry file), + `cached_branches`, `cached_prs`, `pr_resolution_branches`, + `proposed_use_cases`. Add the new tables `projects` (carrying the immutable + `type`, §22.4a) and `project_members` (§22.2, §22.6). `users.role` is + annotated as deployment-scope (§22.7). - **§6.1 Roles.** Add the §22.7 composition note: deployment roles are now one of three tiers; a plain `contributor` has no implicit access to a project until §22.6 grants a project role (subject to §22.5). - **§6.3 Per-RFC delegated authority.** Note that `project_admin` (§22.6) is the same delegation idea at project scope, sitting above per-RFC authority. - **§7 Left pane.** The catalog is per-project, under `/p//`. The - deployment directory (§22.10) is a new surface above it. -- **§8.1 Breadcrumb.** Gains a leading project segment (§22.10). -- **§13.3 Graduation flip.** Operates on the project's content repo; - `RFC-NNNN` is allocated per project (§22.4). + deployment directory (§22.10) is a new surface above it. The catalog gains + an **unreviewed filter** (§22.4c) — the owner's worklist of `active` entries + that landed unreviewed. +- **§8.1 Breadcrumb.** Gains a leading project segment (§22.10). The entry is + named by its slug, not a number (§22.4); the noun shown around it ("RFC", + "Spec", "Feature") is the project type's label (§22.4a). +- **§13.3 Graduation flip.** Operates on the project's content repo. It flips + status and moves the entry, but **allocates no number** — the slug is the + identity throughout (§22.4); the old per-project `RFC-NNNN` allocation is + gone. Graduation is also **conditional on the project's `initial_state` + (§22.4b)**: a project that lands entries `active` has no super-draft phase, + so the graduate action is a no-op there and is not surfaced. For those + entries the **mark-reviewed** action (§22.4c) takes graduation's place as + the owner/admin vetting step — it clears `unreviewed` instead of flipping + state. - **§14.1 Pre-login landing.** Splits into deployment landing (the directory, §22.10) and per-project philosophy/deck (§14 under `/p//`). Deployment name comes from `GET /api/deployment`, not `VITE_APP_NAME`. - **§17 Backend surface.** RFC routes gain the `/p/` / - `project_id` scoping; add `GET /api/deployment`, `GET /api/projects/:id`, - and the `project_members` management + request-to-join endpoints (§22.6, - §22.8). + `project_id` scoping; add `GET /api/deployment`, `GET /api/projects/:id` + (returns the project's `type`, §22.4a), and the `project_members` + management + request-to-join endpoints (§22.6, §22.8). Add a + **mark-reviewed** endpoint clearing an entry's `unreviewed` flag (§22.4c, + owner/admin), and an `unreviewed` filter param on the catalog list. Type- + specific surfaces (§22.4a) add their own routes, mounted only for projects of + the matching type — e.g. the `specification` release-planning endpoints and + the `bdd` scenario/coverage endpoints. - **§18 Stack.** `ENABLED_MODELS` is the deployment fallback; per-project `enabled_models` overrides it (§22.12). - **§20 Versioning / surface.** `VITE_APP_NAME` deprecated in favor of the @@ -309,11 +445,13 @@ Applied in place, in the established amendment-note style (cf. §1's # Part C — Slicing plan -Six slices carry §22 and its amendments end-to-end. The ordering mirrors +Seven slices carry §22 and its amendments end-to-end. The ordering mirrors DEV.md's original principle — foundations and the cache/permission spine 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). +default project keeps the N=1 case whole throughout). The project `type` +(§22.4a) rides M3 (config) and M5 (its surfaces); M1–M4 are type-agnostic +because the engine is. **M1 — The project spine (schema + default-project migration).** *(landed)* The `projects` and `project_members` tables; `project_id` threaded @@ -341,24 +479,45 @@ rows beyond `default`); the `/p//` 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. +project switcher in deployment chrome. This slice also adds the additive +`type` and `initial_state` columns to `projects` (a small migration — M1 +shipped `projects` without them), mirrors both from the registry, returns +them on `GET /api/projects/:id`, and drives the entry-noun terminology off +`type` (§22.4a). It also teaches the shared creation path to honor +`initial_state` (§22.4b) — land a new entry `active` instead of `super-draft`, +stamping `unreviewed` and skipping the graduate gate when the project says so +— plus the `unreviewed` frontmatter fields (§22.4c) mirrored into +`cached_rfcs`, the owner/admin mark-reviewed action, and the §7 catalog +unreviewed filter that queries that cached column. But no type +*surfaces* yet; beyond their label, landing state, and review flag, all three +types still look the same here. 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 -under project scope with per-project slug/ID namespaces (§22.4). Mostly -inherited from M1–M3; this slice is the end-to-end pass that proves a second -project's full lifecycle (propose → super-draft → graduate → `RFC-0001` *in -that project*). +**M4 — Per-project corpus surfaces (the second-project acceptance pass).** The +§7 catalog, §8 entry view, §9/§10 proposal/PR flows, §13 graduation, and §14 +philosophy all confirmed working under project scope with the per-project, +slug-only namespace (§22.4). This is mostly *inherited* from M1–M3 — the work +is an end-to-end pass that proves a second `document` project's full lifecycle +(propose → super-draft → graduate, identified by slug *in that project*), not +new build. Naming it explicitly as the acceptance slice keeps scope that +belongs in M3 from leaking in. -**M5 — Membership lifecycle.** §22.8 invite (re-scoped §12 machinery) and +**M5 — Type modules + type-specific surfaces.** The per-type layer of §22.4a: +type-specific entry-frontmatter validation (`specification`/`bdd` metadata); +the `specification` **release-planning** surface; the `bdd` scenario/coverage +surface. Type-scoped routes mounted only for matching projects (§17). The +shared engine is untouched — this slice only adds the layers on top, so a +`document` project is unaffected and the M4 acceptance still holds. (The +per-type schema/surface details are the §22.4a draft note's open work.) + +**M6 — Membership lifecycle.** §22.8 invite (re-scoped §12 machinery) and request-to-join (re-scoped §28); the inbox surfacing of join requests; the §22.11 cross-project inbox with `project_id` filtering and project-level mute. The admin surface for managing a project's roster. -**M6 — Hardening + operator path.** Per-project `enabled_models` resolution +**M7 — Hardening + operator path.** Per-project `enabled_models` resolution (§22.12) including funder/§6.6 intersection; the registry's place in `docs/DEPLOYMENTS.md` and the flotilla operator tooling; end-to-end tests spanning two projects with disjoint membership; the §20.4 changelog + @@ -375,5 +534,9 @@ upgrade-steps block; the SPEC merge (Part A appended, Part B applied). contract. With OHM becoming one project among several, the registry sits *above* any single project's content repo, so a file inside one project's repo is wrong — a dedicated repo is the right home. -- **Request-to-join vs. invite-only** — drafted with both (§22.8); M5 may - ship invite-only first and add request-to-join second if scope demands. +- **Request-to-join vs. invite-only** — drafted with both (§22.8); M6 + (membership lifecycle) may ship invite-only first and add request-to-join + second if scope demands. +- **Per-type schemas and surfaces** — the §22.4a draft note's open work: + the `specification` release-planning data model and whether `bdd` scenarios + are free-form markdown or a parsed Given/When/Then structure. Pinned in M5. diff --git a/docs/design/multi-project.md b/docs/design/multi-project.md index 3e622bb..74cafab 100644 --- a/docs/design/multi-project.md +++ b/docs/design/multi-project.md @@ -15,13 +15,15 @@ plainly: "this single repository is its content repository." This change makes it **deployment : project = 1 : N**, where *today's entire deployment becomes the N=1 case*. A **project** is what a corpus is now: a -content repo, its own slug/`RFC-NNNN` namespace, its own catalog, philosophy, -branding, member roster, and enabled-models universe. The **deployment** (the -subdomain — e.g. Wiggleverse) becomes a thin shell hosting a directory of -projects plus a shared account/notification layer. +content repo, its own slug namespace, a declared **type** (§ "Project types" +below), its own catalog, philosophy, branding, member roster, and +enabled-models universe. The **deployment** (the subdomain — e.g. Wiggleverse) +becomes a thin shell hosting a directory of projects plus a shared +account/notification layer. -OHM becomes one project among several (specs, vision docs, …) under one -deployment. +OHM — a *document* project — becomes one project among several: specs +(*specification* projects), behavior suites (*BDD* projects), vision docs, … +all under one deployment. The value of this framing: the migration stays mechanical. Everything deployment-scoped today splits cleanly into: @@ -40,15 +42,89 @@ deployment-scoped today splits cleanly into: not see a private project exists. `visibility` is still a per-project field with `public` and `unlisted` escape hatches (see §3) — gated is the default, not the only mode. -3. **RFC numbering is per-project.** Each project has its own `RFC-0001`, - `RFC-0002`… computed as `max(existing IDs in this project) + 1`. +3. **Entries are identified by slug, scoped to the project.** A fully + qualified reference is `(project_id, slug)`. There is *no* type prefix and + *no* per-project numeric ID: inside a project the slug alone is + unambiguous, and the project (its URL prefix, its chrome) supplies all the + surrounding context. This **supersedes** the earlier per-project `RFC-NNNN` + allocation idea — graduation (§13) still flips an entry's status, but no + longer mints a number. +4. **Each project declares a `type`** — `document`, `specification`, or `bdd`. + Type is chosen when the project is created (in the registry), is immutable, + and selects the project's entry frontmatter schema, its terminology/labels, + and any type-specific surfaces (e.g. release planning for specifications). + All types ride the *same* propose→branch→PR→graduate engine, threads, + flags, and chat; type layers on top — it does not fork the lifecycle. + +## Project types + +A project's `type` is a registry field, fixed at creation. It does **not** +change the engine — every type uses the same content repo, the same +propose→branch→PR→discuss→graduate lifecycle, the same threads/flags/chat. It +selects three things: the **entry frontmatter schema** the project validates +against, the **terminology** the chrome uses for an entry, and the set of +**type-specific surfaces** the project exposes on top of the shared catalog. +The engine treats every entry as markdown + frontmatter regardless of type; +type-specific behavior is a layer, implemented as a per-type module the +framework selects on `project.type`. + +> These three definitions — especially the specification release-planning +> surface and the BDD scenario model — are first drafts. The schema/surface +> details below are proposals to refine, not yet locked. + +- **`document`** — long-form normative prose (OHM is the archetype: a model + of principles and definitions). Frontmatter is today's entry schema + (title, status, owners/arbiters, tags). **No** type-specific surfaces; this + is the baseline, and the N=1 default project (§7) is a `document` project. +- **`specification`** — a versioned technical specification (this app's own + `SPEC.md`, with numbered normative sections and upgrade steps, is the + archetype). Frontmatter adds spec metadata (e.g. `version`, lifecycle + `status` of draft/active/superseded, `supersedes`). Type-specific surface: + **release planning** — group entries/changes into versioned releases, track + the changelog + upgrade-steps for each, and show "what's in the next + release." (This mirrors how rfc-app itself runs VERSION + CHANGELOG + + §20 upgrade steps.) +- **`bdd`** — behavior-driven feature specs: each entry describes a feature + as scenarios in Given/When/Then form with acceptance criteria. Frontmatter + adds feature metadata and an optional link to the `specification` entries a + feature verifies. Type-specific surface: a scenario/acceptance view, and + (where a deployment pairs a BDD project with a specification project) a + coverage view linking features back to the spec sections they exercise. + +Types are an **open set** in shape — a new type is a new module plus a new +allowed `type` value; it needs no schema migration beyond the enum. Document, +specification, and BDD are the three defined now. + +**Landing state (`initial_state`).** A project also sets what state a new +entry lands in when its idea-PR merges — `super-draft` (the normal +propose→review→graduate flow) or `active` (graduated on submission). The +default comes from the type: `document` and `specification` default to +`super-draft`; **`bdd` defaults to `active`** — a behavior spec is captured +fact, not a proposal under deliberation, so it skips the review-then-promote +gate. It's an independent registry knob, so a deployment can override the +default per project. This changes only the landing state and whether +graduation is required; the propose→branch→PR engine is unchanged. + +**The `unreviewed` flag.** Skipping straight to `active` means nothing vetted +the entry, so it lands with an **`unreviewed`** flag set. An entry that reaches +`active` the normal way — super-draft → graduate — is never flagged, because +graduation *is* the review. A project owner clears the flag with a +**mark-reviewed** action (same authority as graduate), and the catalog has an +**unreviewed filter** so owners can find the entries still awaiting review. The +flag is an entry property (frontmatter, git-truth like `state`), orthogonal to +the `active` state and only meaningful on `active` entries. + +The separation-of-concerns rule (CLAUDE.md) is satisfied: the *type names* +and their behavior are framework concepts (like role names), not +deployment-specific content. A deployment chooses *which* type each of its +projects is; it does not define new types or rename them. ## 1. Git topology — one content repo per project One Gitea org for the deployment, **N content repos** (`ohm-content`, `specs-content`, `vision-content`, …). The bot already operates org-wide; it -gains more repos and one registry repo. Slug uniqueness and `RFC-NNNN` -sequences become naturally per-repo = per-project. +gains more repos and one registry repo. Slug uniqueness becomes naturally +per-repo = per-project (and the slug is the whole identity — see Decision 3). Rejected alternatives: subdirectories in one repo (`projects//rfcs/…`) churns every path / branch-name / graduation code path and can't carry @@ -67,8 +143,11 @@ deployment: projects: - id: ohm # url slug, stable, unique in deployment name: Open Human Model + type: document # document | specification | bdd (immutable) content_repo: ohm-content # repo under the deployment's Gitea org visibility: gated # gated | public | unlisted + initial_state: super-draft # super-draft | active — landing state of a + # new entry; defaults from type philosophy_repo_path: PHILOSOPHY.md enabled_models: [claude, gemini] # optional; falls back to deployment ENABLED_MODELS theme: { accent: "#5b5bd6" } # optional per-project token overrides @@ -100,8 +179,10 @@ of them: - `notifications`, `actions` ~15-table migration, all backfillable to a single default project (see §7). -`api_graduation.py`'s `RFC-NNNN` allocator scopes its `max()` query by -`project_id`. +With slug-only identity (Decision 3) there is no per-project number to +allocate: `api_graduation.py`'s `RFC-NNNN` allocator is **retired**, and +graduation reduces to the status flip + content-repo move, keyed on +`(project_id, slug)`. New tables: @@ -109,6 +190,10 @@ New tables: projects( id TEXT PRIMARY KEY, -- 'ohm' name TEXT NOT NULL, + type TEXT NOT NULL -- document | specification | bdd (immutable) + CHECK (type IN ('document','specification','bdd')), + initial_state TEXT NOT NULL -- super-draft | active (landing state, §types) + DEFAULT 'super-draft' CHECK (initial_state IN ('super-draft','active')), content_repo TEXT NOT NULL, visibility TEXT CHECK (visibility IN ('gated','public','unlisted')), config_json TEXT, -- theme, tagline, enabled_models, … @@ -189,7 +274,9 @@ Two chrome layers result: - 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. -- Breadcrumb gains a segment: `Wiggleverse / OHM / RFC-0042 · Human › main`. +- Breadcrumb gains a segment: `Wiggleverse / OHM / Human › main` (the entry + is named by its slug; the entry-noun the chrome uses around it is + type-driven — "RFC", "Spec", "Feature"). - The left-pane catalog (§7) becomes per-project; a project switcher lives in deployment chrome. @@ -253,3 +340,16 @@ This confirms the registry decision and fixes how it integrates: - Per-project `ENABLED_MODELS` resolution vs. deployment universe (§18, §6.6, §6.7 funder) — confirm fallback order. - Slicing plan for the build (mirrors DEV.md's original slice approach). +- **Type surfaces — depth of each.** What concretely is in the + `specification` *release-planning* surface (its own tables? a release = + a tag + a changelog entry + a set of graduated entries?), and does the + `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//e/`) 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.)