Skip to content

feat(dash): change the strictness mode from the dashboard - #401

Merged
fu351 merged 1 commit into
DobermanCore:mainfrom
QY-25123:feat/dash/mode-control
Aug 24, 2026
Merged

feat(dash): change the strictness mode from the dashboard#401
fu351 merged 1 commit into
DobermanCore:mainfrom
QY-25123:feat/dash/mode-control

Conversation

@QY-25123

Copy link
Copy Markdown
Contributor

Slice

  • Repo: doberman-core
  • Feature / Slice: dashboard D6 — change the strictness mode from the dashboard
  • Plan reference: n/a (user-requested convenience feature, no plan slice)

What this PR does

Lets light/balanced/strict/paranoid be changed from the dashboard instead of only the
terminal: a change control next to the mode badge opens a small form backed by
GET/POST /api/mode (src/doberman/dash/app.py).

Both directions go through one shared gate, extracted from cli/main.py's
_apply_mode_change into doberman.policy.drift.apply_mode_change(name, repo_root, reason, *, prompter=None, establish_ok=False) — the exact function doberman mode/doberman setup now
call too, so the CLI and the dashboard can never drift out of sync on this gate:

  • Raising strictness stays frictionless (auto-approved, no prompt).
  • Lowering it requires the same possession factor as the CLI (a 2FA code if enrolled, otherwise
    the local password); with neither enrolled it fails closed.
  • Every attempt — approved or denied — is written to the same append-only policy-change ledger
    (doberman policy-history).

POST /api/mode accepts {"mode": <name>, "code"?: <str>}. Exactly like the existing
POST /api/resolve/{id}, the dash server never verifies the code itself — a new
non-interactive _ModeChangePrompter (implementing the existing Prompter protocol) carries
code through opaquely to the real gate in doberman.policy.drift, which performs the actual
verification via the existing auth modules. test_dash_app_still_never_imports_totp in the new
test file guards this the same way the D3 approve/deny tests already do for /api/resolve.

Docs (docs/SETUP.md) and CHANGELOG.md updated to describe the new control.

Tests added (run in CI)

  • tests/unit/test_dash_mode.py (16 tests): auth matrix (401s), GET /api/mode contract,
    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 mode field → 400, the
    dash-never-imports-totp structural guarantee, and the credential never appearing in the HTTP
    response body.
  • Existing tests/unit/test_cli_lowering_gate.py, test_mode_ledger.py,
    test_drift_preferences_gate.py, and the full tests/unit/test_dash_*.py suite pass
    unchanged 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)

  • 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
  • No secret, full file, or unredacted prompt logged or committed
  • Any guardrail/learning change is raise-only (no silent loosening) — lowering the mode is
    gated behind the same possession factor as the CLI; raising is unrestricted, exactly as before
  • Every BLOCK/AUTH carries reason codes + a human explanation — a denied dashboard change
    returns a 403 with an error message and is recorded in the policy-change ledger with its
    denial method
  • doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

  • No plan slice exists for this (it's a user-requested convenience feature layered onto the
    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.
  • Ran the full pytest --cov=doberman suite 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 — reproduces
    identically on a clean main checkout. Every other test, including a full run with just those
    two 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.

@QY-25123
QY-25123 force-pushed the feat/dash/mode-control branch from 0ba887a to fde88dc Compare August 18, 2026 04:32
@fu351

fu351 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

This is strong work, and you made the call I most wanted to see. You routed the dashboard mode-change through the same apply_change weaken-gate as doberman mode instead of standing up a parallel path, so the strictness moves in exactly one place and we audit it in exactly one place. The fail-closed _ModeChangePrompter and the 16-test auth matrix are what a control-plane surface needs.

Two things before it lands:

  1. main shifted under you while this sat, so there's a small conflict now: an import collision between save_message_tone and your save_mode in cli/main.py, plus a two-bullet overlap in the CHANGELOG. A rebase onto current main clears both.
  2. Because this is a control-plane weaken path, it gets a final security sign-off on our side before merge. I already went through it and found nothing wrong, so that step is policy for the risk class, not a worry about your code.

Rebase when you get a moment and I'll take it from there. Really nice work on this one.

@QY-25123
QY-25123 force-pushed the feat/dash/mode-control branch from fde88dc to 47692a9 Compare August 19, 2026 19:49
…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>
@QY-25123
QY-25123 force-pushed the feat/dash/mode-control branch from 47692a9 to 86fece4 Compare August 19, 2026 22:38
@QY-25123

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and resolved the CHANGELOG conflict — CI is green across the board. Ready for you to take the merge whenever.

@fu351

fu351 commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Merged, thanks @QY-25123! The rebase came back clean, and on second read the extraction is my favorite part: apply_mode_change living in policy/drift.py with the CLI as a one-line wrapper means the dash and CLI cannot drift apart on the weaken gate, and your no-op test asserting zero ledger rows keeps the ledger honest. Your other PRs: I landed #415 and #398 myself (details on each, your commits untouched), and #414 has one question waiting for you.

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.

2 participants