Menu

#226 cli: policy-history --json output is not deterministic like the other JSON commands

closed
nobody
2026-08-03
2026-08-02
Anonymous
No

Originally created by: fu351
Originally owned by: AshSgDe29071999

The inconsistency

Three of the CLI's four machine-readable outputs serialize deterministically and compactly. One doesn't.

command line call
scan --json cli/main.py:242 json.dumps(payload, sort_keys=True, separators=(",", ":"))
doctor --json cli/main.py:568 json.dumps(payload, sort_keys=True, separators=(",", ":"))
log --jsonl cli/main.py:810 json.dumps(record, sort_keys=True, separators=(",", ":"), default=str)
policy-history --json cli/main.py:992 json.dumps(rows, default=str)

No sort_keys, no compact separators.

Why it matters

policy-history is the append-only ledger of policy changes — who weakened or strengthened what, and how it was approved. It's exactly the output someone would diff between two points in time, hash for an audit trail, or cache.

Without sort_keys, key ordering isn't guaranteed stable, so a byte-comparison of two dumps of identical ledger state can report a spurious difference. On an audit surface that's a genuinely unhelpful property.

tests/unit/test_cli_policy_history_json.py also has no determinism test, unlike test_cli_scan_json.py, which asserts a repeated invocation produces byte-identical stdout.

What to do

  1. Change line 992 to match its siblings: add sort_keys=True, separators=(",", ":").
  2. Add a determinism test mirroring the one in test_cli_scan_json.py — invoke twice against the same DB state, assert again.stdout == result.stdout.
  3. While you're there: the existing tests only cover the empty ledger. Add a case with actual rows, so the payload shape is verified at all.

Done when

Two consecutive policy-history --json calls on unchanged state produce byte-identical stdout, proven by a new test, and a populated-ledger case exists.

Notes

  • default=str can stay — harmless, though every ledger column is already a JSON-native type.
  • Don't change which fields are emitted, only how they're serialized. The field set is deliberate and its redaction was reviewed separately.
  • Related but distinct: [#192] defines a shared JSON-output contract across all four commands and explicitly excludes behaviour changes. This issue is the narrow bug fix; [#192] is the policy. They don't conflict.

Comment here to claim it.

🤖 Generated with Claude Code

Related

Tickets: #192
Tickets: #230

Discussion

  • Anonymous

    Anonymous - 2026-08-02

    Originally posted by: AshSgDe29071999

    I'd like to make policy-history --json output deterministic like the other JSON commands.

     
  • Anonymous

    Anonymous - 2026-08-03
     
  • Anonymous

    Anonymous - 2026-08-03

    Ticket changed by: fu351

    • status: open --> closed
     

Log in to post a comment.