Skip to content

Part VIII — Integrity, provenance, and recovery

Integrity manifests ✅ {#S22}

The adversary this addresses is the sharpest one in the system. The controls this project relies on are themselves Python files sitting in the same working tree the agents can edit — the destructive gate, the scanner, the fence-restore engine, the capability merge, the red-team checks, the sandbox emitters, the hook. A red-team probe (E4) recorded the umbrella finding plainly: the constitution's referee plays inside the same sandbox as the players. An agent that can write the gate does not need to defeat it. The integrity manifest is the response to that class of tampering.

What it is. A SHA-256 manifest over the enforcement modules, stored at references/enforcement-integrity.json. Crucially, the tracked set includes integrity.py itself, so removing a module's entry — which would silently drop it from coverage — is itself detectable. verify() reports modified, missing, or unmanifested (a tracked module the manifest does not record — reported deliberately, because under-coverage is the manifest's own failure mode).

A missing manifest returns [] — "never set up" ≠ "compromised." An unmanifested repository is not a tampered one; firing on every fresh clone would train operators to mute the check, and a muted check protects nothing. Regenerating the manifest (--write-integrity-manifest) is an explicit operator act, never auto-run — an auto-refreshed manifest would re-bless an attacker's edit on the next build and verify nothing.

Honest ceiling — a speed bump, not a boundary. Stated in-module and central to a reviewer's model: a hash manifest stored beside the files it protects is a speed bump, not a boundary. One who can edit the scanner can edit the manifest; one who can edit integrity.py can make verify() return []. What it buys, honestly: it detects accidental corruption and drift (most real-world damage); it makes tampering a multi-step, recorded act visible in git diff (the E4 cost-raising of S19/S25); and it gives the PreToolUse hook something concrete to check before trusting the scanner. The durable fix is elsewhere: move enforcement into the harness, and keep signing keys outside the agent's environment. Full module treatment: Edition R, S22.

Source. agentteams/integrity.py:1-188; agentteams/cli/commands.py:170-266.

Provenance stamps ⚙ (library / pattern, not auto-wired into the default emit path) {#S23}

The concern: a downstream reader mistaking a provisional snapshot for a settled result. Provenance is a small, reusable stamp recording how a generated artifact was produced — the generator, a generated_at timestamp, input SHA-256 prefixes, and a required provisional list of known limitations.

Two design choices matter to a reviewer. First, generated_at is passed in, never read from the clock — a reproducibility constraint (an implicit clock read would break resume and determinism). Second, it is honest-by-construction: provisional is a required, explicit field, never defaulted to a reassuring value, and an empty list is a deliberate assertion rendered Provisional: none declared (deliberate) — so an empty caveat list can never be confused with an unset one.

Honest ceiling — and note the ⚙ marker. Provenance is a library, not an active pipeline stage: it is stdlib-only and reusable, but not auto-emitted into the default generation pipeline. Reading this as "every generated artifact already carries a provenance stamp" is the overclaim the marker exists to prevent. And a stamp records what its caller declared — it vouches those declarations are recorded honestly; it does not independently verify the declared inputs were the ones used, and an artifact that never calls the library carries no stamp at all. Full treatment: Edition R, S23.

Source. agentteams/provenance.py:1-99.

Backups and baselines ✅ {#S24}

The recovery layer accepts that damage and drift can happen and makes them reversible and detectable respectively — it does not pretend to prevent them.

Backup-before-destructive. Before any destructive write (unless --no-backup or --dry-run), agentteams snapshots the output tree to <output>/.agentteams-backups/YYYYMMDD-HHMMSS/ with a per-file _manifest.json of full SHA-256 plus reason/framework/version. The SHA is hashed from the backup copy, not the source — deliberately closing a time-of-check/time-of-use window, so the recorded digest describes exactly the bytes saved. Verbs: --list-backups; --restore-backup LABEL|latest (snapshot-complete rollback, itself gated by the destructive gate, S11); --verify-backup (re-hash vs recorded SHA → PASS/FAIL/MISSING); --prune-backups [KEEP] (union/fail-safe retention — the single newest is always kept, even at KEEP 0); --backup-mirror (best-effort off-machine copy, non-fatal on failure).

Baselines detect emission drift. A baseline (--capture-baseline / --check-baseline) is a SHA-256 manifest of a generated tree, compared byte-for-byte to detect emission drift — an unintended change in what the generator emits. --check-baseline exits 2 on drift. It hashes raw file bytes only (timestamps, mtime, ordering excluded), so a baseline is stable across machines and a drift finding reflects a real content change, not an environmental difference.

Honest ceiling. A backup makes damage reversible and a baseline makes drift detectable; neither prevents the write or the drift. Backups share the tree's own trust boundary — an attacker who can write the output can write .agentteams-backups/ — the off-machine mirror is best-effort, and a baseline tells you the emission changed, not whether the change was intended. Full verb and line-number detail: Edition R, S24.

Source. agentteams/backup.py:1-497; agentteams/baseline.py:1-131; agentteams/cli/backup_switch.py:1-85; agentteams/cli/app.py:133-163.


Sources for Part VIII. agentteams/integrity.py; agentteams/cli/commands.py; agentteams/provenance.py; agentteams/backup.py; agentteams/baseline.py; agentteams/cli/backup_switch.py; agentteams/cli/app.py. Line-precise provenance: SOURCES.md (S27).