core/docs/workbench/b4-leeway-producer-scope-2026-06-13.md
Shay 217d4ed618 docs(workbench): scope B4 leeway producer — engine-side seam + honest mapping
The leeway decision already exists at chat/runtime.py::_surface_estimate
(accrual.license is a real LicenseDecision) but is discarded — never threaded to
CognitiveTurnResult, and the workbench can't import reliability_gate. Brief lays
out: the exact producer seam, the LicenseDecision/ReachPolicy -> LeewayEvidence
mapping, two honest layers (STRICT-now / earned-APPROXIMATE), the firewall-safe
workbench mapping, constraints, a minimal additive Layer-1 first PR, and 4 open
questions.
2026-06-13 20:02:03 -07:00

8.8 KiB

B4 leeway producer — engine-side scope (2026-06-13)

Status: scoping (Shay reviews; not yet built). Predecessor: b4-leeway-feasibility-gate.md (the B4a nullable LeewayEvidence read model that renders honest absence today). This brief answers the gate's open question: what engine-owned producer populates LeewayEvidence, and where.

The finding (verified against source)

The leeway decision already exists in the serving path — it is computed and then discarded, never threaded to the turn result. That, plus the import-firewall (the workbench may not import core.reliability_gate / generate.derivation), is the entire reason B4 is blocked.

Concretely:

  • The decision is made at chat/runtime.py::_surface_estimate (≈ line 1057). At that point the runtime holds:
    • accrual.license — a real core.reliability_gate.LicenseDecision (class_name, action, checker, measured, required, ratio, licensed), produced by generate/determine/estimation_license.py::serve_license(predicate) (license_for(tally, Action.SERVE, Ceilings.default()), or None when the converse-class is absent from the ratified ledger).
    • policy — a core.response_governance.ReachPolicy (level, admissible_states, rationale, license_ratio) from govern_response(...).
    • the disclosed surface (shape_surface adds the [approximate] prefix).
  • It is discarded. The only governance residue on the response is reach_level=policy.level.value (a string). accrual.license (the class, θ, ratio) and the disclosure semantics never leave the function.
  • CognitiveTurnResult carries no governance/leeway/license field (grep: empty). So nothing downstream — including the workbench — can see it.
  • workbench/api.py::_run_chat_turn never sets leeway_evidence (grep: empty) → the workbench ChatTurnResult.leeway_evidence defaults to None → the B4a UI shows "No leeway evidence recorded."

So this is genuinely engine work: the producer must live where the LicenseDecision is made (the serving path), attach a plain record to the result, and let the workbench map it. The workbench cannot reach across the firewall to compute it.

The data is already a near-perfect match

LicenseDecision + ReachPolicy map almost one-to-one onto the B4a workbench/schemas.py::LeewayEvidence tuple (class_name, license, theta, claim_disclosure, source_digest, calibration_evidence_ref):

LeewayEvidence field Engine source
class_name LicenseDecision.class_name (the converse_class_name(predicate)); "none" when no estimate was attempted
license SERVE if a licensed Action.SERVE; PROPOSE if a licensed Action.PROPOSE; "blocked" if a decision exists but licensed == False; "unknown" if no decision (no ratified tally)
theta LicenseDecision.required (the θ ceiling — 0.99 for SERVE)
claim_disclosure "approximate" when ReachLevel.APPROXIMATE; "none" when STRICT (no latitude granted); "proposal_only" in PROPOSE contexts; "verified" reserved (see open Q3)
source_digest sha256 of the ratified ClassTally bytes the decision read — provenance of the calibration evidence
calibration_evidence_ref class_name, resolvable to the workbench Calibration subject (/calibration?inspect=<class>)

Proposed seam

  1. Engine (the producer). Add an engine-owned @dataclass(frozen=True) LeewayRecord to core/cognition/result.py (NOT the workbench schema — the engine must not import workbench). Populate it at the govern_response seam in chat/runtime.py. It carries the six fields above, derived from the LicenseDecision + ReachPolicy the runtime already computes.
  2. Result. Thread leeway: LeewayRecord | None onto CognitiveTurnResult (additive, default None), the same way versor_condition / trace_hash already ride the result.
  3. Workbench (thin mapping). workbench/api.py::_run_chat_turn maps result.leewayworkbench/schemas.py::LeewayEvidence (leeway_evidence=_leeway_from_result(result)). This is a pure projection of a plain dataclass off the result — no reliability_gate import, so the firewall holds. The journal already persists leeway_evidence; the B4a UI already renders it. No workbench schema or UI change is required — only the mapping is wired.

Two honest layers

The "engine earns the right to guess" path (APPROXIMATE) is off by default and only fires on a converse-guess estimate whose predicate-class holds a ratified SERVE license. So most served turns are STRICT. The producer should be honest at both levels:

  • Layer 1 — governance-level (every governed turn, STRICT today). Emit a truthful "no latitude granted" record: level=strict, license ∈ {blocked, unknown}, claim_disclosure=none, theta = the SERVE ceiling it would have had to clear. This immediately unblocks the B4 UI from "No leeway evidence recorded" to "STRICT governance — no latitude; fully-grounded commits only," which is itself the impressive discipline (the engine refuses to widen). Fully additive; byte-identical serving.
  • Layer 2 — earned-leeway (the real B4 story). When _surface_estimate widens to APPROXIMATE because serve_license(predicate) returned a licensed SERVE, emit the full record: real class_name, license=SERVE, theta=0.99, claim_disclosure=approximate, the ratio. This is where a reviewer sees which class earned latitude, at which θ, with the [approximate] disclosure — the B4 intent verbatim.

Non-negotiable constraints (CLAUDE.md)

  • Observational, not authorizing. The producer only reports the decision the runtime already made. It must never call license_for itself, mutate Ceilings, or alter the served surface. Ceilings stay human-set.
  • wrong == 0 untouched. STRICT stays the load-bearing default and byte-identical; the record is evidence-only. A licensed APPROXIMATE estimate is already a disclosed [approximate] surface (so a wrong estimate is a disclosed wrong, not a silent one) — the producer adds no new commit path.
  • Firewall. Engine emits a plain dataclass; the workbench maps it. The workbench gains no reliability_gate / generate.derivation import.
  • Determinism. LicenseDecision is already pure/deterministic; the source_digest must hash the ledger bytes, not a timestamp.

Minimal first PR

Layer 1 only — smallest safe slice that clears the gate:

  1. LeewayRecord dataclass on core/cognition/result.py; leeway field on CognitiveTurnResult (default None).
  2. Populate it for every governed turn at the chat/runtime.py seam (STRICT-honest; the _surface_estimate widening path fills the full record).
  3. workbench/api.py mapping result.leewayLeewayEvidence.
  4. Tests (non-vacuous): a STRICT turn yields license=blocked/unknown, claim_disclosure=none, surface unchanged; a constructed APPROXIMATE turn (licensed-class fixture) yields license=SERVE, claim_disclosure=approximate; the record never alters the served surface; source_digest reproducible.

Layer 2's earned path is exercised by the same seam; the only added cost is a fixture with a ratified SERVE-licensed converse-class to drive APPROXIMATE.

Open questions for Shay

  1. First-PR scope: Layer 1 only (unblocks the UI, fully safe, no behavior change), or Layer 1 + the Layer 2 earned-path test (needs a SERVE-licensed fixture)? Recommendation: ship Layer 1, then Layer 2 as a follow-up so the UI unblocks on a zero-behavior-change PR.
  2. Record home: LeewayRecord on CognitiveTurnResult (clean for the workbench seam) vs. a turn-event/telemetry channel? Recommendation: on the result, symmetric with versor_condition / pipeline_record.
  3. claim_disclosure for STRICT-grounded commits: "none" (no latitude was needed) vs. "verified". Recommendation: "none"VERIFIED is a RESERVED EpistemicState (canonical-comparison pass not built), so claiming "verified" leeway would over-state. level=strict already carries the "fully grounded" story.
  4. calibration_evidence_ref format: raw class_name, or a resolvable subject URL (/calibration?inspect=<class>)? Recommendation: store the class_name; let the UI build the link (keeps the engine UI-agnostic).

Why this finally moves the blocker

B4a deliberately shipped a nullable read model and gated the rest precisely because the producer is engine-side and the decision wasn't on the result. This brief shows the decision already exists at a single, well-understood seam, maps cleanly onto the existing schema, and unblocks with an additive, byte-identical Layer-1 PR. No new schema, no new UI, no firewall breach.