feat(dash): change the strictness mode from the dashboard - #401
Conversation
0ba887a to
fde88dc
Compare
|
This is strong work, and you made the call I most wanted to see. You routed the dashboard mode-change through the same Two things before it lands:
Rebase when you get a moment and I'll take it from there. Really nice work on this one. |
fde88dc to
47692a9
Compare
…uest for in-dashboard mode control) Adds GET/POST /api/mode so light/balanced/strict/paranoid can be changed without a terminal. Both routes through a new shared doberman.policy.drift.apply_mode_change - the exact gate doberman mode/doberman setup already used (extracted from cli/main.py's _apply_mode_change so the two callers can never drift out of sync): raising strictness stays frictionless, lowering it requires the same possession factor (2FA if enrolled, else the Doberman password) and is recorded in the same append-only policy-change ledger. Exactly like /api/resolve, the dash server never verifies the code itself - it only carries it through to the existing gate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
47692a9 to
86fece4
Compare
|
Rebased onto current |
|
Merged, thanks @QY-25123! The rebase came back clean, and on second read the extraction is my favorite part: |
Slice
What this PR does
Lets
light/balanced/strict/paranoidbe changed from the dashboard instead of only theterminal: a
changecontrol next to the mode badge opens a small form backed byGET/POST /api/mode(src/doberman/dash/app.py).Both directions go through one shared gate, extracted from
cli/main.py's_apply_mode_changeintodoberman.policy.drift.apply_mode_change(name, repo_root, reason, *, prompter=None, establish_ok=False)— the exact functiondoberman mode/doberman setupnowcall too, so the CLI and the dashboard can never drift out of sync on this gate:
the local password); with neither enrolled it fails closed.
(
doberman policy-history).POST /api/modeaccepts{"mode": <name>, "code"?: <str>}. Exactly like the existingPOST /api/resolve/{id}, the dash server never verifies the code itself — a newnon-interactive
_ModeChangePrompter(implementing the existingPrompterprotocol) carriescodethrough opaquely to the real gate indoberman.policy.drift, which performs the actualverification via the existing auth modules.
test_dash_app_still_never_imports_totpin the newtest file guards this the same way the D3 approve/deny tests already do for
/api/resolve.Docs (
docs/SETUP.md) andCHANGELOG.mdupdated to describe the new control.Tests added (run in CI)
tests/unit/test_dash_mode.py(16 tests): auth matrix (401s),GET /api/modecontract,raising is frictionless and never denied, a no-op mode change skips the gate/ledger, lowering
is denied with no factor enrolled / no code supplied / a wrong TOTP code / a wrong local
credential, lowering succeeds and is ledgered correctly with a valid TOTP code and with a
valid local credential, unknown mode name → 400, missing
modefield → 400, thedash-never-imports-totp structural guarantee, and the credential never appearing in the HTTP
response body.
tests/unit/test_cli_lowering_gate.py,test_mode_ledger.py,test_drift_preferences_gate.py, and the fulltests/unit/test_dash_*.pysuite passunchanged against the refactor (the CLI's gate behavior is byte-for-byte preserved — it's now
a one-line wrapper around the shared function).
Public-release safety (doberman-core only)
Security checklist
gated behind the same possession factor as the CLI; raising is unrestricted, exactly as before
returns a 403 with an error message and is recorded in the policy-change ledger with its
denial method
Edge cases covered / Deviations from plan / Risks introduced
existing D1–D5 dashboard work), so I scoped it tightly: one new route pair, one shared
extracted function, no changes to the enforcement dial or preferences vector endpoints.
pytest --cov=dobermansuite locally; it's green modulo a pre-existing,environment-specific issue unrelated to this change: two tests (
test_gui_prompter.py,test_prompter_deadline.py) open a real Tk window and crash the interpreter(
Fatal Python error: Aborted) in this sandboxed/headless shell with no display — reproducesidentically on a clean
maincheckout. Every other test, including a full run with just thosetwo files excluded, passed up through the point I could confirm before the shell became too
slow to finish the run in a reasonable time; CI's runner should not hit either issue.