Skip to content

v1.1.0 — total gate transitions, control-flow taint, honest freeze

Choose a tag to compare

@gianlucamazza gianlucamazza released this 28 Jul 22:10
37a85f3

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). A none verdict falls through to the next gate, exactly like a hook returning False; running off the end halts with no-gate-matched.
  • LLM.judge gains allow_none; adapters that predate it are detected by signature inspection and traced as judge_forced_choice.
  • lint: no catch-all gate and the only when: otherwise gate is a repair are structural — lint --strict now fails a state without a non-repair when: 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 is decision_tainted, and the run stays flow-tainted until a hook: gate or a human.reply injected for that suspension confirms it.
  • An effectful tool: reached under a tainted decision is recorded as untrusted_control_flow; --untrusted-flow halt refuses it before the tool runs. Unclassified tools are effectful by default. The flag crosses call: 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 MCP run/resume tools (stored on the session), and ConsoleTools.
  • lint reports the same shape statically, following call: 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.py gained metrics that can fail (boundary corpus, gold accuracy, gate blind spot, cross/intra-provider split, paraphrase invariance); scripts/repair_convergence.py asks whether repair(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.reply left on the blackboard by an earlier HITL cycle cleared the taint of a later decision the human never saw.
  • console/capabilities.py restated read-only/effectful alongside controlflow.TOOL_EFFECTS; there is now one source of truth, pinned by a test.

Full detail in CHANGELOG.