feat(policy): consume the enforcement dial in the decision path (F10, Option A)#88
Merged
Merged
Conversation
… Option A) effective_enforcement (the ledger-verified tamper clamp) had no callers, so the enforce/monitor/off dial had no effect — decide() always acted on the raised verdict. Wire it into both decision chokepoints with Option A "monitor-floor" semantics (ADR 0029): monitor/off soften a DISCRETIONARY AUTH/BLOCK to observe-only, but the objective floor stays live in every state. - policy/drift.py: pure acted_verdict() + _DISCRETIONARY_SOFT allowlist. Softens only when every reason code is in the small soft set (subset check), so any secret/exfil/taint/destructive/role/policy/trifecta/error code keeps the verdict live — fail-closed: an unlisted (or future) code is enforced by default. - config.py: load_enforcement + resolve_enforcement_sync (sync bridge for the host hook; zero-I/O on the enforce hot path; fails closed to enforce). - hosthooks/claude_code.py: evaluate_pre resolves the state (after the taint floor) and dispatches on the softened verdict; monitor records the would-have, off is silent. - proxy/executor.py: decide_and_execute dispatches on acted_verdict; a softened would-have does not teach the allowed-only baseline (poisoning-resistance) and off is silent; the post-approval TOCTOU re-check stays fully live. Tests: exhaustive acted_verdict invariants incl. every-non-soft-code-stays-live and mixed floor+soft; host-hook + executor wiring; tamper-clamp fail-closed. Adversarially reviewed — floor invariant holds. Bumps 0.13.0 -> 0.14.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice
What this PR does
effective_enforcement(the ledger-verified tamper clamp, PR #86) had no callers — theenforce/monitor/offdial had no effect,decide()always acted on the raised verdict. This wires the dial into both decision chokepoints with Option A semantics (ADR 0029):monitor/offsoften a discretionary AUTH/BLOCK to observe-only, but the objective floor stays live in every state.policy/drift.py— pureacted_verdict(decision, state)+_DISCRETIONARY_SOFTallowlist. Softens only when every reason code is in the small soft set (subset check), so any secret / exfil / taint / destructive / role / policy / trifecta / fail-closed-error code keeps the verdict live. Fail-closed by inversion: an unlisted (or future) code is enforced by default — no hand-maintained floor list to drift out of sync.config.py—load_enforcement+resolve_enforcement_sync(sync bridge for the host hook; zero-I/O on theenforcehot path; fails closed toenforceon any error, incl. a running loop).hosthooks/claude_code.py—evaluate_preresolves the state (after_apply_taint_floor) and dispatches on the softened verdict;monitorrecords the would-have (real verdict, outcome "executed"),offis silent.proxy/executor.py—decide_and_executedispatches onacted_verdict; a softened would-have does not teach the allowed-only baseline (poisoning-resistance) andoffis silent; the post-approval TOCTOU re-check is left fully live.Bumps
0.13.0 → 0.14.0. README updated (enforcement-dial section + roadmap).Tests added (run in CI)
tests/unit/test_enforcement_apply.py— exhaustiveacted_verdictinvariants: enforce/unknown never soften; discretionary softens in monitor/off; every reason code not in the soft allowlist stays live (parametrized over the wholeReasonCodeenum); mixed floor+soft stays live; taint codes (confirmed_exfil/multi_step_exfil, which are not inFLOOR_HARD_BLOCKS) stay live; empty codes fail-closed.tests/integration/test_enforcement_wiring.py— host-hook: destructive/secret floor still blocks in monitor/off; discretionary dispatch honors the dial; monitor records / off silent;resolve_enforcement_syncdefaults + tamper-clamps to enforce. Executor: softened verdict forwarded in monitor, challenged (not forwarded) in enforce.Full suite green (91% cov);
ruff+lint-imports(2/2 contracts) clean.Public-release safety (doberman-core only)
Security checklist
Edge cases covered / Deviations from plan / Risks introduced
Decisionvalidator forbidsfinal_verdictweaker than objective, so the code never mutates the Decision — it returns the verdict to act on and records the truthful Decision._DISCRETIONARY_SOFTis a raise-only weakening — treat additions with care.