diff --git a/backend/migrations/027_projects_runtime_config.sql b/backend/migrations/027_projects_runtime_config.sql new file mode 100644 index 0000000..937d792 --- /dev/null +++ b/backend/migrations/027_projects_runtime_config.sql @@ -0,0 +1,31 @@ +-- §22 M3 (Plan A) — additive registry/runtime-config schema. +-- +-- This is the additive half of M3's backend. It adds the project `type` and +-- `initial_state` columns (mirrored from the registry), the deployment +-- singleton (deployment name/tagline mirrored from the registry), and the +-- §22.4c review columns on cached_rfcs. NO table rebuilds: the §22.13 PK +-- rebuilds and the default->slug re-stamp ride a later migration (Plan B), +-- just before a second project can collide (see migration 026's DEFERRED +-- block and docs/superpowers/specs/2026-06-03-m3-backend-design.md §1/§6). + +ALTER TABLE projects ADD COLUMN type TEXT NOT NULL DEFAULT 'document' + CHECK (type IN ('document', 'specification', 'bdd')); +ALTER TABLE projects ADD COLUMN initial_state TEXT NOT NULL DEFAULT 'super-draft' + CHECK (initial_state IN ('super-draft', 'active')); + +-- Deployment-level identity (name, tagline) mirrored from the registry's +-- `deployment:` block. A singleton: the CHECK pins it to one row. +CREATE TABLE IF NOT EXISTS deployment ( + id INTEGER PRIMARY KEY CHECK (id = 1), + name TEXT, + tagline TEXT, + registry_sha TEXT, + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); +INSERT OR IGNORE INTO deployment (id) VALUES (1); + +-- §22.4c review flag + provenance. unreviewed is git-truth (mirrored from +-- entry frontmatter); it survives a cache rebuild like `state` does. +ALTER TABLE cached_rfcs ADD COLUMN unreviewed INTEGER NOT NULL DEFAULT 0; +ALTER TABLE cached_rfcs ADD COLUMN reviewed_at TEXT; +ALTER TABLE cached_rfcs ADD COLUMN reviewed_by TEXT; diff --git a/backend/tests/test_migration_027.py b/backend/tests/test_migration_027.py new file mode 100644 index 0000000..91ad565 --- /dev/null +++ b/backend/tests/test_migration_027.py @@ -0,0 +1,54 @@ +"""Migration 027 — additive §22 M3 schema (projects.type/initial_state, the +deployment singleton, cached_rfcs review columns). No table rebuilds in Plan A.""" +from __future__ import annotations + +import tempfile +from pathlib import Path + +from app import db +from app.config import Config + + +def _fresh_config() -> Config: + tmp = Path(tempfile.mkdtemp(prefix="mig027-")) / "t.db" + return Config( + gitea_url="x", gitea_bot_user="x", gitea_bot_token="x", gitea_org="x", + meta_repo="meta", registry_repo="registry", + oauth_client_id="x", oauth_client_secret="x", app_url="x", + secret_key="x", database_path=tmp, owner_gitea_login="x", + webhook_secret="x", + ) + + +def test_027_adds_project_type_and_initial_state(): + cfg = _fresh_config() + db.run_migrations(cfg) + conn = db.connect(cfg.database_path) + cols = {r["name"]: r for r in conn.execute("PRAGMA table_info(projects)")} + assert "type" in cols and cols["type"]["dflt_value"] == "'document'" + assert "initial_state" in cols and cols["initial_state"]["dflt_value"] == "'super-draft'" + conn.close() + + +def test_027_creates_deployment_singleton(): + cfg = _fresh_config() + db.run_migrations(cfg) + conn = db.connect(cfg.database_path) + rows = list(conn.execute("SELECT id FROM deployment")) + assert [r["id"] for r in rows] == [1] + try: + conn.execute("INSERT INTO deployment (id) VALUES (2)") + raised = False + except Exception: + raised = True + assert raised + conn.close() + + +def test_027_adds_review_columns_to_cached_rfcs(): + cfg = _fresh_config() + db.run_migrations(cfg) + conn = db.connect(cfg.database_path) + cols = {r["name"] for r in conn.execute("PRAGMA table_info(cached_rfcs)")} + assert {"unreviewed", "reviewed_at", "reviewed_by"} <= cols + conn.close()