Skip to main content
An attestation is the signed record of one job attempt. It captures the exact tested source, the workflow-byte digest and binding state, the runner environment, and evidence that could or could not be collected. The engine attempts to sign and archive each terminal job attempt after settlement (subject to archive quotas), whether it ran on this engine or on a local engine reporting to a trusted signer. Collection is best-effort and never changes the job result. A collection failure is logged, and the run’s latest-projection manifest still reports what was recorded. A process crash in the short window between settlement and archive can leave no receipt; there is no durable pending-archive outbox yet, so do not claim an attestation that the API cannot return. The API is read-oriented. A receipt is immutable; a later facts correction or a new attempt adds another receipt and leaves the earlier signed bytes untouched.

What the UI can show

GET /api/v1/attestations is the previous attestations list. It returns bounded summaries, newest first, and an opaque cursor. A row has:
  • receipt id and creation time;
  • repository, run, job, attempt, terminal conclusion;
  • captured source: kind, snapshot commit, tree, dirty/unknown, execution binding (bound/unbound/unknown), and a compact commit-signature badge (signed commit, format, status, trust, coverage, key fingerprint, verifier) when captured;
  • signer issuer/key id/algorithm;
  • authenticated reporter subject/organization;
  • execution origin, assurance (observed or reported), observer;
  • the reporter’s location assertion, if any (untrusted);
  • manifest and unsigned-report digests;
  • completeness and named evidence gaps.
GET /api/v1/attestations/{id} is the detail view. It adds the exact signed manifest, DSSE envelope, pinned public JWKS snapshot, deterministic unsigned report, and publication status. Use the manifest for “what was attested”: An example summary:

Assurance: signature is not observation

A valid signature says who signed the record. It does not prove the signer watched the job run.
  • execution.assurance = observed means the signing engine assembled the record from its own control-plane state and captured snapshot.
  • execution.assurance = reported means an authenticated caller submitted it. The signature proves the trusted signer accepted that report.
  • execution.origin is a location the receiving service established from operator configuration. unknown is the honest value when it did not.
  • asserted_origin is the reporter’s claim. Show it as an assertion, never as verified execution location.
For UI policy, a hosted-observed badge requires assurance == observed and the origin policy the organization chose. A cryptographically valid receipt of a local report must not display the same badge. The same distinction applies to spec.source.commit_signature. attestation.commit_signature_assurance = observed means this engine verified the commit signature at source intake; reported means the evidence came from the authenticated reporter and was not independently verified by the signing service. Render status = valid, trust = trusted, and coverage = tested-tree only when all three are present: a valid signature over a dirty or merged source is coverage = commit-only and covers the named commit, not the executed bytes. coverage = tested-tree still describes the captured snapshot tree, so do not present it as covering the executed bytes unless spec.source.execution_binding = bound as well. When the binding is unbound or unknown, show the captured-source evidence and the spec.source.execution_binding gap instead. Never treat the commit’s author/committer text or a Signed-off-by trailer as a cryptographic signer identity.

History and pagination

The list is a live keyset feed, newest first. It is not a frozen export: a receipt committed after a cursor was issued can appear above that cursor. limit defaults to 25 and caps at 100. Pages are also byte-bounded. The cursor is bound to the authenticated organization and the selected filters; do not decode or construct one. Detail and export are the exact evidence. The list never loads signed bodies, so it is safe to render a dense history table and fetch detail on selection.

Publishing local receipts

A local engine can publish to one configured trusted signer. Its locally observed receipt remains, and the hosted receipt is stored as a separate receipt linked by the unsigned report digest. If the dedicated remote credential uses the same subject and organization as the local intermediate reporter, the archive’s replay key dedupes the hosted receipt into that local row; the publication relation still carries the authoritative remote id, and the hosted envelope remains at the trusted signer. The publication relation reports: POST /api/v1/attestations/{id}/publish retries the configured target through the local engine. It cannot be pointed at another URL or credential. Because the report identity is stable, a retry after a lost acknowledgement returns the existing hosted receipt rather than creating a second claim.

Verification

preloop attestation show <id> --verify checks the DSSE payload type, canonical bytes, signature, both digests, the normalized report digest, and that the signed signer key id matches the verifying key. Use --trusted-jwks <file> to pin an approved key set, including a retired key. The pinned JWKS returned in a receipt is evidence, not its own trust anchor. It verifies the receipt, not the Git commit signature: the commit evidence was verified by the engine at snapshot intake and is reported here as signed evidence. Re-verifying it would require the original commit object and the same operator trust root; preloop manifest prints the recorded status, trust, coverage, fingerprint, and reason.

Privacy and limits

A manifest never contains a secret value, and it does not contain a salted secret-value commitment: a public salt would let anyone test low-entropy guesses offline. Secret names and the env.secret_values evidence gap remain. Ambiguous environment values and an unbound VM image are also gaps, not inferred claims. The API accepts at most a 4 MiB canonical manifest, enforces bounded field counts and entry lengths, and bounds the archive per reporter and per organization. The organization bounds are 100,000 receipts and 4 GiB of retained bodies; an external reporter is additionally bounded to 10,000 receipts and 1 GiB of retained bodies, so one reporter cannot spend the whole organization budget. Every bound is charged from an aggregate usage ledger inside the receipt-insert transaction. The engine’s automatic observer is exempt from both per-reporter bounds (it remains inside the organization bounds) so automatic attestation does not stop after a fixed number of job attempts or bytes. These caps are not operator-configurable, and receipts are immutable: when the organization bound is reached, new receipts (including automatic ones) are refused until the archive is exported and rotated. Exhaustion returns a retryable error; accepted receipts are never silently dropped.