Skip to main content

Module evidence_bundle

Module evidence_bundle 

Source
Expand description

Evidence bundle export (ticket .kranz/tickets/evidence-bundle-export.md, KRZ-326 — the governance evidence layer’s packaging step): assemble ONE mission’s portable audit package — inputs, gate results, diffs, reviewers, escalations, cost, and the provenance chain — self-contained and suitable for handing to an auditor who has no access to the repo.

The bundle is a plain DIRECTORY, not an archive:

<out>/
  manifest.json     — machine index: every entry with its sha256 + source
  summary.md        — the human-readable audit summary
  chain.json        — the provenance chain (provenance-replay's machine form)
  escalations.json  — the mission's escalation-ledger rows
  cost.json         — the mission's cost fold
  events.jsonl      — the raw (already-scrubbed) event log, verbatim
  artefacts/…       — bytes for every resolvable `file:` artefact ref,
                      plus the well-known mission documents (plan, report)

WHY a directory and not a .tar: neither tar nor zip is anywhere in the dependency tree, and the ticket blesses the directory form — it is also the MORE auditable container: every entry greps, diffs, and opens in any tool with no extraction step, and there is no archive metadata (mtimes, uid/gid, ordering) whose normalization would be a second determinism surface. Determinism is therefore ENTRY identity: the same log yields the same (relative-path → bytes) set, byte for byte. Nothing in assembly consults a clock, a hash map, or a host path — the log’s own event timestamps travel as DATA (escalation rows), which is exactly what “same log → same bundle” requires.

The substrate’s own rules, kept:

  • Everything derives from the already-scrubbed log. The bundle never reintroduces scrubbed values: events.jsonl crossed the redact-at-write boundary when it was appended, and every derived file folds FROM it. A log carrying secret.redacted audits yields a bundle with fingerprints only (test-pinned). Artefact bytes are the one half that did NOT cross a write boundary the engine controls — they are ordinary files in a worker-writable tree — so [read_artefact] scrubs them here, as text, and the manifest digests the redacted form (audit H5).
  • Missing evidence is named, never omitted and never an error. A file: reference whose bytes are gone (a cleaned runs/, a pruned mission) becomes a manifest entry marked unresolved carrying the original reference — the same total-classifier discipline as crate::gate_results::resolve_artefact.
  • No host paths. References stay mission-relative; the absolute path the resolver probed never crosses into the bundle (the same reason the provenance chain records only the classification — a host path would leak the machine layout into the audit record). Bundle-relative paths are always /-joined so the package is host-platform neutral.
  • Read-only against the mission dir; the write target is outside it. No lock (§4.3 read-only observers); export_evidence_bundle refuses an --out inside the mission dir before writing anything.

WHY the raw log ships beside the folds: the chain, escalations, and cost are all pure folds of events.jsonl; an auditor with no repo access can only RE-CHECK that claim if the primary record is in the package. The log is the one entry that is never unresolved — a mission without its log is not a mission (the CLI’s require_mission rule), so a missing/unreadable log fails the export outright.

Structs§

BundleFile
One bundle payload: a /-joined bundle-relative path and its bytes. Logical paths (never host paths), so the in-memory form is already platform-neutral.
EvidenceBundle
The assembled bundle: the manifest plus every NON-manifest file’s bytes, in write order. manifest.json itself is serialized at write time (it cannot list its own hash). Held in memory so two assemblies can be compared for byte identity before anything touches disk.
EvidenceManifest
The machine index (manifest.json): every bundle entry with its sha256 and source reference, in bundle order — generated files first (fixed order), then artefacts in first-appearance order across the chain (gates, then sessions, then the well-known documents), each unique reference appearing exactly once.
ExportOutcome
What export_evidence_bundle wrote, for the CLI’s one-line report.
ManifestEntry
One row of the machine index. path/sha256 are absent exactly when the entry is an unresolved artefact — there are no bytes to point at, and a fabricated path would be a lie.
MissionCostSummary
The mission’s cost fold, bundled (cost.json). All fields come from crate::outcomes::mission_outcomes — the same fold the flight-surgeon surfaces use, so the bundle can never disagree with them.

Enums§

EntryKind
What one manifest entry is. Serde lowercase (the ArtefactStatus idiom).

Constants§

ARTEFACTS_DIR
BUNDLE_FORMAT_VERSION
manifest.json’s version field: the bundle format version. Bump on any layout/schema change so a reader can tell what it is holding.
CHAIN_FILE
COST_FILE
ESCALATIONS_FILE
LOG_FILE
MANIFEST_FILE
SUMMARY_FILE

Functions§

assemble_evidence_bundle
Assemble one mission’s evidence bundle in memory. Read-only against the mission dir (no lock — §4.3 read-only observers), no clock, no network, no git: the same log and artefact bytes always assemble the same bundle.
export_evidence_bundle
Assemble + write the bundle, with the one placement rule enforced: the write target must be OUTSIDE the mission dir (a bundle written into the tree it audits would both mutate the read-only surface and risk shipping itself as evidence).
write_evidence_bundle
Write an assembled bundle to out_dir, returning the number of files written (including manifest.json). The directory must not already hold anything: silently mixing two exports would leave stale artefacts no manifest entry names — the same honesty discipline as unresolved entries. The out dir is created and written through a pinned no-follow capability ([plan_out_dir] / [pin_out_dir]): a symlinked existing component is refused, and nothing written ever crosses a symlink.