Review gate REVISE: 10 blocked merge attempts; escalated via safety>=9 capability requirement
Task Type: recurring governance
Layer: Atlas + Senate
Priority: P80
Spec path: docs/planning/specs/wiki-citation-governance-spec.md
Related quests: external_refs_quest_spec.md — external references for wiki entities (Reactome pathways, UniProt entries, Wikipedia articles, WikiPathways, PDB, AlphaFold, ClinicalTrials.gov, arXiv/bioRxiv) now flow through the unified external_refs table with Wikipedia-style access timestamps. This governance spec continues to govern refs_json + [@key] (paper citations); non-paper refs are governed by the recurring URL-scan ingester defined in the external-refs quest.
Three recurring background tasks that continuously improve citation coverage across all SciDEX wiki pages. These run autonomously — no human required — and make incremental progress on a problem too large to solve in a single task (~9,000 wiki pages).
---
refs_json but no inline [@key] markers, and add inline citations.# 1. Find pages to enrich
pages = db.execute("""
SELECT slug, title, content_md, refs_json
FROM wiki_pages
WHERE refs_json IS NOT NULL
AND refs_json != 'null'
AND refs_json != '{}'
AND content_md NOT LIKE '%[@%'
ORDER BY word_count DESC
LIMIT 15
""").fetchall()
# 2. For each page:
for page in pages:
refs = json.loads(page['refs_json'])
content = page['content_md']
# 3. For each section/paragraph, identify claims that match a ref's topic
# Use LLM to:
# a) Read section and refs, identify where each ref applies
# b) Insert [@key] at end of relevant sentence
# c) Enrich refs with claim/excerpt if missing
# 4. Save enriched content and refs_json via tracked write helper
save_wiki_page(
db, slug=page['slug'], content_md=new_content, refs_json=new_refs,
reason="wiki citation enrichment", source="wiki_citation_enrichment.run"
)When calling LLM to add inline citations, provide:
[@key] inserted + enriched refs (add claim, excerpt where missing)Log after each pass:
[@key] insertions)claim/excerptorchestra task create \
--project SciDEX \
--title "[Atlas] Wiki citation enrichment — add inline citations to 15 pages" \
--type recurring \
--frequency every-6h \
--priority 78 \
--spec docs/planning/specs/wiki-citation-governance-spec.md \
--description "Find wiki pages with refs_json but no inline citations. Add [@key] markers to match claims to papers. Enrich refs_json with claim/excerpt fields. Process 15 pages per pass."---
artifact_links is reflected in that page's refs_json. Close the loop: if the knowledge graph says "paper X supports wiki page Y," then wiki page Y should cite paper X.# 1. Find paper→wiki links not yet in refs_json
gaps = db.execute("""
SELECT wp.slug, wp.refs_json, p.pmid, p.title, p.authors, p.year, p.journal, al.strength
FROM artifact_links al
JOIN wiki_pages wp ON al.source_artifact_id = 'wiki-' || wp.slug
JOIN papers p ON al.target_artifact_id = 'paper-' || p.pmid
WHERE al.link_type = 'cites'
AND al.strength > 0.6
AND (wp.refs_json IS NULL OR wp.refs_json NOT LIKE '%' || p.pmid || '%')
ORDER BY al.strength DESC, p.citation_count DESC
LIMIT 20
""").fetchall()
# 2. Add missing papers to refs_json
for gap in gaps:
refs = json.loads(gap['refs_json'] or '{}')
pmid = gap['pmid']
# Generate a key: first_author_year (e.g., smith2023)
key = generate_ref_key(gap['authors'], gap['year'])
refs[key] = {
"authors": gap['authors'],
"title": gap['title'],
"journal": gap['journal'],
"year": gap['year'],
"pmid": pmid,
"strength": gap['strength'],
# claim/excerpt left for citation-enrichment task to fill
}
save_wiki_page(
db, slug=gap['slug'], refs_json=refs,
reason="paper-to-wiki backlink sync", source="paper_to_wiki_backlink.run"
)
(json.dumps(refs), gap['slug']))def generate_ref_key(authors: str, year: int) -> str:
"""Generate a descriptive refs_json key from first author + year."""
if not authors:
return f"ref{year}"
first = authors.split(',')[0].split(' ')[-1].lower() # surname
first = re.sub(r'[^a-z0-9]', '', first)
return f"{first}{year}"This ensures keys like lai2001, fisher2020 rather than foxp, foxpa.
orchestra task create \
--project SciDEX \
--title "[Atlas] Paper-to-wiki backlink — add missing papers to refs_json" \
--type recurring \
--frequency every-12h \
--priority 76 \
--spec docs/planning/specs/wiki-citation-governance-spec.md \
--description "Find papers linked to wiki pages via artifact_links but missing from refs_json. Add them with proper author/year/pmid metadata. 20 gaps per pass."---
=== SciDEX Wiki Citation Coverage Report ===
Date: 2026-04-10
OVERALL:
Total wiki pages: 9,247
Pages with refs_json: 2,341 (25%)
Pages with inline citations: 187 (2%)
Pages with linked papers: 1,823 (20%)
Target coverage (80%): 1,458 pages need inline citations
TOP UNCITED PAGES (have refs_json, no inline markers):
genes-tp53 12 refs, 0 citations, 2,847 words
diseases-parkinsons 8 refs, 0 citations, 3,120 words
genes-foxp1 5 refs, 0 citations, 725 words ← pilot
genes-foxp2 5 refs, 0 citations, 861 words ← pilot
...
RECENT PROGRESS (last 7 days):
Citations added: 47
Pages enriched: 12
refs_json backfills: 83
CITATION QUALITY:
refs with 'claim' field: 412 / 1,847 (22%)
refs with 'excerpt' field: 189 / 1,847 (10%)
refs with 'figure_ref': 67 / 1,847 (4%)Write a Python script or SQL query that computes these metrics and stores a snapshot in a wiki_citation_metrics table or as a hypothesis/analysis artifact.
orchestra task create \
--project SciDEX \
--title "[Atlas] Wiki citation coverage report — daily metrics" \
--type recurring \
--frequency every-24h \
--priority 72 \
--spec docs/planning/specs/wiki-citation-governance-spec.md \
--description "Compute citation coverage metrics: pages with inline citations, pages with refs_json, coverage %. Store daily snapshot. Flag top 20 pages needing citation work."---
# Find newly evidenced hypotheses (last 24h)
new_evidence = db.execute("""
SELECT h.id, h.title, hp.pmid, hp.evidence_direction, hp.strength, hp.claim
FROM hypothesis_papers hp
JOIN hypotheses h ON hp.hypothesis_id = h.id
WHERE hp.created_at > datetime('now', '-24 hours')
AND hp.strength > 0.7
""").fetchall()
# For each piece of new evidence:
for ev in new_evidence:
# Find wiki pages topically related to this hypothesis
related_wikis = db.execute("""
SELECT wp.slug, wp.refs_json
FROM artifact_links al
JOIN wiki_pages wp ON al.target_artifact_id = 'wiki-' || wp.slug
WHERE al.source_artifact_id = 'hypothesis-' || ?
AND al.strength > 0.5
LIMIT 5
""", (ev['id'],)).fetchall()
for wiki in related_wikis:
# Add the paper to refs_json if not present
pmid = ev['pmid']
refs = json.loads(wiki['refs_json'] or '{}')
if not any(r.get('pmid') == pmid for r in refs.values()):
# fetch paper metadata and add
...
refs[key] = {pmid, title, year, authors,
"claim": ev['claim'], # reuse hypothesis claim
"strength": ev['strength']}
save_wiki_page(
db, slug=wiki['slug'], refs_json=refs,
reason="evidence backfeed from hypothesis link",
source="evidence_backfeed.run"
)orchestra task create \
--project SciDEX \
--title "[Atlas] Evidence backfeed — propagate new hypothesis evidence to wiki pages" \
--type recurring \
--frequency every-24h \
--priority 74 \
--spec docs/planning/specs/wiki-citation-governance-spec.md \
--description "Find hypotheses with newly added evidence papers (last 24h, strength>0.7). Find related wiki pages via artifact_links. Add new papers to wiki refs_json if not already cited."---
{firstauthor_surname}{year} (e.g., lai2001, fisher2020). Avoid ambiguous generic keys like foxp, foxpa.[@key] markerstools.py pubmed_search tool or direct NCBI eutils API. Rate-limit to 3 requests/second. Cache results to avoid re-fetching.Many PMIDs already in the DB are LLM hallucinations. The FOXP2 page had 5 refs with PMIDs pointing to completely unrelated papers (vascular surgery, intravitreal chemotherapy, etc.). The FOXP1 page had similar issues.
Before trusting any PMID in refs_json, always verify via esummary.fcgi that the returned title matches what you expect. The citation enrichment task should include a verification step:
def verify_pmid(pmid, expected_gene_context):
"""Return True only if paper title/journal plausibly relates to context."""
detail = fetch_pubmed_detail([pmid])
r = detail.get(str(pmid), {})
title = (r.get('title','') + ' ' + r.get('fulljournalname','')).lower()
# Reject obvious mismatches (cardiology, ophthalmology, etc.)
blocklist = ['cardiac', 'ophthalmol', 'dental', 'livestock', 'tyrosinase']
return not any(b in title for b in blocklist)lai2001 FOXP2 KE family: 11586359fisher2009 FOXP2 molecular window: 19304338vernes2008 FOXP2/CNTNAP2: 18987363enard2002 FOXP2 evolution: 12192408haesler2007 FOXP2 songbird: 18052609oroak2011 FOXP1 autism: 21572417hamdan2010 FOXP1 ID/autism: 20950788deriziotis2017 speech genome: 28781152ahmed2024 FOXP1/2 compensation: 38761373wiki_citation_metrics table with upsert logicparse_refs_json() function to safely handle list/dict refs_json formatsprocess_page() to use safe parsing instead of raw json.loads()build_citation_prompt() for type safetyrefs_json NOT LIKE '[%')parse_refs_json()parse_refs_json()git push origin HEAD:mainorchestra/task/eb11dbc7-1ea5-41d6-92bb-e0cb0690a14a merged to main (commit cf090cba)atlas/wiki-citation-governance-restore and pushed to originorchestra sync push or PR reviewfind_backlink_gaps() SQL query always returned 0 results — wp.refs_json NOT LIKE '%' || p.pmid || '%' is always FALSE for JSONB (not a text column), and empty-refs_json filter (IS NULL OR = '{}') excluded most pages since most already have non-empty refs_json->> contains operator in standard SQL)parse_refs_json() now handles already-parsed dict from psycopg JSONB; (2) removed SQL-side empty-refs_json filter entirely — fetch all paper→wiki links then filter in Python via paper_already_in_refs() which correctly checks if PMID exists in any ref entrylimit * 3 to compensate for Python-side filteringupdated_at_sql=True was causing PostgreSQL datetime('now') SQL error in save_wiki_page() — changed to False so updated_at is handled by the triggerbaf838b30 to branch orchestra/task/5eef354f-paper-to-wiki-backlink-add-missing-paperconn._conn.journal_context) is correct and workingupdated_at_sql=False) is committedjournal_context to __slots__ so db_transaction can set it_json_dumps instead of json.dumps (handles datetime serialization)NOW() instead of datetime('now') (PostgreSQL syntax)refs_json::text for LIKE on jsonb columnsSUBSTRING() instead of LIKE '[%' (PostgreSQL LIKE char class issue)LIMIT {n} instead of ? placeholder (psycopg placeholder conflict with % in same query)
insert_citations_in_content() returned content but discarded the insertion count — process_page() counted len(citations_info) (total LLM outputs) instead of actual insertions. Many LLM-returned sentences didn't exist verbatim in content, so actually_inserted=0 but citations_added=3 was reported.(modified_content, actually_inserted) tuple; process_page() uses actually_inserted for citation count[@ markers in content_md after runparse_refs_json() to handle already-parsed dict from psycopg JSONB decodefind_backlink_gaps() SQL: use refs_json::text NOT LIKE for JSONB containment check, cast 'null'/'{}' as jsonb for comparisonAGENTS.md, CLAUDE.md, the citation governance spec, alignment feedback-loop notes, and artifact-governance notes.refs_json; narrowed actionable processing to pages with non-empty object refs and DOI/PMID-backed entries.scripts/wiki_citation_enrichment.py; before running it, fixed its --dry-run flag because the prior CLI only logged dry-run mode and still called the write path.scripts/wiki_citation_enrichment.py --dry-run so dry runs no longer call save_wiki_page; also corrected refs_enriched to count newly enriched refs instead of pre-existing claim/excerpt fields.genes-gabra4, and db_write_journal stayed at 635 entries before/after.[@key] citations, and enriched 36 refs; target met (39 >= 5).genes-gabra4, genes-pon2, genes-gabra6, genes-grk6, proteins-rab3b-protein, mechanisms-gadd45g-pathological-sensor-gliosis, genes-stx12, genes-prdx6, genes-tufm, genes-dnajb5, genes-fance, genes-tnfaip3, genes-stx18, genes-abcbl.mechanisms-amyloid-cascade-hypothesis) returned 0 insertable citations because no returned sentence matched the writable prose strongly enough.db_write_journal count for citation-enrichment writes increased from 635 to 649.[@key] citations, and enriched 41 refs; target met (37 >= 5).diagnostics-bradykinesia-cbs (3), genes-fip200 (1), genes-dvl2 (2), genes-atp6v0d1 (3), proteins-htra1-protein (3), genes-atp13a4 (3), proteins-lrpprc (3), genes-bai1 (2), diseases-alsp (3), proteins-adra1b-protein (3), genes-chrm1 (2), genes-kcna7 (4), genes-ncf4 (2), proteins-pspn-protein (3).cell-types) returned 0 citations — navigation/index page with no substantive claims.{
"requirements": {
"analysis": 5,
"reasoning": 5,
"safety": 9
},
"completion_shas": [
"761ba91a8c7af4bd559b342293b9338c96d40f17"
],
"completion_shas_checked_at": "2026-04-12T20:07:27.465852+00:00",
"completion_shas_missing": [
"ac308cd7285ed71f48b2e944d13534d61ed6b9dc",
"99c5ce1b5701049f657e394ac2aeeb8e5f0e563a",
"17b760b257a6d4f28df63ccb54a3f568addef5d7",
"3a04f0a5a93beaba7191acb5ea1c9fc8fa5fa5bf",
"a7846c21f43043a17cd08c3fee9f26a5047ec91c",
"b2b05723fc44878ef73f4537a143699607d6db4a",
"b627652c5b14ae363fd7dce0ff669c906e3ae376",
"9070250c1544e7053fdb38690401a6ca329de5de",
"5267e010c877a2a06e2c3a9c00b368a8de94e07f",
"85d0e86413043974ea1b1e8e8efbfe2ccd892b3b",
"0c22abb57d001296d6936a35dbf8b599c9d442dd",
"628111811d06128aede292d137135a5821b47b02",
"69b5c1a0065ce6a4192d39043187108dd51fcca0",
"eff16ad9044dfab361566ee37c64a74eba545a65",
"35ebbc5015e7c65d45dd4041a3b7af146f25fc8e",
"664955c39922557e95f776c100c7aaa59972949a",
"f08dd736f9277f24e54463933b286062d08e4404",
"65103c0900693d2c6d4d6c31b0e412d12e8593ee",
"fefc96722975dd2efe7cf7ae276ba26ade54c88c",
"0e854bac4ece9737898ee6e25782cb5ec7d61bcb",
"c8a37f0197b35a77f2bb8f3b2fbcdd0e6c384ec9",
"2e6b13d4f4c96312f38528c80a67ade85ac960cf",
"20e1c0f218c651ca2f3a70556e9e7b7abe322104",
"3d3801bff5d78c1b80e78c0b2a018afffa7daf03",
"2fed1657e860dc38f0b3e92ba6c1f5383f2b44b0",
"f5ac59cfa8c44ed8dc13bb9ace74ba9a1aa26b49",
"1a21c3a201e69c0dafa314d1c4e4cdc58e8aff91",
"ec635098087e3c94b49cbcc1e632936ac42e3d71",
"1cf6bdb2efdec0a605b62cf38245b873050948a6",
"a24d3c821fc69cbf2634355d87ca052e8ca968dd",
"b35435fd3c8040f5a837083b9836a846c0f8e6e3",
"9b3236e1eb64bd0ba4e4377ef2e7558aed3f32fd",
"724c565f8a34821f373dbe38271c854abcd6df30",
"556d201eff45e4de2dfb239f30e6caaf3de47f24",
"3bbf827fbf5ff5e62938da7adc440aa6816fdc21",
"c68c6447a957744b8db765b89e8d3a051c0d10f8",
"01e56d551de158d94221bc71f927bab17e98a8b5",
"3e4548024af446fde5e39be4bfd5588c1076e4a6",
"215131eaeb24b21ac923287bfb51e97cf8603388",
"c234d6344b2ef7c1139662784fcd1a1a9f28c51a",
"cc33f11e282a588659e2e14d621a56889deadd79",
"9a92a8049ee6f792a2223f389b0381919f2a5997",
"9889b9d9baeb16e78938f034f6c1e40b233d70e4",
"6181e2b3617239dc511f2184eb17bdcc0aa2b928",
"e146bf1710acc4112390f533386f4b96586a29c4",
"cedd77cddcd0822e5f45be9359fb09a67801793a",
"aa4c7bf670940ba6b9f91e66559e2f51f7f997b9",
"dc7bee9184a473edc164b946e9d422a95b59f3fe",
"7c0effaf1f8625baee0aa2e3632444b3984bbc6a",
"ec6c744a4a8a08f0b58d545ebc5f39e4d8dc946b",
"194e0db2b367d25e00553118823aab8fa145cb67",
"262e38b9e21bcfe5ed36f116707b89166c8c6be1",
"c85ce285e08df1af517deb52a15aa33694d6afc5",
"da1085c7cf3bd4260ed6cd11f47f0643988367b3",
"161221456886eb22c57aa0d6dcf1bf172eb4ed6c",
"b797d4a2bb0e77e290ac6298b320c24c62f79711",
"b953a920d8b4d6260b1c511e6f420e913e7beb77",
"e73961244bcbfdd2c10594378091626feb22d0cc",
"62e716c7133d3061c3bd0ef329cb9e30770482cb",
"13df6dd1222114502e6856186120cf7a3a044b72",
"b90ac582384516980bdc094b36148c744cb7b821",
"5609b4a905eb40379330f9a0bd352b7fa0729413",
"b3f6a2f3db4ee8a7302ff8a6a2de75582278442a"
],
"_gate_retry_count": 0,
"_gate_last_decision": "REVISE",
"_gate_last_reason": "Auto-deploy blocked: Push failed: remote: Invalid username or token. Password authentication is not supported for Git operations.\nfatal: Authentication failed for 'https://github.com/SciDEX-AI/SciDEX.git/'",
"_gate_branch": "orchestra/task/6b77122a-wiki-citation-coverage-report-daily-metr",
"_gate_changed_files": [
"docs/planning/specs/wiki-citation-governance-spec.md",
"wiki_citation_coverage_report.py"
],
"_gate_diff_stat": ".../specs/wiki-citation-governance-spec.md | 22 +-\n wiki_citation_coverage_report.py | 294 +++++++++++++++++++++\n 2 files changed, 308 insertions(+), 8 deletions(-)",
"_gate_history": [
{
"ts": "2026-04-20 15:24:15",
"decision": "REVISE",
"reason": "Auto-deploy blocked: Push failed: remote: Invalid username or token. Password authentication is not supported for Git operations.\nfatal: Authentication failed for 'https://github.com/SciDEX-AI/SciDEX.git/'",
"instructions": "",
"judge_used": "",
"actor": "minimax:65",
"retry_count": 6
},
{
"ts": "2026-04-20 15:26:51",
"decision": "REVISE",
"reason": "Auto-deploy blocked: Push failed: remote: Invalid username or token. Password authentication is not supported for Git operations.\nfatal: Authentication failed for 'https://github.com/SciDEX-AI/SciDEX.git/'",
"instructions": "",
"judge_used": "",
"actor": "minimax:65",
"retry_count": 7
},
{
"ts": "2026-04-20 15:29:23",
"decision": "REVISE",
"reason": "Auto-deploy blocked: Push failed: remote: Invalid username or token. Password authentication is not supported for Git operations.\nfatal: Authentication failed for 'https://github.com/SciDEX-AI/SciDEX.git/'",
"instructions": "",
"judge_used": "",
"actor": "minimax:65",
"retry_count": 8
},
{
"ts": "2026-04-20 15:30:56",
"decision": "REVISE",
"reason": "Auto-deploy blocked: Push failed: remote: Invalid username or token. Password authentication is not supported for Git operations.\nfatal: Authentication failed for 'https://github.com/SciDEX-AI/SciDEX.git/'",
"instructions": "",
"judge_used": "",
"actor": "minimax:65",
"retry_count": 9
},
{
"ts": "2026-04-20 15:32:12",
"decision": "REVISE",
"reason": "Auto-deploy blocked: Push failed: remote: Invalid username or token. Password authentication is not supported for Git operations.\nfatal: Authentication failed for 'https://github.com/SciDEX-AI/SciDEX.git/'",
"instructions": "",
"judge_used": "",
"actor": "minimax:65",
"retry_count": 10
}
],
"_gate_escalated_at": "2026-04-20 15:32:12",
"_gate_escalated_to": "safety>=9",
"_gate_failed_workspace_path": "/home/ubuntu/scidex/.orchestra-worktrees/task-6b77122a-719d-4f88-b50d-5848157eba31",
"_gate_failed_branch": "orchestra/task/6b77122a-wiki-citation-coverage-report-daily-metr"
}