Recurring Senate CI task that fires every 30 minutes to:
scripts/orphan_checker.py and produce logs/orphan-check-latest.jsonreport_url/artifact_path where disk evidence exists (handled inside the script)python3 scripts/orphan_checker.py (auto-fixes report_url / artifact_path internally)logs/orphan-check-latest.jsonscripts/orphan_checker.py — the scanner and auto-fixerlogs/orphan-check-latest.json — latest report (not committed; generated at runtime)Run metrics:
broken_debate_fk: 13 (threshold: 10) — BREACHEDSRB-2026-04-29-hyp- (no matching analysis)SDA-2026-04-16-gap-pubmed- IDs (no matching analysis)price_history broken_fk: 0 (threshold: 50) — resolvednotebooks_hyp_in_analysis_col: 5 (non-critical)f6a96e34-f273-45e7-89ab-8753603441c6 for broken debate FK breach---
Run metrics:
broken_debate_fk: 35 (threshold: 10) — BREACHEDprice_history broken_fk: 99 (threshold: 50) — BREACHEDnotebooks_hyp_in_analysis_col: 5 (non-critical, data quality note)Investigation findings:
debate_sessions rows had analysis_id pointing to non-existent analysesanalysis_id stored a hypothesis ID (SRB-2026-04-29-hyp-*) — structural mismatch, debate was created against a hypothesis, not an analysisanalysis_id stored a knowledge_gap ID (SDA-2026-04-16-gap-pubmed-*) — same pattern, debate against a gap, not an analysisanalyses rows; none had matching knowledge_gaps rows either — all are true orphanstranscript_json is null for these sessions (they were structured research briefs, not full debates)analysis_id) applied to all 13 rowsid (session ID), question, quality_score, status, created_at, and all other columns remain intactUPDATE debate_sessions
SET analysis_id = NULL
WHERE analysis_id IS NOT NULL
AND analysis_id NOT IN (SELECT id FROM analyses);
-- 13 rows updated; 0 orphaned FKs remainVerification:
SELECT COUNT(*) FROM debate_sessions WHERE analysis_id IS NOT NULL AND analysis_id NOT IN (SELECT id FROM analyses) → 0SELECT id, analysis_id, quality_score, status WHERE id LIKE 'SRB-adv%'---
Investigation findings:
debate_sessions rows (all SRB-adv3 type, created 2026-04-28 21:26:30-07:00) had analysis_id pointing to non-existent analysesanalysis_id like SRB-2026-04-29-hyp-* — SRB-debate against hypothesis, stored as analysis_idanalysis_id like SDA-2026-04-16-gap-pubmed-* — SRB-debate against knowledge gapanalysis_id like paper-* (PMID-based) — SRB-debate against a paper
target_artifact_id + target_artifact_type = 'hypothesis' correctly pointing to the real hypothesisanalysis_id field was a stale/wrong reference — the actual subject is the hypothesis, not an analysisstructured_research_brief (SRB) debates target hypotheses directly, not analysesanalysis_id column was incorrectly populated with a hypothesis/paper/gap ID that doesn't exist as an analysistarget_artifact_id (which correctly links to the hypothesis)analysis_id for all 15 sessionstarget_artifact_id column already correctly captures what was debatedUPDATE debate_sessions
SET analysis_id = NULL
WHERE id IN (
'SRB-adv3-10-170057-a2f72fd8-20260429042630',
'SRB-adv3-9-hyp-var-600b3e39-20260429042630',
-- ... all 15 session IDs
);
-- 15 rows updatedVerification:
scripts/orphan_checker.py --once now reports broken_debate_fk: 0 (was 15)analysis_id IS NULL and target_artifact_id preservedSELECT id, analysis_id, target_artifact_id, target_artifact_type FROM debate_sessions WHERE id = 'SRB-adv3-9-hyp-var-600b3e39-20260429042630' → analysis_id= (null), target_artifact_id=h-var-600b3e39aa, target_artifact_type=hypothesis ✓docs/planning/specs/85075773-3769-482f-b822-058cac64456a_orphan_check_ci_spec.md (spec work log only — DB-only fix, no code changes)