v1.1.0 — total gate transitions, control-flow taint, honest freeze
Three holes in the control model, closed together. Language stays 0.3; this is a package MINOR under ADR 0026.
Gate judging is total (SPEC §5)
A gates: list is a relation, not a function — but the fused judge was a forced choice: it had to name one of the batch's conditions even when none held, silently turning first true into best match and making a when: otherwise after a prose gate unreachable except through the unparseable-judge fallback.
- The batch is now presented with one extra option, "none of the above conditions is true" (
N+1). Anoneverdict falls through to the next gate, exactly like ahookreturning False; running off the end halts withno-gate-matched. LLM.judgegainsallow_none; adapters that predate it are detected by signature inspection and traced asjudge_forced_choice.lint:no catch-all gateandthe only when: otherwise gate is a repairare structural —lint --strictnow fails a state without a non-repairwhen: otherwise. This is the one intentional authoring break.
Control-flow taint (SPEC §6 / ADR 0030)
ADR 0025 fenced untrusted values so a model can tell data from directives. That protects the text a state reads; it said nothing about the transition a gate chooses after reading it.
- The engine tracks
external ⊆ tainted. A transition judged with any external key in scope isdecision_tainted, and the run stays flow-tainted until ahook:gate or ahuman.replyinjected for that suspension confirms it. - An effectful
tool:reached under a tainted decision is recorded asuntrusted_control_flow;--untrusted-flow haltrefuses it before the tool runs. Unclassified tools are effectful by default. The flag crossescall:into sub-runs, so one level of indirection does not launder the decision. - The policy is on every surface: CLI
run/resume,engine.run(...), the MCPrun/resumetools (stored on the session), andConsoleTools. lintreports the same shape statically, followingcall:edges when a registry is available.
Honest freeze and metrics
- SPEC §8 "What a trace attests" (normative): the trace records who decided, not why or that it was correct.
- The conformance boundary is written down: every case scripts the oracle, so conformance pins the mechanical contract and nothing about which verdict a model gives.
- ADR 0031 names the falsifiers that would force a language 0.4, with thresholds set before the data.
scripts/gate_divergence.pygained metrics that can fail (boundary corpus, gold accuracy, gate blind spot, cross/intra-provider split, paraphrase invariance);scripts/repair_convergence.pyasks whetherrepair(N)actually lifts the pass rate. No live rows yet — instrumentation, not findings.
Fixed
- Console HITL replies were written into the resumed context unfenced — the one value typed by a human answering a machine. They are now marked untrusted like every other host-injected value (ADR 0025).
- A
human.replyleft on the blackboard by an earlier HITL cycle cleared the taint of a later decision the human never saw. console/capabilities.pyrestated read-only/effectful alongsidecontrolflow.TOOL_EFFECTS; there is now one source of truth, pinned by a test.
Full detail in CHANGELOG.