Skip to content

feat(#62): Action Manifest v0 — Ed25519-signed per-action provenance#52

Merged
tattoosonmyskin merged 2 commits into
mainfrom
feat-action-manifest-v0
Jun 29, 2026
Merged

feat(#62): Action Manifest v0 — Ed25519-signed per-action provenance#52
tattoosonmyskin merged 2 commits into
mainfrom
feat-action-manifest-v0

Conversation

@tattoosonmyskin

Copy link
Copy Markdown
Contributor

Closes the last net-new capability gap from the independent calibration audit (#62 / M9): linking the runtime decision ledger to a portable, externally-verifiable provenance format.

The gap

The audit ledger is a SHA-256 hash chain — tamper-evident but not authentic. The chain hash takes no secret, so anyone with the JSONL can recompute a fully self-consistent chain; a third party cannot prove a log came from a genuine instance. Every other signature in the tree is HMAC (symmetric), which can't give external verifiability either.

What this adds

An Ed25519 (asymmetric) signature over a canonical, machine-readable manifest — verifiers need only the public key.

  • ts_cli::provenance_manifest: ManifestSigner (key 0600, generated on first use; signer_key_id = sha256(pubkey)[..16]; pubkey published beside the log), canonical sorted-key serializer with reference vectors, Merkle checkpoints, claim taxonomy, verifier.
  • Default checkpoint signing (one sig over a Merkle root every N entries); per-action signing opt-in via --manifest-per-action.
  • ts_cli --verify-manifests <log>: chain integrity + signature authenticity + coverage; exits non-zero on any gap.
  • Wired into AuditLogger, recorded after an entry is committed — off the decision path, best-effort, never affects a verdict.
  • Signatures cover the redacted chain bytes only → crypto-shredding (#61) leaves them valid.

Honesty / scope

  • kernel_enforced claim omitted in v0 (the AuditEntry carries no per-action kernel-enforcement flag yet — would overclaim). v1 item.
  • Signing is inline-after-commit, not a background thread (still off the hot path; Ed25519 ≈ tens of µs).
  • Single epoch (0); rotation epochs + transparency-log anchoring are v1/v2.

Validation

  • 7 unit tests: canonical form, Merkle, checkpoint + per-action verify, tamper (chain breaks), forgery-with-different-key (chain intact but authenticity FAILS — proves the gap is closed), post-erasure verification.
  • End-to-end through the daemon: genuine log verifies (exit 0); re-signed with a different key → authenticity FAIL (exit 70); key written 0600.
  • Full ts_cli suite green; cargo fmt --check + clippy -D warnings clean; cargo-deny licenses/bans/advisories/sources all green with ed25519-dalek (pure-Rust, BSD-3; no ring/aws-lc).

Deps: ed25519-dalek = 2. Docs: README Guarantees row 🚧→✅ (opt-in), THREAT_MODEL §12.1, CHANGELOG, ACTION_MANIFEST.md marked as-built.

🤖 Generated with Claude Code

Copilot AI and others added 2 commits June 29, 2026 09:07
Adds asymmetric authenticity on top of the audit ledger's existing
tamper-evidence. The SHA-256 hash chain takes no secret, so anyone with
the JSONL can recompute a self-consistent chain; an Ed25519 signature
over a canonical, machine-readable manifest lets a third party verify a
log offline with only the public key.

New `ts_cli::provenance_manifest`:
- ManifestSigner (ed25519-dalek; key 0600, generated on first use;
  signer_key_id = sha256(pubkey)[..16]); pubkey published beside the log.
- Default checkpoint signing (one sig over a Merkle root every N entries);
  opt-in per-action signing (--manifest-per-action).
- Canonical sorted-key serializer with reference vectors; claim taxonomy
  derived deterministically from each AuditEntry (kernel_enforced omitted
  in v0 to avoid overclaiming).
- verify_manifests(): chain integrity + signature authenticity + coverage;
  one-shot `ts_cli --verify-manifests <log>` exits non-zero on any gap.

Wiring: AuditLogger gains an optional signer, recorded after an entry is
committed (off the decision path; best-effort, never affects a verdict).
Signatures cover the redacted chain bytes only, so crypto-shredding (#61)
leaves them valid.

Deps: ed25519-dalek 2 (pure-Rust, BSD-3; no ring/aws-lc). cargo-deny
licenses/bans/advisories/sources all green.

Tests: 7 unit (canonical form, Merkle, checkpoint + per-action verify,
tamper, forgery-with-different-key, post-erasure) + end-to-end daemon
validation. Docs: README Guarantees row, THREAT_MODEL 12.1, CHANGELOG,
ACTION_MANIFEST.md marked as-built.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the cargo-deny advisories gate failure: RUSTSEC-2026-0190 flags an
unsoundness in anyhow's Error::downcast_mut() for all versions < 1.0.103
(undefined behavior when downcast_mut is called on an error that had
context added via Error::context). Advisory published 2026-06-29; the
patched range is >= 1.0.103. Lockfile-only bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tattoosonmyskin tattoosonmyskin merged commit f9c889b into main Jun 29, 2026
10 checks passed
@tattoosonmyskin tattoosonmyskin deleted the feat-action-manifest-v0 branch June 29, 2026 22:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants