Skip to main content

Module config_history

Module config_history 

Source
Expand description

Phase 35 — the config time machine (ADR-0141).

Config lives in git and its secrets live elsewhere, and the two drift: the copy in git is redacted, so it is not the file that runs, and the file that runs is versioned by nobody. The vault is the one place that holds both, so it keeps a snapshot of every rendered config whenever that config changes.

§Two copies of each snapshot, on purpose

content is the file exactly as it would be deployed, secrets included. masked is the same render with every resolved secret replaced by its fingerprint, which is what unv project export already prints to a terminal. Every default view (list, show, diff, the app) reads masked, so “this key changed” is visible without the key ever being read; content is reached only with --reveal/--out or the app’s Reveal. Masking the stored text afterwards would not work: a rotated-away secret is no longer in the vault, so nothing would know to mask it in an old snapshot.

§Tamper evidence and retention

Each stream (project and exporter) is a hash chain: chain = sha256(prev ‖ project ‖ exporter ‖ sha(content) ‖ sha(masked) ‖ at). Old secrets living forever is a liability, so history is pruned by policy (keep newest and everything younger than days), and pruning a prefix would normally break the chain. A checkpoint row records the chain value at the pruned boundary so the remainder still verifies, and the prune also appends a row to the vault’s own audit chain naming that value; verify requires the two to agree, so forging a checkpoint means forging the audit chain too.

Structs§

Policy
PruneReport
SnapMeta
Snapshot
Stats
Stream
One rendered stream: which project and which exporter produced the text.
VerifyReport

Constants§

DEFAULT_DAYS
DEFAULT_KEEP
MAX_BYTES
A snapshot larger than this is not stored. A rendered config is kilobytes; anything bigger is a file this feature was not built for.

Functions§

diff
A unified diff between two snapshots of the same stream. masked picks the fingerprinted text (the default everywhere) or the real one.
exposed_by_sha
What the files with this hash contained: the union over every snapshot that recorded it (the same text can be recorded again after a revert), or None when no snapshot has it, so a caller can tell “nothing exposed” from “unknown”.
find_by_sha
Snapshots whose content has this hash, newest first. This is how a hub says “the file on that host is the one rendered on 3 October”.
get
init_schema
latest
The newest snapshot of a stream.
list
policy
prune
Deletes, per stream, every snapshot that is both beyond the newest keep and older than days days, leaving a checkpoint so the rest still verifies. dry_run counts without touching anything.
record
Records a snapshot when content differs from the stream’s latest. Returns the new sequence number, or None when nothing changed (or history is off).
resolve_pair
Picks the two snapshots a diff should compare. With from/to unset, the two newest of the stream named by project (and exporter, which is required when the project has more than one stream, such as Compose and its .env).
set_policy
stats
verify
Recomputes every hash and every chain, and checks each checkpoint against the audit chain.