Shay
|
a0372c951f
|
feat(adr-0036): safety-only typed refusal policy
Runtime-checkable SafetyVerdict violations now replace
ChatResponse.surface (and TurnEvent.surface on the main path) with a
deterministic typed refusal string. Ethics violations remain
audit-only.
Why safety-only: safety is the universal floor (ADR-0029,
never-swappable, fail-closed). Ethics is swappable per-deployment;
wiring ethics into refusal would let pack-swappers silently change
refusal behavior via JSON edit. Wrong coupling.
Why typed refusal (not hedge injection / not re-articulation): typed
refusal is deterministic, audit-detectable by prefix, and preserves
replayability. Hedge injection would blur surface-preferences-driven
hedging vs predicate-driven refusal. Re-articulation retry yields the
same surface (planner is deterministic; no refusal-bias hint surface
exists). Deferred to a future ADR.
Refusal contract:
- ChatResponse.surface = typed refusal string
- walk_surface + articulation_surface = unchanged (audit preserved)
- runtime._last_refusal_was_typed = True (next-turn evidence for
no_silent_correction)
- Only runtime_checkable=True violations refuse
- Stub path symmetric
Files:
- chat/refusal.py (new) — pure refusal builder + audit helpers
- chat/runtime.py — invoke build_refusal_surface after safety_verdict
- tests/test_safety_refusal.py (new) — 20 tests
- docs/decisions/ADR-0036-safety-refusal-policy.md (new)
Verification:
- 20 new tests; combined pack-layer suite 116 green
- CLI suites unchanged: smoke 67, runtime 19, cognition 121
- core eval cognition: intent 100%, versor_closure 100% (baseline)
|
2026-05-17 21:10:52 -07:00 |
|