Documentation
¶
Overview ¶
Package audit is the append-only event log every decision lands in: a local JSONL file by default, or a Postgres table in multi-instance mode (postgres.go).
Every entry carries a running SHA-256 hash chain (chain_hash, prev_chain_hash, seq), the same construction a git commit history uses: each entry's hash covers its own content plus the previous entry's hash, so altering, deleting, or reordering any past entry breaks the chain from that point forward in a way VerifyChainFile can pinpoint exactly. "How do we know this log wasn't edited after the fact" is a standard auditor question, and a local file editable by anyone with disk access doesn't answer it; the chain does.
Postgres-backed audit log for multi-instance/HA deployments. The file-backed Logger serializes the hash chain with an in-process mutex plus locally cached lastHash/seq fields - correct for one process, but meaningless once a second process can also be appending to the same chain: two instances logging at nearly the same instant would both read a stale "last hash" and both compute the same next seq/prev_chain_hash, splitting the chain into two disconnected branches.
Postgres mode fixes this the same way internal/approval and internal/saga's Postgres backends do: a tiny singleton "chain head" row, locked with SELECT ... FOR UPDATE inside the same transaction as the new row's insert, so reading the current head and advancing it is one atomic, serialized operation across every instance - not two operations racing between instances that only synchronize within themselves.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ReadRecent ¶
ReadRecent returns the most recent (up to limit) entries from the JSONL audit log, newest first - the read path behind the admin console's live activity feed (internal/adminui). Deliberately lenient, unlike VerifyChainFile: a malformed line is skipped, not a hard failure, since this is a display convenience, not the tamper-evidence check - a display bug over one bad line would be a worse outcome than showing everything else. A log that doesn't exist yet (a fresh deployment with no calls made) returns an empty slice, not an error.
func ReadRecentPostgres ¶
ReadRecentPostgres is ReadRecent's exact counterpart for a Postgres- backed audit log - the most recent (up to limit) entries, newest first, for the admin console's live activity feed. Deliberately lenient like ReadRecent: a malformed row is skipped, not a hard failure.
Types ¶
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
func Open ¶
Open resumes the hash chain from wherever a prior run left off, not just the file offset - it reads the existing log's last entry (if any) and recovers its chain_hash/seq so a restart continues the same chain rather than silently starting a new, disconnected one. A log written before this feature existed (no chain_hash on its last entry) is handled explicitly, not treated as corruption: its entries stay in the file for historical reference, and the chain simply starts fresh from genesis at the first entry written after this upgrade - coverage begins at the point the feature was deployed, which is the honest boundary for a tamper-evidence feature added retroactively to existing data.
func OpenPostgres ¶
OpenPostgres opens (creating the schema if needed) a Postgres-backed Logger safe for concurrent use by multiple proxy processes appending to the same chain. Reuses the same "pgx" database/sql driver as the approval/saga Postgres backends.
type VerifyResult ¶
type VerifyResult struct {
OK bool `json:"ok"`
EntriesChecked int `json:"entries_checked"`
LegacyEntries int `json:"legacy_entries_skipped"` // pre-chain entries: no chain_hash field, not coverable, not a failure
BrokenAtLine int `json:"broken_at_line,omitempty"`
BrokenAtSeq int64 `json:"broken_at_seq,omitempty"`
Reason string `json:"reason,omitempty"`
}
VerifyResult is the outcome of walking a JSONL audit log and recomputing its hash chain from scratch.
func VerifyChainFile ¶
func VerifyChainFile(path string) (VerifyResult, error)
VerifyChainFile walks path in order and confirms every chained entry's chain_hash matches a fresh recomputation, and that it correctly names the prior chained entry's hash as its own prev_chain_hash - exactly the chain Logger.Write builds. Any entry edited, deleted, or reordered after the chain begins breaks verification at precisely that point, not just "somewhere in the file" - BrokenAtLine/BrokenAtSeq name it.
Numeric fields are decoded via json.Number (not the default float64) so recomputation is byte-exact regardless of a caller-supplied field's magnitude - a naive map[string]any round-trip through float64 can silently reformat large integers, which would make a perfectly valid entry look tampered. Getting this exactly right matters more here than almost anywhere else in this codebase: a false positive ("tampering" that never happened) undermines trust in the whole feature as surely as a false negative would.
func VerifyChainPostgres ¶
func VerifyChainPostgres(dsn string) (VerifyResult, error)
VerifyChainPostgres is VerifyChainFile's exact counterpart for the Postgres-backed log: walks every stored entry in seq order and confirms the chain is unbroken, using the identical recomputation algorithm (verifyChainLines in audit.go) - one implementation of "what makes a chain valid" shared by both storage backends.