Menu

#315 feat(cli): status --json and sectioned text output

closed
nobody
None
2026-08-10
2026-08-09
Anonymous
No

Originally created by: AshSgDe29071999

Summary

Implements doberman status --json and blank-line sectioned text so status matches the machine-readable surface of scan / doctor / log / policy-history.

--json

  • One deterministic JSON document on stdout (sort_keys=True, compact separators), same style as scan --json.
  • Shared _status_payload() so text and JSON cannot diverge on redaction.
  • Top-level keys: version, path, doberman_version, role, mode, prefs, prefs_preset, policy, twofa, password, elevations, taint, hooks, recent_decisions, missed_challenges_24h.
  • Enrollment is boolean only; elevations are id/scope/expiry; decisions are ts/verdict/reason codes.

Text view

  • Same content as before, with a blank line between each block (role, mode, prefs, policy, 2FA, password, elevations, taint, hooks, recent decisions) so the dump scans as sections.

Tests

  • test_status_json_parses_with_expected_keys — parses and has the full key set; deterministic across two runs.
  • test_status_text_has_blank_line_section_breaks — section separators present.
  • test_status_never_leaks_enrolled_secret_in_either_view — seeds a TOTP secret; asserts it never appears in text or JSON.

    :::bash
    pytest tests/unit/test_cli_status.py -q

    10 passed

    ruff check src/doberman/cli/main.py tests/unit/test_cli_status.py
    ruff format --check src/doberman/cli/main.py tests/unit/test_cli_status.py

Fixes [#258]

Related

Tickets: #258
Tickets: #298

Discussion

  • Anonymous

    Anonymous - 2026-08-10

    Originally posted by: fu351

    Clean, and the redaction is the part that matters here. Text and JSON both render from the same _status_payload(), so the two views can't drift on what they expose: enrollment is boolean straight from is_enrolled() with no path to the raw secret; elevations whitelist id/scope_glob/expires_at/single_use; decisions carry only ts/final_verdict/reason_codes; taint is keyed by fingerprint, never the raw path. The guard test actually bites — flipping twofa to leak the real TOTP secret turns test_status_never_leaks_enrolled_secret_in_either_view red. And sort_keys=True plus one echo gives a single deterministic document, matching the scan --json precedent.

    One honest NIT, not a blocker: hooks[].path prints the local filesystem path to settings.json (often a full home path) in both views. It's unchanged from the old text output and it isn't secret material, so it's fine to ship — just the one field that isn't purely abstract, if you ever want to class it.

    Merge-ready from my side. This is the third of your PRs I've read today and they've all been tidy — thanks for the steady stream.

     
  • Anonymous

    Anonymous - 2026-08-10

    Ticket changed by: fu351

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-08-10

    Originally posted by: fu351

    Merged — thanks! Airtight redaction and a single shared payload, exactly right; [#258] is now closed. As I flagged on [#298], #192 (a shared JSON-output contract across the CLI's JSON commands) is the natural next step from this --json work — it's yours if you want it.

     

    Related

    Tickets: #258
    Tickets: #298


Log in to post a comment.