Skip to content
Architecture note

What a signed, hash-linked ledger does and does not prove

Our attestation chain is signed, hash-linked, and re-verified on every read. It is tamper-evident. It is not independently verifiable, and the distinction is the first thing a security reviewer will test.

Abstract

A self-attested log is not evidence. This note states precisely what our DSSE and in-toto attestation chain establishes — that no row was edited by anyone lacking the relevant signing key — and precisely what it does not: that the chain was never wholesale replaced by someone who holds those keys or controls the trust map. It explains why external transparency-log anchoring is the gap that separates the two, and why we do not use the phrase 'independently verifiable' before it ships.

Summary

The proposal, policy decision, approval, and execution events for a change are each signed and hash-linked into a chain. Verification re-runs on every read of an incident’s ledger rather than being cached, so a tampered row shows as broken the moment anyone looks — not when a scheduled check happens to run.

That gives a real property. It gives a narrower property than the words normally used to describe it, and this note exists to keep the two apart.

Design

Envelopes follow DSSE with correct pre-authentication encoding, wrapping in-toto Statements. Signing is Ed25519 through the standard library — no additional dependency. Four keypairs exist, one per role: agent, policy engine, human approver, and executor.

Verification checks four things, and the third is the one that makes it a chain rather than a set of signatures:

  • Each envelope carries a valid signature from a key trusted for that role.
  • Role separation holds — an agent-signed envelope cannot occupy an approval slot.
  • Each entry’s signed parent_hashequals the previous envelope’s own hash. Insertion, deletion, reordering, or edit breaks a signature, not merely a foreign-key reference.
  • Cross-references are consistent: an approved policy’s proposal hash must match the real proposal, and an execution’s applied-diff hash must equal what was proposed and approved, not something swapped in afterwards.

Denied proposals get a real chain too. A denied change never reaches git and has no blob-SHA-based diff hash to sign against, so the proposed-but-never-committed changeset is content-hashed directly. That chain terminates at the denial and never grows an execution event — a refusal is recorded as a decision that was reasoned about, not as the absence of one.

Fail-closed asymmetry, deliberate
Approval signing happens before the merge and blocks it on failure: an unsignable approval must not let a merge through unattested, which is the entire point of the ledger. Execution signing happens after a successful merge and cannot fail closed the same way, because the merge already happened. A signing failure there is recorded as its own audit event rather than thrown or silently swallowed. The asymmetry is a consequence of where the irreversible step sits, not an oversight.

What verification establishes

Claims supported and unsupported by ledger verification
ClaimSupported?Why
No row was edited by someone without the relevant keyYesAn edit invalidates that entry's signature and every subsequent parent hash.
The recorded approval was produced by the approver role's keyYesRole separation is checked as part of verification, not assumed.
The executed diff equals the approved diffYes, for changes through this pipelineCross-reference check, against a hash recomputed from the provider's API.
The approval was made by a specific named personNoOne keypair per role, not per individual. The approver identity is a string the caller supplies.
The chain was never wholesale replacedNoThe rows and the trust map live in the same database. A holder of the keys could rebuild both.

Trust boundaries

Two boundaries are currently weaker than the cryptography suggests, and both are configuration rather than credential boundaries.

The signing keys are split across two trust-domain secrets rather than one shared secret, and the separation is enforced at runtime: each service refuses to load the other domain’s role, and — tested directly, bypassing that guard — decrypting the approver key with the agent secret fails authenticated decryption. The wrong key genuinely cannot read the ciphertext.

But all four keys live in one table, and possession of an environment string is the entire authorization model, with no external check. Nothing structurally prevents both secrets reaching one process; that happened once, briefly, during initial setup. The correct sentence is that the agent process does not currently hold that credential. Not that it cannot obtain it.

Implemented
Two distinct trust-domain secrets with runtime role refusal, empirically verified on 17 July 2026 — including confirmation that the correct key still decrypts, so the failure above was a real boundary and not a universal break.
Open question
Whether a config boundary can ever be presented as a security property to a reviewer who asks “who can forge this?” Our current answer is no, which is why revocation and certification are both blocked behind key custody rather than built on top of it. A kill switch built on shared custody is decorative, and decorative is worse than absent once it has been claimed in a security review.

Vocabulary, and why we police it

A security reviewer who catches loose language discounts every other claim on the page. So these are fixed:

  • Tamper-evident is correct today. Tamper-proof is never correct, at any stage of this work.
  • Independently verifiable is not correct until external anchoring exists, because independence is exactly what is missing.
  • Consequentialis the policy engine’s term of art. We avoid “risky”, which implies detection rather than classification.

The independence claim is the core of the argument for this whole system, so this is not a cosmetic restriction. It gates the strongest version of what we can say.

Open work

Planned
Transparency-log anchoring, or RFC 3161 timestamping, so that the chain’s existence at a point in time is witnessed outside the database that stores it. Until that ships, “independently verifiable” is not a phrase available to us.
Planned
Per-approver identity binding, and beyond it, no long-lived private key for the approver role at all — keyless signing with identity-bound short-lived certificates, or a hardware token the approver holds.

There is one further operational constraint worth stating plainly: signing keys must be provisioned per environment before any incident can be processed. Signing is not best-effort. An unprovisioned key throws rather than silently processing an unsigned change.