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 (
observedorreported), 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 = observedmeans the signing engine assembled the record from its own control-plane state and captured snapshot.execution.assurance = reportedmeans an authenticated caller submitted it. The signature proves the trusted signer accepted that report.execution.originis a location the receiving service established from operator configuration.unknownis the honest value when it did not.asserted_originis the reporter’s claim. Show it as an assertion, never as verified execution location.
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 theenv.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.