Skip to content

feat(policy): consume the enforcement dial in the decision path (F10, Option A)#88

Merged
fu351 merged 1 commit into
mainfrom
feat/f10-enforcement/consume-enforcement-dial
Jul 8, 2026
Merged

feat(policy): consume the enforcement dial in the decision path (F10, Option A)#88
fu351 merged 1 commit into
mainfrom
feat/f10-enforcement/consume-enforcement-dial

Conversation

@fu351

@fu351 fu351 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Slice

  • Repo: doberman-core
  • Feature / Slice: F10 / 10.7 — enforcement-consumption wiring (Option A "monitor-floor")
  • Plan reference: doberman_core_plan.md (Feature 10) · ADR 0029

What this PR does

effective_enforcement (the ledger-verified tamper clamp, PR #86) had no callers — the enforce/monitor/off dial 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/off soften a discretionary AUTH/BLOCK to observe-only, but the objective floor stays live in every state.

  • policy/drift.py — pure acted_verdict(decision, state) + _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 / 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.pyload_enforcement + resolve_enforcement_sync (sync bridge for the host hook; zero-I/O on the enforce hot path; fails closed to enforce on any error, incl. a running loop).
  • hosthooks/claude_code.pyevaluate_pre resolves the state (after _apply_taint_floor) and dispatches on the softened verdict; monitor records the would-have (real verdict, outcome "executed"), off is silent.
  • proxy/executor.pydecide_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 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 — exhaustive acted_verdict invariants: enforce/unknown never soften; discretionary softens in monitor/off; every reason code not in the soft allowlist stays live (parametrized over the whole ReasonCode enum); mixed floor+soft stays live; taint codes (confirmed_exfil/multi_step_exfil, which are not in FLOOR_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_sync defaults + 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)

  • Contains nothing from the "not allowed" list: no enterprise/hosted code, no proprietary detection, no customer data, no secrets, no commercial-license code
  • Core still builds/tests/runs with NO enterprise package installed

Security checklist

  • Fails closed on error / uncertainty (sync bridge → enforce; unlisted reason code → enforced; unknown state → enforced)
  • No secret, full file, or unredacted prompt logged or committed
  • Any guardrail/learning change is raise-only (the dial only softens the discretionary layer; the objective floor is never lowered; a softened would-have does not train the baseline)
  • Every BLOCK/AUTH carries reason codes + a human explanation (the real Decision is preserved/recorded; never mutated)
  • doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

  • The Decision validator forbids final_verdict weaker than objective, so the code never mutates the Decision — it returns the verdict to act on and records the truthful Decision.
  • Adversarially reviewed (fresh-context security pass): the floor invariant holds; no path softens a floor/secret/exfil/error verdict. Its LOW observability notes were applied (baseline-teaching guard, off-silence, comment fix).
  • Deferred (safe — stays fully enforcing): the PostToolUse output-scan is left enforcing in all states (its blocks are the secret-egress floor). Growing _DISCRETIONARY_SOFT is a raise-only weakening — treat additions with care.

… 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>
@fu351 fu351 merged commit 208f3f1 into main Jul 8, 2026
6 checks passed
@fu351 fu351 deleted the feat/f10-enforcement/consume-enforcement-dial branch July 8, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant