feat(adr-0206): response-governance bridge scaffold (STRICT-only, inert)
Adds the first consumer of CORE's two built epistemic substrates — the decode-state taxonomy (core/epistemic_state.py) and the risk-reward reliability gate (core/reliability_gate/, ADR-0175) — beginning to close the LABEL-ONLY consumption gap they sat behind. Ships the scaffold only: - core/response_governance/policy.py — ReachLevel (STRICT < APPROXIMATE < EXTRAPOLATE < CREATIVE), ReachPolicy, govern_response (STRICT-only stub), shape_surface (STRICT = identity transform; higher levels real but unreachable in production), and the 9/5/1 EpistemicState partition. - chat/runtime.py — cognition-path seam: the response surface now flows through shape_surface(govern_response(...)). STRICT = identity, so the path is byte-identical to pre-bridge. reach_level carried on ChatResponse. - core/physics/identity.py — reach_level on TurnEvent (default "strict"). wrong==0 untouched: select_self_verified is NOT touched (ADR-0206 §5); reach_level is NOT added to the telemetry JSONL dict, so pinned lane SHAs stay byte-identical. Widening, SITUATE/FEED-BACK, the math-serving seam, and the EVIDENCED reconcile are deferred to their own PRs. Tests (tests/test_response_governance.py): STRICT-only contract over all states x license x stakes, STRICT identity, live-wiring proof (forces APPROXIMATE -> policy-sensitive surface), total+disjoint taxonomy partition. Each fails loudly under the violation it guards.
This commit is contained in:
parent
4c095c3621
commit
d5872a2e96
6 changed files with 615 additions and 3 deletions
|
|
@ -37,6 +37,7 @@ from core.epistemic_state import (
|
|||
epistemic_state_for_grounding_source,
|
||||
normative_detail_from_verdicts,
|
||||
)
|
||||
from core.response_governance import govern_response, shape_surface
|
||||
from chat.telemetry import (
|
||||
TurnEventSink,
|
||||
format_correction_event_jsonl,
|
||||
|
|
@ -447,6 +448,13 @@ class ChatResponse:
|
|||
# Comma-separated violated boundary/commitment IDs when normative
|
||||
# clearance is VIOLATED or SUPPRESSED; empty string otherwise.
|
||||
normative_detail: str = ""
|
||||
# ADR-0206 — Response Governance Bridge reach level for this turn.
|
||||
# Mirrors the TurnEvent field so callers (CLI, demos, tests) can read
|
||||
# the governed reach level from ChatResponse. Scaffold contract:
|
||||
# always "strict" (govern_response is STRICT-only until widening is
|
||||
# built). ``"strict"`` default preserves byte-identity for callers
|
||||
# that construct ChatResponse without this field.
|
||||
reach_level: str = "strict"
|
||||
# ADR-0072 (R5) — operator-visible register identity per turn.
|
||||
# Mirrors the TurnEvent fields so callers (CLI, demos, tests) can
|
||||
# read the register state from ChatResponse without re-parsing the
|
||||
|
|
@ -2335,9 +2343,24 @@ class ChatRuntime:
|
|||
refusal_emitted=refusal_emitted,
|
||||
hedge_injected=hedge_injected,
|
||||
)
|
||||
main_epistemic_state = epistemic_state_for_grounding_source(
|
||||
main_grounding_source
|
||||
).value
|
||||
main_epistemic = epistemic_state_for_grounding_source(main_grounding_source)
|
||||
main_epistemic_state = main_epistemic.value
|
||||
# ADR-0206 — Response Governance Bridge seam (cognition path).
|
||||
# govern_response is STRICT-only (scaffold), so shape_surface is the
|
||||
# identity transform and response_surface is returned verbatim —
|
||||
# byte-identical to the pre-bridge path. This is live wiring, not
|
||||
# dead code: the response surface now flows through the policy
|
||||
# consumer, and the ONLY thing keeping it strict is the STRICT
|
||||
# return value (proven by the live-wiring test). The risk-reward
|
||||
# widening pathway and the math-serving seam are deferred to their
|
||||
# own PRs (ADR-0206 §5); wrong==0 is untouched here.
|
||||
main_reach_policy = govern_response(epistemic_state=main_epistemic)
|
||||
main_reach_level = main_reach_policy.level.value
|
||||
response_surface = shape_surface(
|
||||
main_reach_policy,
|
||||
committed_surface=response_surface,
|
||||
decode_state=main_epistemic,
|
||||
)
|
||||
main_normative_clearance = clearance_from_verdicts(verdicts_bundle).value
|
||||
main_normative_detail = normative_detail_from_verdicts(verdicts_bundle)
|
||||
turn_event = TurnEvent(
|
||||
|
|
@ -2371,6 +2394,7 @@ class ChatRuntime:
|
|||
epistemic_state=main_epistemic_state,
|
||||
normative_clearance=main_normative_clearance,
|
||||
normative_detail=main_normative_detail,
|
||||
reach_level=main_reach_level,
|
||||
)
|
||||
self.turn_log.append(turn_event)
|
||||
self._emit_turn_event(turn_event)
|
||||
|
|
@ -2421,6 +2445,7 @@ class ChatRuntime:
|
|||
epistemic_state=main_epistemic_state,
|
||||
normative_clearance=main_normative_clearance,
|
||||
normative_detail=main_normative_detail,
|
||||
reach_level=main_reach_level,
|
||||
refusal_reason=refusal_surface if refusal_emitted else "",
|
||||
dispatch_trace=dispatch_trace,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -298,6 +298,15 @@ class TurnEvent:
|
|||
epistemic_state: str = "undetermined"
|
||||
normative_clearance: str = "unassessable"
|
||||
normative_detail: str = ""
|
||||
# ADR-0206 — Response Governance Bridge reach level for this turn.
|
||||
# The reach policy that governed the response surface, as a
|
||||
# lower_snake_case string mirroring core.response_governance.ReachLevel
|
||||
# without importing that module here (preserving identity.py's
|
||||
# low-coupling shared-value-type role). Scaffold contract: always
|
||||
# "strict" — govern_response emits STRICT-only until the risk-reward
|
||||
# widening loop is built (ADR-0206 §3). Default "strict" so callers
|
||||
# that omit the field stay byte-identical and conservatively governed.
|
||||
reach_level: str = "strict"
|
||||
# ADR-0153 (W-020a) — canonical SHA-256 trace hash for this turn,
|
||||
# back-stamped by ``CognitiveTurnPipeline.process`` after
|
||||
# ``compute_trace_hash`` runs. Empty string on construction;
|
||||
|
|
|
|||
47
core/response_governance/__init__.py
Normal file
47
core/response_governance/__init__.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""ADR-0206 — Response Governance Bridge (scaffold).
|
||||
|
||||
The consumption bridge that lets CORE's two *built* epistemic substrates —
|
||||
the ratified decode-state taxonomy (:mod:`core.epistemic_state`) and the
|
||||
deterministic risk-reward gate (:mod:`core.reliability_gate`) — govern how a
|
||||
response is shaped. Today that loop has **no consumer** (see ADR-0206 §1);
|
||||
this package is its first, honestly-inert beginning.
|
||||
|
||||
Scaffold contract (ADR-0206 §3):
|
||||
|
||||
- :func:`govern_response` always returns :data:`STRICT_POLICY`. The
|
||||
stakes-weighing / license-gated widening logic is *designed* in the ADR
|
||||
and intentionally **not built**.
|
||||
- :func:`shape_surface` is the IDENTITY transform at
|
||||
:attr:`ReachLevel.STRICT` — the only level emitted today — so wiring it
|
||||
into the response path is byte-identical to the pre-bridge path.
|
||||
|
||||
The seam is therefore **live wiring held strict by exactly one return
|
||||
value** (the stub's STRICT), not dead code. ``wrong == 0`` is preserved
|
||||
because nothing widens: the math serving chokepoint
|
||||
(``select_self_verified``) is untouched by this PR (deferred to its own
|
||||
PR per ADR-0206 §5).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from core.response_governance.policy import (
|
||||
ACTIVE_STATES,
|
||||
RECONCILE_STATES,
|
||||
RESERVED_STATES,
|
||||
STRICT_POLICY,
|
||||
ReachLevel,
|
||||
ReachPolicy,
|
||||
govern_response,
|
||||
shape_surface,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ACTIVE_STATES",
|
||||
"RECONCILE_STATES",
|
||||
"RESERVED_STATES",
|
||||
"STRICT_POLICY",
|
||||
"ReachLevel",
|
||||
"ReachPolicy",
|
||||
"govern_response",
|
||||
"shape_surface",
|
||||
]
|
||||
186
core/response_governance/policy.py
Normal file
186
core/response_governance/policy.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
"""ADR-0206 §2-§3 — the reach policy and its STRICT-only scaffold.
|
||||
|
||||
A :class:`ReachPolicy` is the per-response decision about *how far into
|
||||
uncertainty a response may reach* — the thing CORE today does not compute.
|
||||
The full bridge will derive it from the decode-state of the grounding and
|
||||
the reliability gate's license (ADR-0206 §1). This module ships only the
|
||||
**STRICT** end of that spectrum, wired so the seam is live but inert.
|
||||
|
||||
Two functions form the seam:
|
||||
|
||||
``govern_response``
|
||||
The decision point. Scaffold: returns :data:`STRICT_POLICY` for every
|
||||
input. This is the single return value the ``wrong == 0`` guarantee
|
||||
rests on — mutate it and the live-wiring tests diverge loudly.
|
||||
|
||||
``shape_surface``
|
||||
The consumer. At :attr:`ReachLevel.STRICT` it is the IDENTITY
|
||||
transform (returns the committed surface verbatim). The higher reach
|
||||
levels are the future widening pathway; they are *unreachable in
|
||||
production today* because ``govern_response`` never emits them, but
|
||||
they are real, policy-sensitive code so the seam is not dead.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, unique
|
||||
|
||||
from core.epistemic_state import EpistemicState
|
||||
|
||||
|
||||
@unique
|
||||
class ReachLevel(Enum):
|
||||
"""How far a governed response may reach beyond fully-grounded fact.
|
||||
|
||||
Ordered conceptually STRICT < APPROXIMATE < EXTRAPOLATE < CREATIVE.
|
||||
Only STRICT is emitted by the scaffold; the rest name the spectrum the
|
||||
bridge will earn the right to enter via the reliability gate.
|
||||
"""
|
||||
|
||||
STRICT = "strict" # commit only fully-grounded; else refuse/disclose (TODAY)
|
||||
APPROXIMATE = "approximate" # disclosed best-estimate from incomplete evidence
|
||||
EXTRAPOLATE = "extrapolate" # disclosed inference past the evidence
|
||||
CREATIVE = "creative" # disclosed imaginative / novel construction
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ReachPolicy:
|
||||
"""The per-response governance decision.
|
||||
|
||||
``admissible_states`` is the set of decode-states whose surface may be
|
||||
*committed as-is* at this level; states outside it are either refused
|
||||
(STRICT) or surfaced as a disclosed alternative (higher levels).
|
||||
``rationale`` and ``license_ratio`` keep the decision inspectable —
|
||||
the same discipline the reliability gate uses for ``LicenseDecision``.
|
||||
"""
|
||||
|
||||
level: ReachLevel
|
||||
admissible_states: frozenset[EpistemicState]
|
||||
rationale: str
|
||||
license_ratio: float = 0.0
|
||||
|
||||
|
||||
# --- Taxonomy scoping (ADR-0206 §4) -----------------------------------------
|
||||
# The 15 declared EpistemicState members, partitioned by their role in the
|
||||
# governing loop. This is the "honestly scoped, not half-dead" contract:
|
||||
# every state is either ACTIVE (produced today AND consumed by the loop),
|
||||
# RESERVED (named here, not yet produced, each tied to a capability that
|
||||
# must land first), or RECONCILE (a real drift, fixed in its own PR).
|
||||
#
|
||||
# Verified against the source tree on the ADR-0206 branch: exactly 9 states
|
||||
# are ever produced (DECODED ×6, UNDETERMINED ×3, EPISTEMIC_STATE_NEEDED ×3,
|
||||
# UNVERIFIED_POSSIBLE/UNVERIFIED_NOVEL/CONTRADICTED/AMBIGUOUS ×2,
|
||||
# EVIDENCED_INCOMPLETE/INFERRED ×1) and 6 are never produced. Of those 6,
|
||||
# five are RESERVED and one (EVIDENCED) is RECONCILE.
|
||||
|
||||
ACTIVE_STATES: frozenset[EpistemicState] = frozenset(
|
||||
{
|
||||
EpistemicState.DECODED, # fully grounded — strict-admissible
|
||||
EpistemicState.EVIDENCED_INCOMPLETE, # partial grounding — approximate+
|
||||
EpistemicState.INFERRED, # derived — extrapolate+
|
||||
EpistemicState.UNVERIFIED_POSSIBLE, # possibility — extrapolate+ (disclosed)
|
||||
EpistemicState.UNVERIFIED_NOVEL, # OOV — creative only (disclosed)
|
||||
EpistemicState.CONTRADICTED, # never committed — force refuse
|
||||
EpistemicState.AMBIGUOUS, # never committed — force refuse/clarify
|
||||
EpistemicState.UNDETERMINED, # never committed — refuse
|
||||
EpistemicState.EPISTEMIC_STATE_NEEDED, # sentinel — force situate step
|
||||
}
|
||||
)
|
||||
|
||||
# Never produced today; each unlocks only when its named capability lands.
|
||||
RESERVED_STATES: frozenset[EpistemicState] = frozenset(
|
||||
{
|
||||
EpistemicState.VERIFIED, # needs canonical-comparison pass (soundness != correctness);
|
||||
# the ONLY state that will license widening past gold.
|
||||
EpistemicState.COMPUTATIONALLY_BOUNDED, # search-budget exhausted; search.py is the near-term emitter.
|
||||
EpistemicState.SCOPE_BOUNDARY, # out-of-domain refusal; needs domain-scope detection.
|
||||
EpistemicState.PERCEIVED, # raw ingest pre-comprehension; needs a perception lane.
|
||||
EpistemicState.DECODED_UNARTICULATED, # decoded but no realizer surface; articulation-gap case.
|
||||
}
|
||||
)
|
||||
|
||||
# Declared here but never produced, while recognition/outcome.py defines its
|
||||
# OWN "evidenced" constant. Real drift — reconciled in a dedicated PR
|
||||
# (ADR-0206 §5), kept OUT of this purely-additive scaffold.
|
||||
RECONCILE_STATES: frozenset[EpistemicState] = frozenset({EpistemicState.EVIDENCED})
|
||||
|
||||
|
||||
# --- The scaffold's single governing decision -------------------------------
|
||||
|
||||
# STRICT admits only the fully-grounded decode-state; everything else is
|
||||
# refused/disclosed by the existing response path. This is informational
|
||||
# at STRICT (shape_surface short-circuits to identity) but records the
|
||||
# contract the bridge will enforce once widening is built.
|
||||
_STRICT_ADMISSIBLE: frozenset[EpistemicState] = frozenset({EpistemicState.DECODED})
|
||||
|
||||
STRICT_POLICY: ReachPolicy = ReachPolicy(
|
||||
level=ReachLevel.STRICT,
|
||||
admissible_states=_STRICT_ADMISSIBLE,
|
||||
rationale="scaffold:strict-only (ADR-0206 §3 — risk-reward widening not yet built)",
|
||||
license_ratio=0.0,
|
||||
)
|
||||
|
||||
# Disclosure prefixes for the (currently unreachable) widening levels. Real
|
||||
# code so the higher-level branch of shape_surface is genuinely
|
||||
# policy-sensitive, exercised by the live-wiring test.
|
||||
_DISCLOSURE_PREFIX: dict[ReachLevel, str] = {
|
||||
ReachLevel.APPROXIMATE: "[approximate]",
|
||||
ReachLevel.EXTRAPOLATE: "[extrapolated]",
|
||||
ReachLevel.CREATIVE: "[exploratory]",
|
||||
}
|
||||
|
||||
|
||||
def govern_response(
|
||||
*,
|
||||
epistemic_state: EpistemicState | None = None,
|
||||
license_decision: object | None = None,
|
||||
stakes: object | None = None,
|
||||
) -> ReachPolicy:
|
||||
"""Decide the reach policy for a response.
|
||||
|
||||
SCAFFOLD (ADR-0206 §3): returns :data:`STRICT_POLICY` unconditionally.
|
||||
The inputs are accepted now so the call site is the final shape, but
|
||||
the stakes-weighing and license-gated widening they will drive is
|
||||
*designed, not built*. Every response is therefore governed at STRICT
|
||||
— commit-only-when-grounded, else the existing refuse/disclose path —
|
||||
which is exactly the pre-bridge behavior.
|
||||
|
||||
This single return value is the load-bearing line for ``wrong == 0``:
|
||||
the live-wiring tests prove that the *only* thing keeping the response
|
||||
path strict is this STRICT return, not the absence of a consumer.
|
||||
"""
|
||||
return STRICT_POLICY
|
||||
|
||||
|
||||
def shape_surface(
|
||||
policy: ReachPolicy,
|
||||
*,
|
||||
committed_surface: str,
|
||||
decode_state: EpistemicState,
|
||||
disclosed_alternative: str | None = None,
|
||||
) -> str:
|
||||
"""Apply ``policy`` to a committed surface — the response-path consumer.
|
||||
|
||||
At :attr:`ReachLevel.STRICT` (the only level :func:`govern_response`
|
||||
emits today) this is the IDENTITY transform: the committed surface is
|
||||
returned verbatim, byte-identical to the pre-bridge path. This is why
|
||||
wiring the seam into :mod:`chat.runtime` changes no behavior.
|
||||
|
||||
The higher reach levels are the future widening pathway. They are
|
||||
real and policy-sensitive — a non-admissible decode-state is surfaced
|
||||
as a *disclosed alternative* rather than committed silently — so the
|
||||
seam is live wiring, not dead code. They are unreachable in production
|
||||
today only because ``govern_response`` never emits them.
|
||||
"""
|
||||
if policy.level is ReachLevel.STRICT:
|
||||
return committed_surface
|
||||
|
||||
# --- Future widening pathway (unreachable in production today) ---
|
||||
alternative = (
|
||||
committed_surface if disclosed_alternative is None else disclosed_alternative
|
||||
)
|
||||
if decode_state in policy.admissible_states:
|
||||
return alternative
|
||||
prefix = _DISCLOSURE_PREFIX.get(policy.level, "")
|
||||
return f"{prefix} {alternative}".strip() if prefix else alternative
|
||||
167
docs/decisions/ADR-0206-response-governance-bridge.md
Normal file
167
docs/decisions/ADR-0206-response-governance-bridge.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# ADR-0206 — Response Governance Bridge (scaffold)
|
||||
|
||||
- **Status:** Accepted (scaffold step; widening deferred)
|
||||
- **Date:** 2026-06-03
|
||||
- **Supersedes / relates to:** ADR-0175 (calibrated-learning reliability
|
||||
gate), the Epistemic-State program (Phase 3, `core/epistemic_state.py`)
|
||||
- **Scope of THIS PR:** purely additive — design on record + cognition-path
|
||||
seam, STRICT-only, no behavior change.
|
||||
|
||||
## Context — the consumption gap
|
||||
|
||||
CORE has two **built** epistemic substrates:
|
||||
|
||||
1. **A ratified decode-state taxonomy** — `core/epistemic_state.py`, a
|
||||
`@unique` 15-member `EpistemicState` enum plus an orthogonal
|
||||
`NormativeClearance` axis. Every chat turn is *labeled* with its
|
||||
decode-state (derived from the grounding source) and the label is
|
||||
stamped onto `TurnEvent` / `ChatResponse` and emitted to telemetry.
|
||||
|
||||
2. **A deterministic risk-reward gate** — `core/reliability_gate/`
|
||||
(ADR-0175). `license_for(class, action, ceilings)` decides whether a
|
||||
class has *earned* an action via `measured_reliability / θ_required ≥ 1`.
|
||||
|
||||
A source audit (2026-06-03) established the load-bearing fact this ADR
|
||||
addresses:
|
||||
|
||||
> **The decode-state taxonomy is LABEL-ONLY.** No consumer reads a
|
||||
> decode-state to weigh stakes, modulate response risk, or gate
|
||||
> approximation/creativity. Every read is a stamp, serialize, badge, or
|
||||
> filter. The reliability gate is likewise *explicitly not serving-wired*
|
||||
> (`core/reliability_gate/__init__.py`: "NOT wired into the serving/eval
|
||||
> path"). The loop that would let these substrates **govern response
|
||||
> behavior** has zero consumers.
|
||||
|
||||
This is the same shape as the ratify-vs-consume gap: the artifacts exist,
|
||||
the runtime does not read them to act.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce the **Response Governance Bridge** — the missing consumer that
|
||||
reads the two substrates and produces a per-response `ReachPolicy`
|
||||
governing *how far into uncertainty a response may reach*. This ADR ships
|
||||
only the **scaffold**: the design on record + a live but inert seam on the
|
||||
cognition path. The risk-reward widening logic and the math-serving seam
|
||||
are explicitly deferred (§5).
|
||||
|
||||
### §1 — The governing loop (designed; not all built)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
T[turn] --> S[SITUATE<br/>stakes/gravity]
|
||||
S --> L[LICENSE<br/>reliability_gate.license_for]
|
||||
L --> G[GOVERN<br/>govern_response → ReachPolicy]
|
||||
G --> H[SHAPE<br/>shape_surface reads policy]
|
||||
H --> D[DISCLOSE<br/>decode-state label]
|
||||
D --> F[FEED BACK<br/>outcome → ledger]
|
||||
F -. next turn .-> L
|
||||
```
|
||||
|
||||
| Step | Status |
|
||||
|------|--------|
|
||||
| SITUATE (stakes/gravity from intent + domain + decode-state) | **designed** — not built |
|
||||
| LICENSE (`reliability_gate.license_for`) | **built** — not yet called from serving |
|
||||
| GOVERN (`govern_response → ReachPolicy`) | **this PR** — stubbed to STRICT |
|
||||
| SHAPE (`shape_surface`, response path reads policy) | **this PR** — STRICT = identity |
|
||||
| DISCLOSE (decode-state label) | **built** |
|
||||
| FEED BACK (outcome → reliability ledger) | **designed** — not built |
|
||||
|
||||
Only **GOVERN + SHAPE** ship here. SITUATE and FEED-BACK are named and
|
||||
deferred to follow-ups.
|
||||
|
||||
### §2 — The reach policy
|
||||
|
||||
`core/response_governance/policy.py` defines:
|
||||
|
||||
- `ReachLevel` — `STRICT < APPROXIMATE < EXTRAPOLATE < CREATIVE`. Only
|
||||
`STRICT` is emitted today; the rest name the spectrum the bridge will
|
||||
earn entry to via the reliability gate.
|
||||
- `ReachPolicy(level, admissible_states, rationale, license_ratio)` — the
|
||||
inspectable per-response decision (same discipline as
|
||||
`reliability_gate.LicenseDecision`).
|
||||
- `govern_response(*, epistemic_state, license_decision, stakes)` — the
|
||||
decision point. **Scaffold: returns `STRICT_POLICY` for every input.**
|
||||
- `shape_surface(policy, *, committed_surface, decode_state,
|
||||
disclosed_alternative)` — the consumer. **At `STRICT` it is the IDENTITY
|
||||
transform.** Higher levels surface a *disclosed alternative* for
|
||||
non-admissible decode-states — real, policy-sensitive code so the seam is
|
||||
live, but unreachable in production because `govern_response` never emits
|
||||
them.
|
||||
|
||||
### §3 — Why STRICT-only scaffold
|
||||
|
||||
The scaffold makes the email's "in active development" claim **true the
|
||||
moment it lands** while guaranteeing `wrong == 0` is untouched:
|
||||
|
||||
- `govern_response → STRICT` is the **single load-bearing return**. The
|
||||
cognition response surface now flows through `shape_surface`, but STRICT
|
||||
is identity, so the path is **byte-identical** to pre-bridge.
|
||||
- The seam is **live wiring, not dead code**: `test_seam_is_live_wiring`
|
||||
forces `APPROXIMATE` and proves the *same consumer the production path
|
||||
calls* yields a different surface. The cognition path's strictness is
|
||||
held by exactly the STRICT return — mutate it and the proofs diverge.
|
||||
- The **math-serving chokepoint (`select_self_verified`) is not touched.**
|
||||
It is the single most load-bearing line for `wrong == 0`; after the
|
||||
ProofNode lesson (a merge silently killed a guard) it does not get its
|
||||
first edit in a scaffold PR. The deferred math seam (§5) carries its own
|
||||
widening test.
|
||||
|
||||
### §4 — Taxonomy scoping (honestly scoped, not half-dead)
|
||||
|
||||
Source-verified split of the 15 `EpistemicState` members (2026-06-03,
|
||||
including the mapping functions in `epistemic_state.py`): **9 produced, 6
|
||||
never produced.** (The earlier audit's "7 never produced" over-counted —
|
||||
its grep excluded `epistemic_state.py`, where the partial→`EVIDENCED_INCOMPLETE`
|
||||
mapping lives. `EVIDENCED_INCOMPLETE` *is* produced; corrected count is 6.)
|
||||
|
||||
| Role | Count | States | Consumed by loop as |
|
||||
|------|-------|--------|---------------------|
|
||||
| **ACTIVE** (produced + consumed) | 9 | `DECODED`, `EVIDENCED_INCOMPLETE`, `INFERRED`, `UNVERIFIED_POSSIBLE`, `UNVERIFIED_NOVEL`, `CONTRADICTED`, `AMBIGUOUS`, `UNDETERMINED`, `EPISTEMIC_STATE_NEEDED` | strict-admissible / approximate+ / extrapolate+ / creative / force-refuse / sentinel |
|
||||
| **RESERVED** (named trigger, not produced) | 5 | `VERIFIED`, `COMPUTATIONALLY_BOUNDED`, `SCOPE_BOUNDARY`, `PERCEIVED`, `DECODED_UNARTICULATED` | each gated on a capability that must land first |
|
||||
| **RECONCILE** (drift, own PR) | 1 | `EVIDENCED` | declared but never produced; `recognition/outcome.py` owns its own `"evidenced"` |
|
||||
|
||||
Reserved triggers:
|
||||
- `VERIFIED` — needs a canonical-comparison pass (the soundness ≠
|
||||
correctness gap); the **only** state that will license widening past gold.
|
||||
- `COMPUTATIONALLY_BOUNDED` — search-budget exhausted; `generate/derivation/search.py`
|
||||
is the near-term emitter.
|
||||
- `SCOPE_BOUNDARY` — out-of-domain refusal with a reason; needs domain-scope
|
||||
detection.
|
||||
- `PERCEIVED` — raw ingest pre-comprehension; needs a perception lane.
|
||||
- `DECODED_UNARTICULATED` — decoded but no realizer surface; articulation-gap.
|
||||
|
||||
The partition is enforced by `test_taxonomy_partition_is_total_and_disjoint`
|
||||
(total + pairwise-disjoint over the live enum), so adding an enum member
|
||||
without scoping it fails the build.
|
||||
|
||||
### §5 — Deferred (one kind of change per PR)
|
||||
|
||||
- **Math-serving seam** — parameterizing `select_self_verified` with a
|
||||
`ReachPolicy` (STRICT branch byte-identical) + the full math-serving
|
||||
widening test. Its own PR; smaller blast radius on the wrong==0 organ.
|
||||
- **`EVIDENCED` reconcile** — map recognition's outcome into
|
||||
`EpistemicState.EVIDENCED` at the boundary (or document the two axes). A
|
||||
real corrective defect; kept out of this purely-additive scaffold.
|
||||
- **JSONL emission of `reach_level`** — `reach_level` is carried on
|
||||
`TurnEvent` / `ChatResponse` but **not** added to the telemetry audit
|
||||
dict here, so every pinned lane SHA stays byte-identical. Emission +
|
||||
SHA re-pin is a follow-up (re-pinning a frozen gate is not "additive").
|
||||
- **SITUATE / FEED-BACK / widening** — the risk-reward logic itself.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Honest claim unlocked:** decode-state taxonomy + risk-reward gate =
|
||||
*built*; the bridge that lets them govern response reach = *in active
|
||||
development* (committed seam, STRICT-only, design on record, widening
|
||||
gated behind wrong==0 proofs). Never "live."
|
||||
- **`wrong == 0` preserved:** STRICT identity + untouched
|
||||
`select_self_verified`; serving metric stays `3/47/0`.
|
||||
- **No frozen gate touched:** telemetry JSONL byte-identical → all pinned
|
||||
lane SHAs green.
|
||||
|
||||
## Verification
|
||||
|
||||
- `tests/test_response_governance.py` — STRICT-only contract, STRICT
|
||||
identity, **live-wiring proof (cognition path)**, taxonomy partition.
|
||||
- Full-surface suite green (doctrine: not smoke).
|
||||
- `scripts/verify_lane_shas.py` green; `3/47/0` preserved.
|
||||
178
tests/test_response_governance.py
Normal file
178
tests/test_response_governance.py
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
"""ADR-0206 — Response Governance Bridge scaffold tests.
|
||||
|
||||
These are the schema-defined proof obligations for the scaffold (CLAUDE.md
|
||||
"Schema-Defined Proof Obligations"). Each test is written so it would
|
||||
**meaningfully fail** if the property it guards were silently broken:
|
||||
|
||||
- ``test_govern_response_is_strict_only`` fails if the stub ever emits a
|
||||
non-STRICT policy.
|
||||
- ``test_strict_is_identity`` fails if STRICT stops being byte-identical.
|
||||
- ``test_seam_is_live_wiring`` fails if ``shape_surface`` stops being
|
||||
policy-sensitive — i.e. if the seam became dead code that ignores its
|
||||
policy. This is the proof that the cognition seam is live wiring held
|
||||
strict by exactly the STRICT return, NOT dead code.
|
||||
- ``test_taxonomy_partition_is_total_and_disjoint`` fails if the 9/5/1
|
||||
scoping drifts from the actual EpistemicState enum.
|
||||
|
||||
No test here imports ``generate.derivation.verify`` — the math-serving
|
||||
chokepoint (``select_self_verified``) is untouched by this PR (ADR-0206 §5).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.epistemic_state import EpistemicState
|
||||
from core.response_governance import (
|
||||
ACTIVE_STATES,
|
||||
RECONCILE_STATES,
|
||||
RESERVED_STATES,
|
||||
STRICT_POLICY,
|
||||
ReachLevel,
|
||||
ReachPolicy,
|
||||
govern_response,
|
||||
shape_surface,
|
||||
)
|
||||
|
||||
# A small but representative cross-product of governance inputs. The
|
||||
# scaffold must return STRICT for ALL of them.
|
||||
_ALL_STATES = tuple(EpistemicState)
|
||||
_LICENSE_STANDINS = (None, object(), {"licensed": True}, {"licensed": False})
|
||||
_STAKES_STANDINS = (None, "high", "low", 0.0, 1.0)
|
||||
|
||||
|
||||
# --- govern_response: STRICT-only contract ----------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("state", _ALL_STATES)
|
||||
@pytest.mark.parametrize("license_decision", _LICENSE_STANDINS)
|
||||
@pytest.mark.parametrize("stakes", _STAKES_STANDINS)
|
||||
def test_govern_response_is_strict_only(state, license_decision, stakes):
|
||||
"""The stub governs every input at STRICT — the wrong==0 load-bearing line.
|
||||
|
||||
If this fails, the scaffold has begun widening before the risk-reward
|
||||
loop and its proofs exist; that is the exact regression the STRICT-only
|
||||
contract forbids.
|
||||
"""
|
||||
policy = govern_response(
|
||||
epistemic_state=state, license_decision=license_decision, stakes=stakes
|
||||
)
|
||||
assert policy.level is ReachLevel.STRICT
|
||||
assert policy is STRICT_POLICY
|
||||
|
||||
|
||||
def test_strict_policy_admits_only_decoded():
|
||||
"""STRICT's admissible set is exactly {DECODED} — fully-grounded only."""
|
||||
assert STRICT_POLICY.admissible_states == frozenset({EpistemicState.DECODED})
|
||||
|
||||
|
||||
# --- shape_surface: STRICT identity + live wiring ---------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("state", _ALL_STATES)
|
||||
def test_strict_is_identity(state):
|
||||
"""At STRICT, shape_surface returns the committed surface verbatim.
|
||||
|
||||
This is the byte-identity guarantee that makes the cognition seam a
|
||||
no-op. A disclosed_alternative is supplied to prove STRICT ignores it
|
||||
(it must NOT leak into the surface at STRICT).
|
||||
"""
|
||||
committed = f"grounded answer for {state.value}"
|
||||
out = shape_surface(
|
||||
STRICT_POLICY,
|
||||
committed_surface=committed,
|
||||
decode_state=state,
|
||||
disclosed_alternative="A DIFFERENT, RISKIER STRING",
|
||||
)
|
||||
assert out == committed
|
||||
|
||||
|
||||
def test_seam_is_live_wiring():
|
||||
"""LIVE-WIRING PROOF (cognition path, no select_self_verified touched).
|
||||
|
||||
Force the governor's decision to APPROXIMATE and show the SAME consumer
|
||||
the production path calls produces a DIFFERENT surface than at STRICT
|
||||
for the same input. This proves shape_surface is genuinely
|
||||
policy-sensitive — the seam reads its policy and is not dead code — and
|
||||
therefore that the cognition path's strictness is held by exactly the
|
||||
STRICT return value, nothing else.
|
||||
"""
|
||||
committed = "grounded answer"
|
||||
alternative = "best-effort estimate"
|
||||
decode_state = EpistemicState.UNVERIFIED_POSSIBLE # not strict-admissible
|
||||
|
||||
strict_out = shape_surface(
|
||||
STRICT_POLICY,
|
||||
committed_surface=committed,
|
||||
decode_state=decode_state,
|
||||
disclosed_alternative=alternative,
|
||||
)
|
||||
|
||||
approximate_policy = ReachPolicy(
|
||||
level=ReachLevel.APPROXIMATE,
|
||||
admissible_states=frozenset({EpistemicState.DECODED}),
|
||||
rationale="test:forced-approximate",
|
||||
license_ratio=1.0,
|
||||
)
|
||||
approx_out = shape_surface(
|
||||
approximate_policy,
|
||||
committed_surface=committed,
|
||||
decode_state=decode_state,
|
||||
disclosed_alternative=alternative,
|
||||
)
|
||||
|
||||
# The consumer read the policy: STRICT committed verbatim; APPROXIMATE
|
||||
# surfaced the disclosed alternative with an epistemic prefix.
|
||||
assert strict_out == committed
|
||||
assert approx_out != strict_out
|
||||
assert alternative in approx_out
|
||||
assert approx_out.startswith("[approximate]")
|
||||
|
||||
|
||||
def test_widening_admissible_state_commits_without_disclosure():
|
||||
"""A widening level commits an admissible decode-state without a prefix.
|
||||
|
||||
Guards the branch that distinguishes "admissible at this level" (commit
|
||||
the alternative as-is) from "inadmissible" (disclose). Keeps the
|
||||
future-pathway logic honest and exercised.
|
||||
"""
|
||||
widening = ReachPolicy(
|
||||
level=ReachLevel.APPROXIMATE,
|
||||
admissible_states=frozenset({EpistemicState.EVIDENCED_INCOMPLETE}),
|
||||
rationale="test:admissible",
|
||||
)
|
||||
out = shape_surface(
|
||||
widening,
|
||||
committed_surface="committed",
|
||||
decode_state=EpistemicState.EVIDENCED_INCOMPLETE,
|
||||
disclosed_alternative="alt",
|
||||
)
|
||||
assert out == "alt" # admitted: no disclosure prefix
|
||||
|
||||
|
||||
# --- Taxonomy scoping (ADR-0206 §4) -----------------------------------------
|
||||
|
||||
|
||||
def test_taxonomy_partition_is_total_and_disjoint():
|
||||
"""The 9/5/1 partition covers every EpistemicState exactly once.
|
||||
|
||||
If a state is added to the enum without being placed in the governing
|
||||
loop's partition, this fails — keeping the "honestly scoped, not
|
||||
half-dead" contract enforced rather than asserted.
|
||||
"""
|
||||
assert len(ACTIVE_STATES) == 9
|
||||
assert len(RESERVED_STATES) == 5
|
||||
assert len(RECONCILE_STATES) == 1
|
||||
|
||||
union = ACTIVE_STATES | RESERVED_STATES | RECONCILE_STATES
|
||||
assert union == set(EpistemicState)
|
||||
|
||||
# Pairwise disjoint — no state wears two roles.
|
||||
assert not (ACTIVE_STATES & RESERVED_STATES)
|
||||
assert not (ACTIVE_STATES & RECONCILE_STATES)
|
||||
assert not (RESERVED_STATES & RECONCILE_STATES)
|
||||
|
||||
|
||||
def test_evidenced_is_the_reconcile_state():
|
||||
"""EVIDENCED is the orphaned drift (recognition owns its own copy)."""
|
||||
assert RECONCILE_STATES == frozenset({EpistemicState.EVIDENCED})
|
||||
Loading…
Reference in a new issue