Closes three audit gaps left by the ADR-0035→ADR-0038 pack-layer surface: 1. TurnVerdicts bundle (chat/verdicts.py) — frozen dataclass aggregating identity_score + safety_verdict + ethics_verdict + refusal_emitted + hedge_injected. Attached to both ChatResponse.verdicts and TurnEvent.verdicts. Fields typed as object for the same module-coupling reason as TurnEvent.safety_verdict. 2. Stub-path TurnEvent emission — _stub_response accepts optional tokens kwarg and appends a TurnEvent to turn_log when invoked from a real turn. Audit consumers can now iterate turn_log end-to-end without missing stub paths. Defensive call sites (correct() fallback) bypass the append by omitting tokens. 3. refusal_emitted / hedge_injected flags — runtime tracks whether it actually mutated the surface this turn. hedge_injected uses idempotent-on-prefix semantics (True iff the runtime ADDED a hedge, not iff a hedge happens to be present). Test-pattern note: previous "gate on rt.turn_log to detect main vs stub" pattern is now broken; updated to gate on walk_surface == _UNKNOWN_DOMAIN_SURFACE. One existing hedge-injection test gate updated accordingly. Back-compat: ADR-0035→0038 per-field accessors (response.safety_verdict, etc.) still work. New consumers should read response.verdicts. Files: - chat/verdicts.py (new) — TurnVerdicts dataclass - chat/runtime.py — _stub_response tokens kwarg + stub TurnEvent append + hedge_injected tracking + bundle construction - core/physics/identity.py — TurnEvent.verdicts: object = None - tests/test_turn_verdicts_bundle.py (new) — 16 tests - tests/test_hedge_injection.py — gate fix for stub detection - docs/decisions/ADR-0039-audit-completeness.md (new) Verification: - Combined pack-layer suite: 170 green (was 154 after ADR-0038) - CLI suites unchanged: smoke 67, runtime 19, cognition 121 - core eval cognition: intent 100%, versor_closure 100% (baseline)
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""ADR-0039 — unified per-turn verdict bundle.
|
|
|
|
Three orthogonal verdict surfaces fire on every turn:
|
|
|
|
* ``IdentityScore`` — geometric alignment against the manifold.
|
|
* ``SafetyVerdict`` — universal-floor boundary predicates.
|
|
* ``EthicsVerdict`` — deployment-commitment predicates.
|
|
|
|
Plus two remediation flags captured by the runtime:
|
|
|
|
* ``refusal_emitted`` — typed refusal replaced the surface this turn.
|
|
* ``hedge_injected`` — manifold's hedge phrase was prepended.
|
|
|
|
The bundle is a convenience aggregate. The individual fields remain
|
|
on ``ChatResponse`` and ``TurnEvent`` for back-compat with callers
|
|
written against ADR-0035 / ADR-0036; the bundle is the new single
|
|
accessor for downstream telemetry and audit consumers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TurnVerdicts:
|
|
"""Single-turn verdict bundle.
|
|
|
|
Fields are typed as ``object`` to avoid coupling this module's
|
|
consumers to the concrete packs.* and core.physics.identity
|
|
types at import time. Callers that need the concrete shape
|
|
downcast at use site — same discipline ``TurnEvent`` uses.
|
|
|
|
The two remediation flags are derived from the surface that was
|
|
actually emitted:
|
|
|
|
* ``refusal_emitted=True`` iff the response surface is a typed
|
|
refusal (ADR-0036 / ADR-0037).
|
|
* ``hedge_injected=True`` iff a hedge phrase was prepended this
|
|
turn (ADR-0038).
|
|
|
|
The two are mutually exclusive: refusal supersedes hedge.
|
|
"""
|
|
|
|
identity_score: object
|
|
safety_verdict: object
|
|
ethics_verdict: object
|
|
refusal_emitted: bool
|
|
hedge_injected: bool
|