I3130 — The ledger answers how many false dones finish caught, by doctrine, with the tasks it counted
Declare one read capability in the gates family that aggregates task.refused and task.finished from this checkout's ledger: tasks accepted, tasks refused at least once before acceptance (false dones caught), refusals per doctrine, tasks refused and never accepted, and the time from first refusal to acceptance, each with the task ids behind it; project it to CLI, HTTP and MCP by majordomus generate.
BLOCKED wave 1 · p2 · implementation profile · parallel safe
Blocked. This issue cannot start until I0401 is done. The status is derived from that, not declared.
Objective
Declare one read capability in the gates family that aggregates task.refused and task.finished from this checkout's ledger: tasks accepted, tasks refused at least once before acceptance (false dones caught), refusals per doctrine, tasks refused and never accepted, and the time from first refusal to acceptance, each with the task ids behind it; project it to CLI, HTTP and MCP by majordomus generate.
Why
share/events.yaml says task.refused exists because 'a refusal that was later satisfied is a false done that was caught'; the 2026-10-13 assessment named exactly that the primary product metric. The producer exists; there is still no reader. On 2026-10-18 the primary checkout's ledger held 2 task.refused lines and the linked worktrees' ledgers held 127 (recount: `git worktree list --porcelain | awk '/^worktree /{print $2}' | while read w; do cat \"$w/.ai/local/state/ledger.jsonl\" 2>/dev/null; done | grep -c '\"event\":\"task.refused\"'`), so one checkout's ledger is not a meaningful denominator. The term 'accepted' already belongs to I0401, and the Rust side already has a ledger reader to build on. Proposed in ADR 0208 (The product's value is stated as the false dones its finish caught, beside the friction it cost) (proposed; it carries no authority until the owner accepts it). Sources: chatgpt-majordomus:c256#2, chatgpt-majordomus:c119#7.
Current state
lib/finish.sh:108 appends task.refused to $MJ_STATE_DIR/ledger.jsonl; lib/history.sh:158 prints it line by line; gates.completion judges a single task; apps/majordomus-cli/src/ledger.rs reads the ledger; no aggregate exists.
Desired state
A gates.caught (name per the registry's conventions) capability with a typed answer, absence of a ledger reported as not observed, and a unit test over a fixture ledger.
Scope
- apps/majordomus-cli/src/capability/builtin/gates.rs
- docs/EVIDENCE.md
- apps/majordomus-cli/src/ledger.rs
- docs/claims/finish-refusal-is-recorded.md
- docs/CLAIMS.yaml
- .ai/repo/project/intents
Out of scope
- Publishing the figure on the public site
- Any tracked copy of the ledger
- Preserving the ledger of a worktree removed after landing; the answer covers ledgers that exist when it runs
- Recording a finish --check forecast event (I2949's to decide)
Dependencies
What waits on this
- I3131BLOCKED A refusal answered by no change to the tree is not counted as a caught false done
- I3132BLOCKED /ui shows what finish caught beside what being caught cost, and says not observed where there is no ledger
Acceptance criteria
- Over a fixture ledger the capability returns the expected counts and task ids for accepted, caught, by-doctrine and never-accepted
- A checkout with no ledger answers not observed, never zero
- The capability is declared once and appears on every transport from majordomus generate alone
- The aggregate reads every checkout `git worktree list` reports, the primary included, and the answer names each ledger read with its task.* line count
- Not observed means no task.* event in any ledger read; a missing ledger, or one holding only other events, never reads zero
- The aggregate is built on ledger::read and Entry; no new JSONL parser is added, and corrupt lines are reported as ledger::read counts them
- Accepted uses I0401's written definition of an accepted outcome; I3130 defines none of its own
- The answer types a class for each refusal (caught, unanswered, downgraded, digest unknown) and assumes no tree-only rule; a refusal that predates the answer digest reads digest unknown
- The answer states it counts finish refusals only, and that finish --check forecasts are not counted
- docs/claims/finish-refusal-is-recorded.md and docs/CLAIMS.yaml no longer say the refusal counts nothing on its own, and a claim for the aggregate is added
- Criterion false-dones-are-counted of the intent carries evidence: test with ref apps/majordomus-cli/tests/gates_caught.rs
Validation
- bash test/run.sh 32_refusal_lifecycle
- majordomus generate --check
Evidence required
- test_gates_caught
Evidence
None recorded. Every token above needs a command or an artifact behind it before this issue can be completed; narrative is refused.
Timeline
- started
- —
- verified
- —
- completed
- —
Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.
Canonical record: .ai/repo/project/issues/I3130.yaml. Read it back with majordomus plan show I3130.