Skip to content

feat(transparency): decision explainer + doberman tui (ADR 0029)#87

Merged
fu351 merged 1 commit into
mainfrom
feat/transparency/decision-explainer-tui
Jul 8, 2026
Merged

feat(transparency): decision explainer + doberman tui (ADR 0029)#87
fu351 merged 1 commit into
mainfrom
feat/transparency/decision-explainer-tui

Conversation

@fu351

@fu351 fu351 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Slice

  • Feature / Slice: DT.1 — Decision Transparency: explain helper + doberman tui
  • Plan reference: ADR 0029 (decision transparency + monitor floor)

What this PR does

Turns any redacted decision-log row into a plain-language "why", and adds a Textual TUI to browse the log with an explanation panel.

  • doberman.explaintemplate_explanation() (deterministic, offline, always available) + an opt-in Claude-Haiku narrator gated on all three of: anthropic importable, ANTHROPIC_API_KEY, DOBERMAN_EXPLAIN_LLM truthy. use_llm can only restrict (a caller can force the template but can never bypass the env opt-in). The LLM sees only an allowlist projection (REDACTED_FIELDS) of the already-redacted row — narrator, never judge. Any failure falls back to the template; the module never raises.
  • doberman tui — Textual decision browser (q quit / r reload). Row-derived strings render markup-inert (rich.text.Text cells, markup=False panel) so a crafted stored value can't restyle/spoof the browser. LLM enrichment runs in a debounced, cached thread worker with a stale-selection guard, so a slow narrator call never freezes the UI or overwrites a newer row. Lazily imported behind an importlib guard: without the tui extra the command prints an install hint and exits 1, and core stays fully standalone.
  • policy/checklist.pyenforcement docstring corrected to the Option A monitor-floor semantics (the objective floor stays live in every enforcement state).
  • pyproject.toml — 0.13.0; new extras tui=[textual], explain=[anthropic]; textual added to dev so CI actually exercises the TUI tests (anthropic deliberately NOT in dev — tests fake the SDK).

Tests added (run in CI)

  • tests/unit/test_explain.py (26) — template wording per verdict, malformed reason codes never raise, pinned REDACTED_FIELDS allowlist (the constant can't bless itself), payload projection, full opt-in gate matrix incl. non-truthy flag values and use_llm restrict-only semantics, request-egress check that a stray secret on the row never reaches the (fake) SDK, keyword-strict fake messages.create mirroring the real surface, LLM failure/blank fallback, and the doberman tui no-textual CLI guard (kept here so it runs in the standalone venv).
  • tests/unit/test_tui.py (6, importorskip("textual")) — pilot tests: seeded rows + explanation shown, empty-repo placeholder, q quits, a synthetic secret in the raw target is never visible in any cell or the panel (end-to-end through storage redaction), crafted Rich markup renders literally, r reloads newly recorded decisions.

Public-release safety

  • 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 (and with neither optional extra installed)

Security checklist

  • Fails closed on error / uncertainty (LLM path falls back to the template; TUI never crashes on a narrator failure)
  • No secret, full file, or unredacted prompt logged or committed (allowlist projection + synthetic-secret egress/visibility tests)
  • Any guardrail/learning change is raise-only (no decision-path change at all — explain/tui are read-only over the redacted log)
  • Every BLOCK/AUTH carries reason codes + a human explanation (this PR adds the plain-language layer on top)

Edge cases covered / Deviations from plan / Risks introduced

  • Malformed/tampered rows (bad JSON, non-string reason codes, missing keys, Rich markup) render safely, never crash.
  • use_llm=True cannot bypass the env opt-in; non-truthy flag values ("0", "false", "no", "off", "", " ") stay off.
  • Per user decision: no doberman explain command — the TUI is the rich surface, doberman log the plain fallback.
  • Risk: LLM narration quality is unverified against a live API (opt-in only; deterministic template is the contract).

- explain.py: deterministic template explanation for any redacted decisions
  row + OPT-IN Claude-Haiku narrator (anthropic installed AND ANTHROPIC_API_KEY
  AND DOBERMAN_EXPLAIN_LLM, use_llm can only restrict); allowlist-projected
  payload only; every failure falls back to the template, never raises.
- tui.py + 'doberman tui': Textual decision-log browser with a why-panel;
  markup-inert rendering (Text cells, markup=False panel), debounced cached
  thread-worker enrichment with a stale-selection guard; lazy import behind
  an importlib guard so core stays standalone without the extras.
- policy/checklist.py: enforcement docstring corrected to the Option A
  monitor-floor semantics (objective floor stays live in every state).
- pyproject: 0.13.0; extras tui=[textual], explain=[anthropic]; textual added
  to dev so CI exercises the TUI tests.
- tests: 26 explain tests (gate matrix, pinned allowlist, payload-egress
  secret check, keyword-strict fake SDK) + 6 TUI pilot tests (secret never
  visible end-to-end, markup literal, reload, quit, empty repo).
@fu351 fu351 merged commit ed2b8d0 into main Jul 8, 2026
6 checks passed
@fu351 fu351 deleted the feat/transparency/decision-explainer-tui branch July 8, 2026 18:07
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