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)