Files
ten31-database/backend/test_positioning_framings.py
Keysat 196f1f6c65 thesis: seed 5 Architect positioning framings into the Workshop (v0.1.0:58)
Saves the 2026-06-05 Architect positioning pass as competing CANDIDATE options
under the core line's positioning variant group, beside Option A/B: Convergence
(47/60), Access (40), Asymmetry (36), Scarcity/chokepoints (35), Freedom-tech (28),
each with its red-team weakness inline. One-time, additive, non-canonical
(guardrail #4); idempotent via an interaction_log sentinel so a partner-deleted
option is never resurrected. ensure_positioning_framings runs after the v5 seed.
Test: test_positioning_framings.py (count/candidacy/idempotency/no-resurrection/log).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:40:25 -05:00

78 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Test the one-time Architect positioning-framings seed (thesis_seed.ensure_positioning_framings).
Verifies: the 5 framings land as CANDIDATE options under the core line's positioning
variant group (beside Option A/B); the insert is idempotent; an interaction_log sentinel
prevents resurrection after a partner soft-deletes one; and per-framing + sentinel audit
rows are written. Synthetic schema only (guardrail #9). Run: cd backend && python3 test_positioning_framings.py
"""
import os
import sqlite3
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import thesis_seed as ts # noqa: E402
SCHEMA = """
CREATE TABLE thesis_lines (id TEXT PRIMARY KEY, line_key TEXT, name TEXT, segment_key TEXT, is_core INT, description TEXT, created_at TEXT, updated_at TEXT, deleted_at TEXT);
CREATE TABLE thesis_nodes (id TEXT PRIMARY KEY, line_id TEXT, parent_id TEXT, node_type TEXT, ord REAL, title TEXT, body TEXT, status TEXT, variant_group TEXT, created_at TEXT, updated_at TEXT, deleted_at TEXT);
CREATE TABLE segments (id TEXT PRIMARY KEY, segment_key TEXT, name TEXT, definition TEXT, needs_to_hear TEXT, avoid TEXT, version_no INT, status TEXT, created_at TEXT, updated_at TEXT);
CREATE TABLE interaction_log (id TEXT PRIMARY KEY, ts TEXT, actor_type TEXT, actor_id TEXT, action TEXT, target_type TEXT, target_id TEXT, payload TEXT, source TEXT, created_at TEXT);
"""
FAILS = []
def check(cond, msg):
print((" PASS " if cond else " FAIL ") + msg)
if not cond:
FAILS.append(msg)
def positioning(conn):
return conn.execute(
"SELECT title, status FROM thesis_nodes WHERE variant_group='positioning' AND deleted_at IS NULL ORDER BY ord"
).fetchall()
def main():
db = os.path.join(tempfile.mkdtemp(), "t.db")
conn = sqlite3.connect(db)
conn.executescript(SCHEMA)
conn.commit()
ts.ensure_thesis_seed(conn) # core line + Option A/B + segments
ts.ensure_positioning_framings(conn) # + 5 Architect framings
opts = positioning(conn)
check(len(opts) == 7, f"7 positioning options after seed (A,B + 5 framings), got {len(opts)}")
check(all(s == "candidate" for _, s in opts), "every positioning option is candidate (nothing canonical)")
check(sum("Architect" in t for t, _ in opts) == 5, "exactly 5 Architect framings inserted")
ts.ensure_positioning_framings(conn) # idempotent
check(len(positioning(conn)) == 7, "second run adds nothing (idempotent)")
# a partner soft-deletes one framing; a restart must NOT resurrect it
conn.execute("UPDATE thesis_nodes SET deleted_at='x' WHERE title LIKE 'Option C%'")
conn.commit()
ts.ensure_positioning_framings(conn)
check(len(positioning(conn)) == 6, "soft-deleted framing is not resurrected on re-run (sentinel guard)")
sentinel = conn.execute("SELECT COUNT(*) FROM interaction_log WHERE action='thesis.positioning_framings_seeded'").fetchone()[0]
perframe = conn.execute("SELECT COUNT(*) FROM interaction_log WHERE action='thesis.framing_seeded'").fetchone()[0]
check(sentinel == 1, f"exactly one sentinel row (got {sentinel})")
check(perframe == 5, f"one audit row per framing (got {perframe})")
conn.close()
if FAILS:
print(f"\nFAILED ({len(FAILS)})")
for f in FAILS:
print(" - " + f)
sys.exit(1)
print("\nALL PASS (positioning framings seed)")
if __name__ == "__main__":
main()