diff --git a/AGENTS.md b/AGENTS.md index 58597af..b590668 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,7 +102,8 @@ _Phase 0 substrate + Phase 1 thesis/outreach are built; current package is **v0. - **Working (all draft-only):** CRM + ingest (chunk→embed→Qdrant + retrieval) + redaction boundary; Gmail capture (DWD) + email-activity propose→approve; Thesis Workshop + Architect (Claude) with dual-approval gate; Outreach Draft Assistant + follow-up radar + per-user voice + Tier-B in-thread Gmail draft creation. - **Deployed:** v0.1.0:74 is committed, pushed (`main` @ `aec2b77`), built, and **installed to the box** (`$START9_BOX_HOST` / immense-voyage.local now reports v0.1.0:74, up from v72). On boot, `ensure_thesis_v2_promoted` makes the v2.0 reserve-asset spine the working *approved* spine (node-level, reversible). **Unverified post-deploy:** service health after the v72→v74 migration, and the security fixes behaving live (no box CRM URL/auth on hand). - **Shipped in v0.1.0:74** (security/privacy hardening from the 2026-06-12 full-eval; report in `EVALUATION.md`): closed a pre-auth `/assets/` path traversal (could read crm.db / JWT secret / Gmail key); wired the local-Qwen NER backstop into the outreach redaction boundary (free-prose email bodies were reaching Claude with unknown names in the clear); added `deleted_at IS NULL` to every get-by-id + nested sub-select read path. Verified locally (py_compile, query exec, redaction/outreach tests, containment logic) + two reviewer passes. +- **Local verification (2026-06-12):** all documented commands run clean — `py_compile` OK, **13/13 backend tests green**, `./start.sh`/`./start_beta.sh` boot (health 200, auth 401), `make` builds the x86 s9pk (v0.1.0:74), `/assets/` traversal 404s locally (incl. URL-encoded). The 2 stale thesis tests are fixed (seed structure now documented in `docs/guides/thesis.md`). Box-only checks still open: live service health + security fixes on `$START9_BOX_HOST`. - **Decided, not yet built:** CRM as canonical thesis backbone with the signal-engine reading from it (reconciliation unwired); reply-all for Tier-B drafts (drafts currently reply to the LP only). -- **Known debt (P2, not deploy-blocking):** 2 thesis tests red vs. the v73 seed + no aggregate runner; `?limit=abc` crashes the request thread; scrub-gateway TLS verify off; `cryptography==42.0.5`; unpkg/no-SRI frontend; stale user-visible `start9/0.4/assets/ABOUT.md`; hardcoded Spark/Qdrant IPs in the s9pk; the 5.4k-line `server.py` monolith. P3 batch + full list in `EVALUATION.md`. +- **Known debt (P2, not deploy-blocking):** no aggregate test runner (the Commands `for` loop is it); `?limit=abc` crashes the request thread (authenticated list path); scrub-gateway TLS verify off; `cryptography==42.0.5`; unpkg/no-SRI frontend; stale user-visible `start9/0.4/assets/ABOUT.md`; hardcoded Spark/Qdrant IPs in the s9pk; the 5.4k-line `server.py` monolith. P3 batch + full list in `EVALUATION.md`. - **Other gaps:** the v2.0 spine is the *working* spine but **not a canonical `thesis_version`** (needs Grant + Jonathan dual sign-off); Appendix-A conviction/exposure (incl. ~40% Strike) stay Grant's working read, not canonical, not fed to the engine; live features (Claude/Qdrant/Gmail) unverified on the box. -- **Next:** 1) verify v0.1.0:74 live on the box — service health + `curl --path-as-is .../assets/../../data/crm.db` → 404; 2) clear P2 debt (start: 2 red thesis tests + aggregate runner + add traversal/soft-delete/NER regression tests); 3) Grant + Jonathan freeze v2.0 canonical; 4) build reply-all; 5) confirm Appendix-A + Maple/OpenSecret/Primal, then promote. +- **Next:** 1) verify v0.1.0:74 live on the box — service health + `curl --path-as-is .../assets/../../data/crm.db` → 404; 2) clear P2 debt (next: aggregate test runner + add traversal/soft-delete/NER regression tests; 2 stale thesis tests already realigned); 3) Grant + Jonathan freeze v2.0 canonical; 4) build reply-all; 5) confirm Appendix-A + Maple/OpenSecret/Primal, then promote. diff --git a/backend/test_thesis_actions.py b/backend/test_thesis_actions.py index b123f2e..cd62690 100644 --- a/backend/test_thesis_actions.py +++ b/backend/test_thesis_actions.py @@ -35,12 +35,14 @@ def main(): c = sqlite3.connect(db) c.row_factory = sqlite3.Row - # choose-variant: pick Option A in the positioning group -> Option B retired + # choose-variant: pick Option A in the positioning group -> every other option retired. + # The group holds Option A/B plus the 5 Architect framings (ensure_positioning_framings) = 7. opts = c.execute("SELECT id, title FROM thesis_nodes WHERE variant_group='positioning' AND deleted_at IS NULL").fetchall() - check(len(opts) == 2, f"positioning starts with 2 options (got {len(opts)})") + check(len(opts) == 7, f"positioning starts with 7 options: Option A/B + 5 Architect framings (got {len(opts)})") a = next(o for o in opts if "Option A" in (o["title"] or "")) res = at.choose_variant(a["id"], db=db) - check(res.get("retired_siblings") == 1, "choose_variant retired the 1 sibling") + expected_retired = len(opts) - 1 # 6: Option B + the 5 framings + check(res.get("retired_siblings") == expected_retired, f"choose_variant retired the {expected_retired} siblings (got {res.get('retired_siblings')})") live = c.execute("SELECT COUNT(*) FROM thesis_nodes WHERE variant_group='positioning' AND deleted_at IS NULL").fetchone()[0] check(live == 1, f"positioning now has 1 live option (got {live})") check(c.execute("SELECT status FROM thesis_nodes WHERE id=?", (a["id"],)).fetchone()[0] == "approved", diff --git a/backend/test_thesis_seed.py b/backend/test_thesis_seed.py index 72a6ce1..4e1598a 100644 --- a/backend/test_thesis_seed.py +++ b/backend/test_thesis_seed.py @@ -3,8 +3,9 @@ Runs the real init_db against a throwaway DB (applies migration 0002 and the auto-seed), then asserts the Workshop substrate: a core line, one line per segment, -the Option A/B banner as a 2-member variant group, the pillars/proof, and the -segments table — and that re-seeding is a no-op. +the positioning variant group (Option A/B banner + the 5 Architect framings seeded +by ensure_positioning_framings), the pillars/proof, and the segments table — and +that re-seeding is a no-op. Run: cd backend && python3 test_thesis_seed.py """ @@ -47,7 +48,11 @@ def main(): check("throughline" in types, "core has a throughline node") check("proof_point" in types, "core has a proof_point node") variants = [n for n in nodes if n["variant_group"] == "positioning"] - check(len(variants) == 2, f"Option A/B banner is a 2-member variant group (got {len(variants)})") + banner = [n for n in variants if (n["title"] or "").startswith(("Option A", "Option B"))] + framings = [n for n in variants if "(Architect," in (n["title"] or "")] + check(len(banner) == 2, f"Option A/B banner present in positioning group (got {len(banner)})") + check(len(framings) == 5, f"five Architect positioning framings seeded into the group (got {len(framings)})") + check(len(variants) == 7, f"positioning variant group = Option A/B + 5 framings = 7 (got {len(variants)})") pillars = [n for n in nodes if n["node_type"] == "claim" and n["title"] and n["title"][0] in "123"] check(len(pillars) == 3, f"three pillar claims (got {len(pillars)})") diff --git a/docs/guides/thesis.md b/docs/guides/thesis.md index 379d17f..5ccb4d0 100644 --- a/docs/guides/thesis.md +++ b/docs/guides/thesis.md @@ -24,5 +24,6 @@ Read this before editing thesis nodes, versions, the review flow, or the Archite ## Boot behavior - On boot, `ensure_thesis_v2_promoted` makes the v2.0 reserve-asset spine the working *approved* spine (node-level, reversible) — it does **not** freeze a canonical version. Promotion to canonical still waits on dual sign-off in the Workshop. +- **The core `positioning` variant group has 7 members, not 2.** The seed plants Option A/B, then `ensure_positioning_framings` (2026-06-05 Architect pass) additively inserts 5 competing framings (titles `Option C–G … (Architect, NN/60)`) into the same group. So `choose_variant` on any member retires the other 6. Tests (`test_thesis_seed.py`, `test_thesis_actions.py`) assert this 2+5=7 shape — keep them in sync if the framing set changes. See also `docs/thesis-handoff.md` for the current thesis content state.