feat(storage): add configurable decision log pruning
Your AI's guard dog to stop it from going rogue.
Brought to you by:
doberman
Originally created by: slegarraga
Closes [#213]
decision-log-prune command with optional max-age and max-retained-row policies.record_decision and leave the append-only policy-drift ledger untouched.pytest tests/integration/test_decision_log.py -v — 10 passedtest_decision_log.py, test_cli_views.py, test_cli_help.py — 67 passedruff check .ruff format --check .lint-importstests/integration/test_real_plugin_install_discovery.py::test_installed_plugin_is_discovered_and_fires_despite_pip_target: the child interpreter cannot import the editable core (ModuleNotFoundError: doberman). It also fails on clean main locally. The remaining full suite is green.
Tickets: #146
Tickets: #213
Tickets: #462
Tickets: #498
Tickets: #502
Ticket changed by: fu351
Ticket changed by: fu351
Ticket changed by: fu351
Ticket changed by: fu351
Originally posted by: fu351
Thanks @slegarraga, the exact-boundary cutoff test is my favourite part, one row at exactly 90 days and one a second older, with the verdict set compared before and after, that's how a prune test should be written. Two things to change before I can land it, both need your tests:
decision-log-pruneis missing from_DOBERMAN_CONTROL_SUBCOMMANDSinengine/rules/commands.py, so a mediated agent can shell out todoberman decision-log-prune --max-rows 0and empty the audit log; that rule is an allowlist, not a catch-all. Add it plus a row intest_rule_commands_control_plane.pylike thememory pruneone._RESOLVED_DECISIONS_PREDICATEhasANDbetween its two clauses, so a row withfinal_verdict = 'AUTH'is never eligible even afterauth_resultis approved or denied, which means the--max-rowsbudget never holds for anyone using approvals (I seeded a year-old approved AUTH row and it survived--older-than-days 1). It wantsOR, plus the mirror of your unresolved-AUTH test asserting a resolved one does go.After that a rebase onto main clears the one import-line conflict in
cli/main.py, and a CHANGELOG bullet under Unreleased would be great. Looking forward to the update!Originally posted by: slegarraga
Thanks for the precise review — updated in f151631.
decision-log-pruneto_DOBERMAN_CONTROL_SUBCOMMANDS, with the requested--max-rows 0control-plane regression.OR: non-AUTH verdicts remain final, while AUTH rows become eligible only after an explicitapproved,denied, orexecutedresult.mainand resolved the import-onlycli/main.pyconflict while retaining both the upstream exclusions import and this PR's prune import.Local verification:
git diff --checkpassed.test_real_plugin_install_discovery.py::test_installed_plugin_is_discovered_and_fires_despite_pip_target(temporary environment cannot importdoberman); I reproduced the identicalModuleNotFoundErroron a clean detached worktree at currentorigin/main, so it is unrelated to this branch.Fresh CI is now running on f151631. Thanks again — the approved-AUTH case and the mediated-command gap were both important catches.
Originally posted by: vinayjagan03
Seems like all necessary CI tests have passed. Manually inspection of diffs looks fine. Discussed offline with @fu351 and seems okay for merge. Good job :)
Ticket changed by: vinayjagan03
Ticket changed by: vinayjagan03
Ticket changed by: vinayjagan03
Originally posted by: fu351
Thanks @slegarraga, this one's in! The boundary test is still my favourite part, one row exactly at the cutoff and one a second older, with the verdict set checked before and after, so an off-by-one or a prune that touches a verdict both go red. One thing I changed after the merge, in [#502]: the resolved predicate listed approved/denied/executed, but the proxy actually persists the auth method name (soft_confirm, local_auth, the approval method) plus blocked and error, so the most common resolved AUTH rows were never prunable. It now treats any non-NULL auth_result as resolved, pending rows still stay, and the --older-than-days help text now says the exact-cutoff row is kept. If you want the next one on the same log, [#201] (level-7) is a live-tail mode for the TUI decision-log browser, it reads the same table you just added pruning to.
Related
Tickets: #201
Tickets:
#502