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>
This commit is contained in:
@@ -37,6 +37,12 @@ except Exception:
|
||||
jwt = None
|
||||
JWT_AVAILABLE = False
|
||||
|
||||
# Phase-1 Architect: human-gated thesis approval logic (pure stdlib; guarded).
|
||||
try:
|
||||
import thesis_review # type: ignore
|
||||
except Exception:
|
||||
thesis_review = None
|
||||
|
||||
# ─── Configuration ────────────────────────────────────────────────────────────
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -1731,6 +1737,16 @@ class CRMHandler(BaseHTTPRequestHandler):
|
||||
if path == '/api/audit-log':
|
||||
return self.handle_list_audit_log(user, params)
|
||||
|
||||
# ─── Architect thesis (Phase 1) ───
|
||||
if path == '/api/thesis/lines':
|
||||
return self.handle_list_thesis_lines(user)
|
||||
if path == '/api/thesis/versions':
|
||||
return self.handle_list_thesis_review_queue(user)
|
||||
if re.match(r'^/api/thesis/versions/[^/]+$', path):
|
||||
return self.handle_get_thesis_version(user, path.split('/')[-1])
|
||||
if re.match(r'^/api/thesis/[^/]+/canonical$', path):
|
||||
return self.handle_get_canonical_thesis(user, path.split('/')[-2])
|
||||
|
||||
self.send_error_json("Not found", 404)
|
||||
|
||||
def do_POST(self):
|
||||
@@ -1795,6 +1811,10 @@ class CRMHandler(BaseHTTPRequestHandler):
|
||||
if path == '/api/fundraising/backup-verify':
|
||||
return self.handle_verify_fundraising_backups(user)
|
||||
|
||||
# ─── Architect thesis review (Phase 1, human approval gate) ───
|
||||
if re.match(r'^/api/thesis/versions/[^/]+/review$', path):
|
||||
return self.handle_thesis_review(user, path.split('/')[-2], body)
|
||||
|
||||
self.send_error_json("Not found", 404)
|
||||
|
||||
def do_PUT(self):
|
||||
@@ -3408,6 +3428,41 @@ class CRMHandler(BaseHTTPRequestHandler):
|
||||
conn.close()
|
||||
return self.send_json({"message": "Tag deleted"})
|
||||
|
||||
# ─── Architect thesis (Phase 1) ───
|
||||
def handle_list_thesis_lines(self, user):
|
||||
if thesis_review is None:
|
||||
return self.send_error_json("Thesis module unavailable", 503)
|
||||
return self.send_json(thesis_review.list_lines(DB_PATH))
|
||||
|
||||
def handle_list_thesis_review_queue(self, user):
|
||||
if thesis_review is None:
|
||||
return self.send_error_json("Thesis module unavailable", 503)
|
||||
return self.send_json(thesis_review.list_versions_for_review(DB_PATH))
|
||||
|
||||
def handle_get_thesis_version(self, user, version_id):
|
||||
if thesis_review is None:
|
||||
return self.send_error_json("Thesis module unavailable", 503)
|
||||
return self.send_json(thesis_review.get_version(DB_PATH, version_id))
|
||||
|
||||
def handle_get_canonical_thesis(self, user, line_key):
|
||||
if thesis_review is None:
|
||||
return self.send_error_json("Thesis module unavailable", 503)
|
||||
return self.send_json(thesis_review.get_canonical(DB_PATH, line_key))
|
||||
|
||||
def handle_thesis_review(self, user, version_id, body):
|
||||
# Promotion to canonical is a human partner action (guardrail #4).
|
||||
if not require_admin(user):
|
||||
return self.send_error_json("Admin required", 403)
|
||||
if thesis_review is None:
|
||||
return self.send_error_json("Thesis module unavailable", 503)
|
||||
body = body or {}
|
||||
res = thesis_review.record_review(DB_PATH, version_id, user['user_id'],
|
||||
body.get('decision'), body.get('feedback'),
|
||||
body.get('target_node_id'))
|
||||
if res.get('error'):
|
||||
return self.send_error_json(res['error'], 400)
|
||||
return self.send_json({"data": res})
|
||||
|
||||
def handle_list_users(self, user):
|
||||
conn = get_db()
|
||||
users = rows_to_list(conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user