code-change-aggregate predicate, v0.1
What continuous integration signs over a range of commits, and the one rule that decides what a signature is allowed to say.
"predicateType": "https://provene.dev/attestation/code-change-aggregate/v0.1"
A local emitter writes an unsigned code-change receipt per session. CI then re-observes what it can, signs with its own workload identity, and emits an aggregate naming the constituent receipts by digest. That is promotion, and it is where a receipt stops being a self-attestation.
The rule that matters
A promoted receipt must not restate a claim it did not itself observe. Claims carried forward from a lower tier retain the observedBy of their original observer.
A test run the developer's machine observed does not become independently verified by appearing inside a document CI signed. The signature says CI vouches for what CI saw — the range, the digests, the runs it executed. It says nothing more, and the tool enforces that rather than trusting emitters to be careful.
This is also why the aggregate carries no changes block: CI observed the test runs, not the agent's edits, so attribution is never promoted.
What it carries
| Field | Claim |
|---|---|
attestation.attester | The CI identity that signed. Checked against the signing certificate, not taken on the document's word. |
attestation.trustRoot | sigstore-github — recorded as a kind rather than a specific instance, because the signer does not choose it: GitHub selects the public-good instance for public repositories and its private one otherwise. |
binding | The change digest over the whole range, computed with the same algorithm as a single receipt. |
constituents | The lower-tier receipts this aggregate covers, by digest. |
coverage | complete, plus how many constituents were found against how many commits are in range. Emitted even when incomplete — a missing aggregate is indistinguishable from a crashed job, while complete: false is a signed statement of exactly how much was covered. |
verification.runs | Runs CI observed. All carry observedBy: "ci". |
Verifying one
provene verify-aggregate --repo owner/name --base <commit>
Signature verification is delegated to gh attestation verify and its exit code is the trust boundary. The tool distinguishes four outcomes that are easy to collapse into one and must not be: verified, no attestation exists, the signature is invalid, and the verifier could not be run. Only the first exits zero, and an absent attestation is never reported as a failed signature.