Files
Keysat 3e199fd8d5 Phase 1 Workstream A+E: thesis substrate + dual-approval gate
- migration 0002_phase1_architect: thesis_lines (core spine + per-segment lines),
  thesis_nodes (+ append-only revisions), thesis_versions (one-canonical-per-line
  DB invariant), thesis_reviews (dual approval + feedback), segments. Reversible.
- backend/mcp/architect_tools.py: agent draft tools (node tree, versions,
  segments, get_canonical fails-closed) — NO self-approval path. MCP-exposed.
- backend/thesis_review.py + server.py routes: human-gated approval. Dual sign-off
  via thesis_required_approvals; atomic supersede; every action logged.
- docs/PHASE_1.md (kickoff brief); docs/OPERATIONS.md (partner guide);
  start9/0.4 "Resolve duplicate names" fuzzy action.

Verified on synthetic data: dual approval promotes correctly, exactly one
canonical survives supersede, get_canonical fails closed, full interaction_log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:20:00 -05:00

125 lines
7.1 KiB
SQL

-- Phase 1 — Workstream A/E: the Architect's thesis substrate.
--
-- ADDITIVE AND REVERSIBLE ONLY (guardrail #3). Reversal: 0002_phase1_architect.down.sql.
-- Applied once by backend/core_migrations.py (schema_migrations ledger).
--
-- Models the "living messaging source of truth" as: multiple THESIS LINES (a core
-- spine + per-segment narratives), each a tree of typed NODES with full revision
-- history, frozen into immutable VERSIONS that a human signs off to make canonical.
-- Dual approval + collaborative text feedback live in thesis_reviews. Segments are
-- versioned defs that tie to the Phase-0 canonical_entities.segment pointer.
-- ============================================================================
-- thesis_lines — each distinct narrative line. is_core=1 is the shared spine;
-- others are segment-specific (Grant: different segments may carry different,
-- related thesis lines). Full-length ids (not the 8-char CRM ids).
-- ============================================================================
CREATE TABLE IF NOT EXISTS thesis_lines (
id TEXT PRIMARY KEY, -- 'thl_' + uuid4 hex
line_key TEXT NOT NULL UNIQUE, -- slug: 'core' | 'btc_native_hnwi' | ...
name TEXT NOT NULL,
segment_key TEXT, -- NULL for the core spine; else the segment this line serves
is_core INTEGER NOT NULL DEFAULT 0,
description TEXT,
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
deleted_at TEXT
);
-- ============================================================================
-- thesis_nodes — typed node tree per line (the unit of iteration).
-- ============================================================================
CREATE TABLE IF NOT EXISTS thesis_nodes (
id TEXT PRIMARY KEY, -- 'thn_' + uuid4 hex
line_id TEXT NOT NULL REFERENCES thesis_lines(id) ON DELETE CASCADE,
parent_id TEXT, -- tree edge; NULL for thesis_root
node_type TEXT NOT NULL, -- thesis_root|throughline|section|claim|proof_point|objection|rebuttal|segment_cut
ord REAL NOT NULL DEFAULT 0,-- REAL so insert-between never renumbers siblings
title TEXT,
body TEXT,
status TEXT NOT NULL DEFAULT 'draft', -- draft|candidate|approved|retired
variant_group TEXT, -- nodes sharing this are competing phrasings of one idea (A/B)
meta TEXT, -- JSON: tone tags, confidence, evidence_refs -> canonical_entities/source ids
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
deleted_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_thesis_nodes_line ON thesis_nodes(line_id);
CREATE INDEX IF NOT EXISTS idx_thesis_nodes_parent ON thesis_nodes(parent_id);
CREATE INDEX IF NOT EXISTS idx_thesis_nodes_variant ON thesis_nodes(variant_group);
-- ============================================================================
-- thesis_node_revisions — append-only per-node history (provenance + undo).
-- ============================================================================
CREATE TABLE IF NOT EXISTS thesis_node_revisions (
id TEXT PRIMARY KEY,
node_id TEXT NOT NULL,
line_id TEXT,
rev_no INTEGER NOT NULL,
node_type TEXT, title TEXT, body TEXT, status TEXT, ord REAL, variant_group TEXT, meta TEXT,
change_summary TEXT,
change_reason TEXT, -- WHY the edit
actor_type TEXT, -- human | agent
actor_id TEXT, -- users.id or 'architect'
claude_session_id TEXT, -- ties an agent edit back to its reasoning session
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_thesis_revs_node ON thesis_node_revisions(node_id);
-- ============================================================================
-- thesis_versions — immutable named snapshots per line; ONE canonical per line.
-- body_json freezes the published artifact (the Architect->Scribe contract).
-- ============================================================================
CREATE TABLE IF NOT EXISTS thesis_versions (
id TEXT PRIMARY KEY, -- 'thv_' + uuid4 hex
line_id TEXT NOT NULL REFERENCES thesis_lines(id) ON DELETE CASCADE,
version_no INTEGER NOT NULL,
body_json TEXT NOT NULL, -- {throughline,pillars,claims,proof_points,segment_angles,voice,guardrails}
status TEXT NOT NULL DEFAULT 'draft', -- draft|in_review|canonical|superseded
rationale TEXT,
parent_version_id TEXT,
created_by TEXT, -- users.id or 'architect'
created_at TEXT DEFAULT (datetime('now')),
approved_at TEXT,
superseded_by TEXT
);
CREATE INDEX IF NOT EXISTS idx_thesis_versions_line ON thesis_versions(line_id, version_no);
-- Hard invariant: at most one canonical version per line.
CREATE UNIQUE INDEX IF NOT EXISTS idx_thesis_one_canonical ON thesis_versions(line_id) WHERE status = 'canonical';
-- ============================================================================
-- thesis_reviews — dual approval + collaborative text feedback. Both partners
-- can comment/approve; the Architect ingests feedback to iterate. Promotion to
-- canonical happens when distinct 'approve' reviews meet the required count
-- (app_settings 'thesis_required_approvals', default 1) via the human route.
-- ============================================================================
CREATE TABLE IF NOT EXISTS thesis_reviews (
id TEXT PRIMARY KEY,
version_id TEXT NOT NULL REFERENCES thesis_versions(id) ON DELETE CASCADE,
reviewer_user_id TEXT NOT NULL, -- users.id (the human partner)
decision TEXT NOT NULL, -- approve | request_changes | comment
feedback TEXT, -- free-text the Architect reads to iterate
target_node_id TEXT, -- optional: feedback scoped to one node
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_thesis_reviews_version ON thesis_reviews(version_id);
-- ============================================================================
-- segments — versioned LP segment definitions; one active per segment_key.
-- canonical_entities.segment (Phase 0) stores the segment_key pointer.
-- ============================================================================
CREATE TABLE IF NOT EXISTS segments (
id TEXT PRIMARY KEY,
segment_key TEXT NOT NULL, -- slug
name TEXT NOT NULL,
definition TEXT,
needs_to_hear TEXT,
avoid TEXT,
version_no INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'active', -- active | retired
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_segments_one_active ON segments(segment_key) WHERE status = 'active';