{
 "generated": "2026-09-03 11:10",
 "plan": {
  "goal": "Ship a governed engine that emits Astro-ready `index.yaml` files + a batch manifest for expedify.com, with provenance-backed facts, sampled human approval, and a measured improve loop — never deploying, and never faster than the agreed growth cap.",
  "next_step": "Phase B step 7 (facts-block retrofit, staged in tranches) is blocked on nothing technical but should wait for the first human captures so the blocks carry sources, not just leads. Dead source links: DONE 2026-09-03 — 6 URLs / 10 pages, live replacements on the same authority, staged under handoff/fix-links-2026-09-03/ (commit). Legal checklist DONE; generator grammar fixes DONE as `scripts/gen_idp_nationality_v2_1.py` (new file, v2 untouched; wrapper now uses v2.1; 19 staged pages regenerated). Next: facts-block retrofit (step 7) once the first human captures exist; Phase C blocked on the Astro schema. Done before: Phase B step 5–6: `scripts/engine/staleness.py` (demotion file changes per max-age) and `source_watch.py` + timer on the top-value lead URLs that DO fetch (home-affairs.ec.europa.eu, ica.gov.sg, gov.uk, canada.ca…). In parallel, people work: the 642-line source queue (John/Expedify) and the 26 dead source links. Previous: `scripts/engine/truth_gate.py` (deterministic gate over a page's facts: every rendered number has a fact id; no fact past max-age; money facts need an agreeing second source; captures from `data/facts/captures/` promote lead → sourced). The human source queue (`reports/source-queue-2026-09-03.md`) runs in parallel — it is people work, not engine work. Staged fixes under handoff/fix-fact-2026-09-03/ await John's decision to apply to live. Order: (a) ~~FIX-FACT the 24 convention pages~~ APPLIED 2026-09-03 via `scripts/fixes/fix_convention_statements_2026-09-03.py` (exact-match, count-guarded; 17 hubs ×4 occurrences incl. a `families[]` block the extractor did not vote on, 7 guides, Ireland verdict) → extractor: 0 disagreements, 0 self-contradictions; 2 diff-review rounds (round 1: Indonesia rows + 5 leftover FAQ sentences + last_reviewed), round 2 clean → COMMITTED + pushed 2026-09-03; (a2) for-costa-ricans + for-mexicans regenerated as non_party into `handoff/fix-fact-2026-09-03/` (STAGED, live untouched per John's new rule); root cause = idp-routes.json stale → John: do all three. DONE 2026-09-03: `scripts/fixes/build_corrected_idp_routes_2026-09-03.py` → `data/idp-routes.corrected-2026-09-03.json` (19 lcc + 20 dest re-derived, 55 routes recomputed, new `convention_required: non_party` value); `scripts/gen_idp_nationality_staged.py` (wrapper, generator unmodified) staged all 19 nationality pages under `handoff/fix-fact-2026-09-03/`; 2 review rounds (CTA slug clobber, non-party×non-party copy, stale validity/multi fields, Nepal→Indonesia name clobber — all fixed) → COMMITTED + pushed 2026-09-03. OPEN: wire the corrected routes file into the checker only after site-astro renders `non_party`; apply the 19 staged pages to live = John's call; (b) human-tier the ~100 unknown source hosts listed in the report; (c) capture script for the 172 money figures with a scoped lead; (d) human source queue for the 15 with none; (e) fix the 1 malformed literal (japan/visa/for-indians footer). source the 661 third-party money figures (550 have a tier-1/2 candidate URL on the page → capture script; 111 → human source queue). John still to send the developer requests.",
  "current_phase": "Phase B — Truth layer (plan v1.0 signed 2026-09-02; execution unfrozen; Phase B not yet started)",
  "phases": [
   {
    "id": "Phase A",
    "title": "Plan to v1.0 (no build)",
    "status": "complete",
    "started": null,
    "gate": null,
    "wire": null,
    "items": [
     {
      "done": true,
      "text": "John answers v0.4 §8 open items 1–6 — DONE 2026-09-02: files-only; legal read = Expedify (we supply claim list); cap + short post-mortem; Expedify names reviewer; request Astro read access; prototype deleted"
     },
     {
      "done": true,
      "text": "Fill v0.4 §9 numbers — DONE 2026-09-02 (cap 10/wk + 5 %; floor indexed+90d+200 impr; cohort 80/50/≤30; margin +15 % or −3 pos; review 8 min/25 pp/wk; Opus ≤20/wk; max-age 90/90/180/365; fail-closed fallbacks; 3 deferrals with triggers) — ADR 0005"
     },
     {
      "done": true,
      "text": "Measurement source — direct GSC API via service account (Ahrefs = reference only); request added to developer requests §1b"
     },
     {
      "done": false,
      "text": "Obtain Astro content-collection schema; record its field list in findings.md — moved to Phase C prerequisites (requested, pending Expedify)"
     },
     {
      "done": true,
      "text": "Client PRD aligned to v1.0 (§6 post-mortem, §7 asks incl. GSC service account)"
     },
     {
      "done": true,
      "text": "ADRs written in expedify `docs/adr/`: 0001 files-never-deploys, 0002 legal read = Expedify, 0003 cap + post-mortem, 0004 named reviewer (growth-cap numbers ADR waits on §9)"
     },
     {
      "done": true,
      "text": "Developer requests drafted: `docs/Expedify_Developer_Requests_2026-09-02.md` (repo access, reviewer, legal sign-off, release safety, footprint, cap/cohort/conversion)"
     },
     {
      "done": false,
      "text": "Send the requests to Expedify; track each as a blocked card when execution unfreezes"
     },
     {
      "done": true,
      "text": "John signs v1.0 → unfreeze (2026-09-02; file renamed to _v1.0.md)"
     }
    ],
    "done": 7,
    "total": 9
   },
   {
    "id": "Phase B",
    "title": "Truth layer (plan P1 → components C1)",
    "status": "in_progress",
    "started": "2026-09-02",
    "gate": "≥ 95 % of money figures carry a tier 1/2 source; remainder suppressed. Falsified otherwise → Phase D shrinks to sourced pages only.",
    "wire": "`index.yaml` gains a `facts:` block; gates read it; monitors feed the board.",
    "items": [
     {
      "done": true,
      "text": "Define `facts:` schema — DONE 2026-09-02 `docs/content engine/facts.schema.json` (tiers 0–3, statuses incl. not_stated, conditional source/second_source/sign_off requirements): `{id, claim_type: fee|processing_time|eligibility|convention|validity, value, unit, source_url, authority_tier: 1 govt|2 embassy|3 aggregator, captured_at, max_age_days, verification_status, second_source_url?}`; sections reference facts by `{{fact:id}}` instead of literal numbers"
     },
     {
      "done": true,
      "text": "`scripts/engine/extract_facts.py` — DONE 2026-09-02, read-only, 745 pages → 7,521 candidates in `data/facts/candidates/`, report `reports/facts-extraction-2026-09-02.md`; 5 unit tests in `tests/test_extract_facts.py`. Money gate baseline: 3,316 fee facts = 1,658 first-party + 997 not_stated + 550 with a tier-1/2 candidate on page + 111 with none → 661 third-party figures need real sourcing"
     },
     {
      "done": true,
      "text": "Independent challenge round 1 (12 findings: 3 blockers — empty convention DB, misleading gate arithmetic, tier heuristic; 7 majors) → INCORPORATED 2026-09-02: extractor v2 + schema v0.2 + `data/facts/authority_registry.json` (112 hosts) + 11 tests on real data. Gate baseline (v2.1): 187 distinct third-party money figures, 0 % captured, 92 % with a scoped lead, 8 % none. 25 convention disagreements (17 stale hubs + 8 self-contradicting driving guides)"
     },
     {
      "done": true,
      "text": "Independent challenge round 2 → 4 majors + 4 minors (incl. 2 v2 regressions) → INCORPORATED (v2.1, 16 tests, every page schema-validated)"
     },
     {
      "done": true,
      "text": "Round 3: all 8 confirmed fixed, verdict COMMIT; 3 follow-ups (money ranges, FAQ-question votes, 'in any N-day' windows, road-authority hosts) folded in → v2.2, 17 tests → COMMITTED + pushed 2026-09-02"
     },
     {
      "done": true,
      "text": "Tier the unknown source hosts — DONE 2026-09-03: 127 hosts tiered (registry 112 → 224 hosts, ccTLD map 140), 0 unknown left; leads 92 % → 94 %, no-lead 11 figures; idaoffice.org registered as related property, never a source"
     },
     {
      "done": true,
      "text": "Capture script `scripts/engine/capture_facts.py` — DONE 2026-09-03: fetches lead URLs (cached, polite, honest UA), matches value near a currency/unit marker, confidence = claim word nearby (weak matches never count). First full run: 21 hits / 546 fetches; **money gate 0 → ~2 %**; travel.state.gov + mofa.go.jp + immi.homeaffairs.gov.au block bots (403), VFS/moi.gov.ae are JS shells, most ministry leads are sections not fee pages"
     },
     {
      "done": true,
      "text": "Human source queue `scripts/engine/source_queue.py` → `reports/source-queue-2026-09-03.md` (~180 figures grouped by lead host and failure reason: value_not_on_lead_page / blocked_403 / js_or_thin_page / no_lead / weak_match_confirm)"
     },
     {
      "done": false,
      "text": "Source the candidates: convention facts from `data/idp-countries-by-convention.json` (audited 2026-08-30, tier 1 via UN treaty lists); fees/times from `data/genuine_fees_*.json` + VisaHQ matrix (tier 3 → needs tier 1/2 confirmation); everything else → human source queue card"
     },
     {
      "done": true,
      "text": "`scripts/engine/truth_gate.py` — DONE 2026-09-03 (R1 provenance, R2 second source, R3 max-age, R4 disagreement; merges captures; per-page verdict PASS/HOLD/FAIL + corpus gate). First run: corpus gate NOT YET, 4/187 money figures sourced. Remaining design gap: the 'every rendered number has a fact id' check needs the facts block retrofit (step 7)"
     },
     {
      "done": true,
      "text": "`scripts/engine/staleness.py` — DONE 2026-09-03: ages every dated claim vs §9 max-age, stages 1/2/3 (suppress figure = TODO until the {{fact:id}} retrofit / banner / noindex), stage 2-3 as staged whole-file copies under handoff/demotions-<date>/. Today: 0 stale; 271 fee facts fall due 2026-11, 762 in 2027-02, 1,527 in 2027-08 (the corpus's single last_verified date makes staleness arrive in waves — a reason to capture, not just re-date)"
     },
     {
      "done": true,
      "text": "`scripts/engine/source_watch.py` — DONE 2026-09-03: watch list = 56 lead URLs that fetch cleanly (ranked by facts backed), sentence-level diff of numeric lines vs stored snapshot, aos-notify warning on change, report per run; weekly user timer `expedify-source-watch.timer` (Agentic-OS ops/systemd). Opens no W2 card yet (W2 not built)"
     },
     {
      "done": true,
      "text": "Legal claim checklist (ADR 0002 deliverable) — DONE 2026-09-03: `reports/legal-claim-checklist-2026-09-03.{md,csv}`, 2,279 claims / 5 sections with provenance + blank sign-off columns. Expedify's reviewer signs; not yet sent"
     },
     {
      "done": false,
      "text": "Retrofit run on the corpus; commit facts blocks in tranches (≤ 50 files per PR, playbook order: visa hubs → driving guides → country hubs → tuples)"
     },
     {
      "done": false,
      "text": "Ledger: SYSTEM.md entry for the timer + scripts"
     }
    ],
    "done": 12,
    "total": 15
   },
   {
    "id": "Phase C",
    "title": "Contract + gates (plan P2 → C2, C3 single-brief, C4, C5, C9)",
    "status": "pending",
    "started": null,
    "gate": "dry-run batch builds clean on the site; applied set == manifest.",
    "wire": "brief → writer lane → gates → reviewer → sampled approval → handoff folder.",
    "items": [
     {
      "done": false,
      "text": "Write `docs/policies/corpus-admission.md` (right-to-exist test) + `scripts/engine/admit_brief.py` (mechanical parts: sibling payload check, refresh-not-new lookup, query-overlap check vs `reports/` SERP data, commercial-value flag from a per-page-type value table)"
     },
     {
      "done": false,
      "text": "Brief template `briefs/_template.yaml` with `facts_plan:` (which fact ids the page will carry and their planned sources)"
     },
     {
      "done": false,
      "text": "Create path `scripts/engine/create_page.py`: brief → registry-composed draft via luna-content (Gemma) → `validate_page.py` → `truth_gate.py` → `dedup_gate.py` (siblings + competitor corpus via `pipelines/query_competitor_corpus.py`) → `citation_gate.py` (only where the page type warrants) → `kanban_request_review` (opus-reviewer, evidence-ID contract, ≤ 3 rounds)"
     },
     {
      "done": false,
      "text": "Sampled approval `scripts/engine/approve_batch.py`: picks k files (start k = batch = 5), prints the 3 questions per file, records verdicts + time-on-page to `reviews/approvals/<batch_id>.json`; batch-size rule (double after 2 clean, halve on any rejection, any rejection rejects the batch); alarm when median review time < threshold"
     },
     {
      "done": false,
      "text": "Handoff `scripts/engine/handoff.py`: copies approved files to `handoff/<batch_id>/`, writes `manifest.json` (file, sha256, action, approver, verdicts, redirect_from), idempotent re-run; pin `manifest.schema.json`"
     },
     {
      "done": false,
      "text": "Growth-cap + audit log `scripts/engine/cap.py`: refuses a handoff that would exceed URLs/week or %-modified/week; appends every handoff to `reviews/audit-log.jsonl`"
     },
     {
      "done": false,
      "text": "Site-side §6 items 1–6 landed by Expedify's developer (schema read access, build-diff == manifest, atomic release + rollback, kill switch, deploy checks vs indexation watcher, build close-out) — track as one blocked card per item"
     },
     {
      "done": false,
      "text": "Dry run: 20-page batch through the whole path into `handoff/`, developer builds it on a preview → measures writer throughput (pages/day) for §9"
     },
     {
      "done": false,
      "text": "Ledger entries for scripts, board conventions, handoff folder"
     }
    ],
    "done": 0,
    "total": 9
   },
   {
    "id": "Phase D",
    "title": "Validation cohort (plan P3 → C6)",
    "status": "pending",
    "started": null,
    "gate": "≥ 80 % indexed @ 30 d; ≥ 50 % with ≥ 1 impression-bearing query @ 90 d; no manual action. Fail ⇒ STOP.",
    "wire": null,
    "items": [
     {
      "done": false,
      "text": "Pick 30–60 URLs (page types + hardest tuples) with John/client; record list in findings.md"
     },
     {
      "done": false,
      "text": "Measurement `scripts/engine/measure.py`: pull GSC (source per Phase A decision), URL-inspection indexation, conversion events; write `data/measure/`; per-page cost ledger from Phase C logs"
     },
     {
      "done": false,
      "text": "Per-page (not sampled) approval for every cohort file"
     },
     {
      "done": false,
      "text": "Release in tranches over several weeks; robots lifted incrementally by the developer; indexation watcher reporting to aos-notify"
     },
     {
      "done": false,
      "text": "Hold 8–12 weeks; weekly readout card"
     }
    ],
    "done": 0,
    "total": 5
   },
   {
    "id": "Phase E",
    "title": "Improve loop on the cohort (plan P4 → C7, W3, W5)",
    "status": "pending",
    "started": null,
    "gate": "refreshed beats control at 90 d by the §9 margin; else keep only FIX-FACT/TECHNICAL.",
    "wire": null,
    "items": [
     {
      "done": false,
      "text": "`scripts/engine/triage.py`: evidence floor check → deterministic typed decision (NO-ACTION default; FIX-FACT / REFRESH / CONSOLIDATE / PRUNE / REWORK-INTENT / TECHNICAL-ONLY) with a stated hypothesis and evidence IDs; cannibalization detection (same query, multiple URLs)"
     },
     {
      "done": false,
      "text": "Randomized 30–40 % held-out control; weekly site-wide change cap enforced by `cap.py`"
     },
     {
      "done": false,
      "text": "Decisions → W2/W5 cards; Opus only on ambiguous cases"
     },
     {
      "done": false,
      "text": "Optionally rebuild the diagnostic half (SERP top-3 gap analysis) from the deleted prototype's design notes in memory — only after the evidence-ID contract is generalised"
     }
    ],
    "done": 0,
    "total": 4
   },
   {
    "id": "Phase F",
    "title": "Scale, research, citation (plan P5–P7 → parameterized C3, C8, W4, W6)",
    "status": "pending",
    "started": null,
    "gate": "only if D and E passed.",
    "wire": null,
    "items": [
     {
      "done": false,
      "text": "Parameterize `create_page.py` into `build_family.py` (\"visa|IDP|passport family for [country set]\") under `cap.py` + commercial gate"
     },
     {
      "done": false,
      "text": "`scripts/engine/research.py`: source-watch events + SERP-shift detection as primary triggers → candidate briefs → `admit_brief.py`; keyword sweep secondary, low-frequency; idempotency rule vs in-flight W2 cards"
     },
     {
      "done": false,
      "text": "Citation experiment: metric + baseline on the cohort, A/B on a subset, rich-result eligibility re-verified first"
     }
    ],
    "done": 0,
    "total": 3
   }
  ],
  "key_questions": [
   {
    "resolved": true,
    "text": "~~Delivery model~~ → files-only, confirmed 2026-09-02 (ADR 0001)."
   },
   {
    "resolved": true,
    "text": "~~Measurement source~~ → direct GSC API on Expedify's property; service account requested."
   },
   {
    "resolved": true,
    "text": "~~Named reviewer~~ → Expedify names a real person (ADR 0004); name itself pending from Expedify."
   },
   {
    "resolved": false,
    "text": "Astro content-collection schema — requested 2026-09-02; which `meta` keys does the site read (incl. `last_verified`)? **PENDING EXPEDIFY**"
   },
   {
    "resolved": true,
    "text": "~~Legal read owner~~ → Expedify, we supply the claim checklist (ADR 0002)."
   },
   {
    "resolved": true,
    "text": "~~Disclosure~~ → cap presented with a short non-identifying post-mortem (ADR 0003)."
   },
   {
    "resolved": true,
    "text": "~~§9 numbers~~ → all agreed 2026-09-02 (plan §9 table). Deferred with triggers: writer throughput (dry run), cohort pick (client), manifest schema (Astro schema)."
   },
   {
    "resolved": true,
    "text": "~~v1.0 sign-off~~ → signed 2026-09-02; execution unfrozen."
   }
  ]
 },
 "progress": [
  {
   "session": "2026-09-02",
   "title": "Phase A: Plan to v1.0",
   "status": "complete",
   "actions": [
    "Audited project state; found PRD (v0.2-derived, publishes) vs v0.3 (files, no publish) conflict and v0.3 internal contradictions.",
    "John: pause all execution, solidify the plan first. Verified nothing running (no procs/timers/cron; no running/ready/review cards).",
    "Archived t_e87b5d09 (japan improve) + t_6b4a559a, t_872c6ed1, t_7f8140a6, t_54478ab4, t_714daa4d on luna-expedify; commented on parent t_d3e2c562.",
    "Wrote `docs/AI_Content_Engine_Project_Plan_v0.4.md` (components C1–C9, workflows W1–W6, site-side contract, decisions ledger, §9 blanks). Committed 78764724, pushed to expedify-hk/content-workspace main.",
    "Deleted parked prototype `pipelines/page-content-audit/` on John's instruction.",
    "Revised client PRD to v0.4 (handoff not publish; 0–7 phases; open decisions). Unversioned (outbound/ gitignored).",
    "Created these planning files (task_plan.md, findings.md, progress.md) per planning-with-files; added them to the expedify repo .gitignore.",
    "`docs/AI_Content_Engine_Project_Plan_v0.4.md` (new, committed)",
    "`~/Agentic-OS/outbound/Expedify/Expedify_AI_Content_Engine_PRD.md` (revised)",
    "`task_plan.md`, `findings.md`, `progress.md` (new, ignored)",
    "`.gitignore` (+3 planning files)",
    "`docs/adr/0001..0004-*.md`, `docs/Expedify_Developer_Requests_2026-09-02.md` (new)",
    "`docs/AI_Content_Engine_Project_Plan_v0.4.md`, `docs/Expedify_AI_Content_Engine_PRD.md` (decisions recorded)",
    "Decision session with John: v0.4 §8 items 1–5 resolved (see task_plan Decisions). Plan §8 + PRD §6/§7 updated; ADRs 0001–0004 written to `docs/adr/`; developer requests drafted.",
    "§9 session with John: all 13 lines + measurement source agreed; plan §9 rewritten as AGREED table; ADR 0005 (growth cap); dev requests §1b (GSC service account); PRD §7 item 7. Plan marked v1.0 CANDIDATE pending John's explicit sign-off.",
    "John signed v1.0 (file renamed, status line updated, ADR/PRD references bumped). Phase A complete; execution unfrozen; Phase B not started."
   ]
  },
  {
   "session": "2026-09-02",
   "title": "Phase B: Truth layer",
   "status": "in_progress",
   "actions": [
    "Discovery: mapped where facts live (verdict prose, roadRules table, pricingBox, visaTable, requirements rows); recorded in findings.md.",
    "Wrote `docs/content engine/facts.schema.json` (draft 2020-12; validated with jsonschema).",
    "Wrote `scripts/engine/extract_facts.py` (read-only) + `tests/test_extract_facts.py` (5 tests, pass).",
    "Run 1: 745 pages, 7,498 candidates; found 203 gov fees mis-tagged first_party (row text \"+ Expedify fee\") and 23 hedged guides with no convention fact. Fixed both (first-party = quote box / expedify_fee prop / amount adjacent to \"Expedify\"; hedged guide → not_stated fact). Run 2: 7,521 candidates, 1 residual mis-tag (canada/visa/for-indians CAD 185).",
    "Disagreements vs data files: 0 (content was generated from the same DBs — expected; the DBs themselves are the thing to source).",
    "Independent adversarial review (subagent, 24 tool calls): 12 findings, 3 blockers. All blockers + majors incorporated: extractor v2, schema v0.2, authority registry (112 hosts), 11 tests incl. real-data loaders + schema validation of real output. Run 3: 6,280 facts; gate 0.0 % captured / 76 % scoped lead / 24 % none; 23 real convention disagreements (13 stale country hubs + India bug etc.). Round-2 re-check dispatched.",
    "Review round 2: blockers 1/2/3/5 confirmed fixed, ids partially; 4 majors (value-bearing ids + 10 schema failures, voter tie-breaks masking inconsistent pages, fx/$0 inflating the denominator, generic-gov + dubai/schengen country scoping) + 4 minors. ALL incorporated (v2.1): value-free ids `claim_type.<subject-key>`, per-path convention votes with inconsistent pages reported as disagreement, fx inside any \"(~ …)\" group incl. \"/\"-separated, $0 by row subject, rolling windows → qualifier, generic gov `^gov.<cc>$` + `.gv.at`, country aliases (dubai→UAE, schengen→None), own-country advice → tier 1, no invented captured_at, dead code removed, unknown hosts fully listed. Tests 16 (every page validated with FormatChecker; no duplicate ids). Run 4: 187 distinct third-party money figures, 0 % captured, 92 % scoped lead, 8 % none; 25 disagreements of which 8 are self-contradicting driving guides.",
    "Review round 3: all 8 confirmed fixed; verdict commit. Folded in its 3 follow-ups + registry road hosts (lto.gov.ph, nhtsa.gov, …) + europa.eu → v2.2, 17 tests. Final run: 187 distinct third-party figures / 0 % / 92 % lead / 8 % none; 25 disagreements, 7 self-contradicting guides.",
    "Committed + pushed (expedify main) with data/facts/candidates/ gitignored as regenerable output."
   ]
  },
  {
   "session": "2026-09-03",
   "title": "Phase B step 3a — FIX-FACT convention statements",
   "status": "complete (committed + pushed 2026-09-03; round-2 diff review clean)",
   "actions": [
    "Dumped all 25 flagged pages' convention sentences via `x_votes`; found Ireland's verdict (\"either type\") that the voter treated as ambiguous.",
    "Wrote `scripts/fixes/fix_convention_statements_2026-09-03.py`: exact-match/regex replacements with per-file expected counts; dry-run ABORTED on first pass (hubs had 4 occurrences not 3; Indonesia guide 3 not 2) → located the extra occurrences (`families[]` block, requirements row note) → counts corrected → applied to 24 files with backups.",
    "Re-ran extractor (`reports/facts-extraction-2026-09-03.md`): disagreements 0, self-contradictions 0, all YAML valid, 17 tests OK. Residual-wording grep: clean.",
    "Diff review round 1: 1 blocker (Indonesia rows), 2 majors (leftover FAQ sentences; review metadata), minors (voice, script anchors). Restored the 24 backups, extended the script (Indonesia rows via non-party template, 7 FAQ edits, `last_reviewed` + `next_review`, anchored+asserted date bumps, three-sentence neither wording), re-applied. Extractor: 0/0 again; 17 tests OK; YAML valid. Round 2 dispatched."
   ]
  },
  {
   "session": "2026-09-03",
   "title": "Phase B step 3a2 — nationality pages + IDP routes (2026-09-03)",
   "status": "complete (staged, not applied to live; committed + pushed 2026-09-03 after 2 review rounds)",
   "actions": [
    "Found root cause: generator reads data/idp-routes.json, never updated after the 08-30 audit (19 lcc / 20 dest discrepancies; routes = 12 licence × 23 dest subset, 55 routes with a changed destination).",
    "John's rule: fixes to NEW files. Built `data/idp-routes.corrected-2026-09-03.json` (script clones existing route templates per status pair; authored copy for the new non-party-destination case). Wrapper `scripts/gen_idp_nationality_staged.py` stages pages from the corrected file without touching the generator or content/.",
    "Staged 19 pages; 19/19 verdicts match the corrected status. Review round 1: 8 findings (3 substantive in the routes recomputation) → all but the generator-internal ones fixed; rebuilt + restaged; round 2 found the Nepal→Indonesia name clobber in sub_names → placeholder fix + Indonesia diagonal + next_review/sources-note polish; rebuilt, restaged, verified (0 clobbers, 19/19 parse) → committed."
   ]
  },
  {
   "session": "2026-09-03",
   "title": "Progress tracker (2026-09-03)",
   "status": "complete",
   "actions": []
  },
  {
   "session": "2026-09-03",
   "title": "Phase B step 3b–4 — host tiering, capture, source queue, truth gate (2026-09-03)",
   "status": "complete",
   "actions": [
    "Tiered all 127 unknown source hosts into the registry (224 hosts, 140 ccTLD mappings; idaoffice.org = related property, never a source). Leads 92 % → 94 %; 11 figures without a lead.",
    "Wrote `scripts/engine/capture_facts.py` (fetch lead URL, visible text, value-near-marker match, cached, polite). Smoke test 0/12: 403 (immi.homeaffairs.gov.au), JS shells (punetejashtme.gov.al), and a matcher miss on mfa.am. Full run (546 fetches, 120 hosts): 21 raw hits; added confidence scoring (claim word near the value; weak matches never count) → 17 strong captures, 4 of them money figures → **gate 0 → 2.1 %**. 403 bot-blocks: travel.state.gov (80 attempts), mofa.go.jp, immi.homeaffairs.gov.au; JS shells: visa.vfsglobal.com, moi.gov.ae; 26 of our own source links return 404 (content defect).",
    "`scripts/engine/source_queue.py` → `reports/source-queue-2026-09-03.md`: 642 figures (fee+processing+stay) by lead host and reason (value_not_on_lead_page 229, blocked_403 123, no_lead 113, fetch_failed 94, js_or_thin_page 53, lead_url_404 26, weak_match_confirm 4).",
    "`scripts/engine/truth_gate.py` (R1 provenance, R2 second source, R3 max-age, R4 disagreement): 623 PASS (mostly pages with no third-party money), 122 FAIL on R1; corpus gate NOT YET (4/187).",
    "Committed + pushed."
   ]
  },
  {
   "session": "2026-09-03",
   "title": "Phase B step 5–6 — staleness + source watch (2026-09-03)",
   "status": "complete",
   "actions": []
  },
  {
   "session": "2026-09-03",
   "title": "Dead source links (2026-09-03)",
   "status": "complete (staged, committed)",
   "actions": []
  },
  {
   "session": "2026-09-03",
   "title": "Legal checklist + generator v2.1 (2026-09-03)",
   "status": "complete",
   "actions": []
  }
 ],
 "gate": {
  "file": "facts-extraction-2026-09-03.md",
  "distinct_figures": "187",
  "captured_pct": "0.0",
  "lead_pct": "94",
  "none_pct": "6",
  "disagreements": "0",
  "inconsistent": "0",
  "unknown_hosts": "0",
  "facts": "4378",
  "pages": "745"
 },
 "commits": [
  {
   "hash": "c84b78d2",
   "date": "2026-09-03",
   "subject": "engine: legal claim checklist (ADR 0002) + nationality generator v2.1 (grammar/count fixes, new file)"
  },
  {
   "hash": "4af0400f",
   "date": "2026-09-03",
   "subject": "content(staged): replace 6 dead source links (404) on 10 pages with live pages on the same authority"
  },
  {
   "hash": "e5a5be6d",
   "date": "2026-09-03",
   "subject": "engine: Phase B step 5-6 — staleness demotion plan + source watch (56 lead URLs, weekly)"
  },
  {
   "hash": "a7ceabfb",
   "date": "2026-09-03",
   "subject": "engine: Phase B step 3b-4 — host tiering, capture script, human source queue, truth gate"
  },
  {
   "hash": "8466e6d6",
   "date": "2026-09-03",
   "subject": "engine: progress tracker generator (task_plan/progress/report/git → static page + status.json, wrangler deploy)"
  },
  {
   "hash": "f3d4efd4",
   "date": "2026-09-03",
   "subject": "engine: corrected IDP routes copy + 19 idp_nationality pages staged from it (live untouched)"
  },
  {
   "hash": "ee097337",
   "date": "2026-09-03",
   "subject": "content: FIX-FACT convention statements on 24 pages (17 country hubs, 7 driving guides)"
  },
  {
   "hash": "094a5965",
   "date": "2026-09-02",
   "subject": "engine: Phase B step 1-2 — facts schema v0.3, authority registry, read-only fact extractor"
  },
  {
   "hash": "56674bff",
   "date": "2026-09-02",
   "subject": "docs: sign AI Content Engine plan v1.0 — §9 numbers agreed, growth-cap ADR 0005"
  },
  {
   "hash": "14e3a1ee",
   "date": "2026-09-02",
   "subject": "docs: close plan v0.4 §8 decisions — ADRs 0001-0004, developer requests, PRD post-mortem"
  },
  {
   "hash": "0ea1cc70",
   "date": "2026-09-02",
   "subject": "docs: client-facing AI Content Engine PRD, revised 2026-09-02 to plan v0.4"
  },
  {
   "hash": "7f487c13",
   "date": "2026-09-02",
   "subject": "chore: ignore planning-with-files working memory"
  }
 ],
 "staged": [
  {
   "batch": "fix-fact-2026-09-03",
   "files": 19,
   "examples": [
    "driving-document/for-swiss/index.yaml",
    "driving-document/for-egyptians/index.yaml",
    "driving-document/for-argentines/index.yaml",
    "driving-document/for-spaniards/index.yaml"
   ]
  }
 ]
}