Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/tutorials/verifying-the-audit-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ if call_count is not None and len(calls) != call_count:

For high-assurance scenarios, individual calls may carry external execution receipts: signed by a third-party (the caller, an orchestrator, or a notary) rather than the agent that produced the Trust Record.

The spec (§3.3.1) defines the receipt structure:
The spec (§3.3.2) defines the receipt structure:

| Field | Description |
|---|---|
Expand Down
2 changes: 1 addition & 1 deletion docs/verification-outcome-statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The rows are representative, not exhaustive. Each row reads: the outcome a verif

| Verification outcome | Supported bounded statement | Required non-claim | Evidence to retain | Follow-up |
|---|---|---|---|---|
| **Verified at the required floor.** Signature binding verified, record fresh, `appraisal.status` is `affirming` (or `warning`), and `appraisal.provenance_depth_verified` meets the deployment profile floor. | This record was not altered after issuance, and the checks the verifier ran passed at the recorded depth, under the appraisal policy named in `appraisal.policy_ref`. ([§3.2.2](../spec/trace-v0.2.md), [What verification proves](verification.md#what-verification-proves)) | Not current trust in the signing key unless revocation was actually consulted ([LIMITATIONS](../LIMITATIONS.md): pure offline verification cannot prove non-revocation). Not that the bound policy achieves its intended outcome ([LIMITATIONS](../LIMITATIONS.md): policy correctness is a separate control). Not soundness of the model's behavior ([§2.4](../spec/trace-v0.2.md)). Not success of any physical or business outcome ([§3.3.2](../spec/trace-v0.2.md)). Not a general trust score for the subject. | The record itself; the trusted-key thumbprint used; the freshness bounds applied; if revocation was checked, the bundle identity and `valid_until` - reported as "verified against revocation bundle valid at T" ([§3.2.3](../spec/trace-v0.2.md)); the complete `appraisal`. | Re-verify before material reliance on an aging record; consult current revocation status at decision time; keep the `policy_ref` target resolvable for whatever retention the published statement promises. |
| **Verified at the required floor.** Signature binding verified, record fresh, `appraisal.status` is `affirming` (or `warning`), and `appraisal.provenance_depth_verified` meets the deployment profile floor. | This record was not altered after issuance, and the checks the verifier ran passed at the recorded depth, under the appraisal policy named in `appraisal.policy_ref`. ([§3.2.2](../spec/trace-v0.2.md), [What verification proves](verification.md#what-verification-proves)) | Not current trust in the signing key unless revocation was actually consulted ([LIMITATIONS](../LIMITATIONS.md): pure offline verification cannot prove non-revocation). Not that the bound policy achieves its intended outcome ([LIMITATIONS](../LIMITATIONS.md): policy correctness is a separate control). Not soundness of the model's behavior ([§2.4](../spec/trace-v0.2.md)). Not success of any physical or business outcome ([§3.3.3](../spec/trace-v0.2.md)). Not a general trust score for the subject. | The record itself; the trusted-key thumbprint used; the freshness bounds applied; if revocation was checked, the bundle identity and `valid_until` - reported as "verified against revocation bundle valid at T" ([§3.2.3](../spec/trace-v0.2.md)); the complete `appraisal`. | Re-verify before material reliance on an aging record; consult current revocation status at decision time; keep the `policy_ref` target resolvable for whatever retention the published statement promises. |
| **Honestly downgraded, floor still met.** Evidence the claimed depth needs did not resolve; the verifier stopped lower and recorded the achieved depth in `appraisal.provenance_depth_verified`; nothing contradicted; the achieved depth still meets the configured floor. | Verification reached `builder` (for example); the deeper claim was left unverified because named evidence did not resolve; the unresolved evidence is identified. ([§3.3.1](../spec/trace-v0.2.md), [downgrade column of the depth table](verification.md#verifying-build-provenance-depth)) | The downgrade is not a defect in the record: "a record is not defective because someone else's transparency log is unreachable" ([verification.md](verification.md#verifying-build-provenance-depth)). Equally, nothing is assured at the unexecuted depth - unverified is not silently true ([what each stopping point leaves unknown](build-provenance-depth.md)). | `appraisal.provenance_depth_verified`; the evidence locations attempted and why each did not resolve; the configured floor; the final `appraisal`. | Retry resolution or obtain the evidence out-of-band; where the gap matters to the reader, state it explicitly instead of letting the affirming status carry the weight. |
| **Resolved and contradicted.** Named evidence resolves and refutes the record - attestation subject mismatch, dependency publisher outside the trusted set, digest mismatch. The appraisal fails and is not downgraded to escape the contradiction; `appraisal.status` is `contraindicated`. | The named artifact resolves and contradicts this record's claim at the named depth; the appraisal failed, and the record is treated as untrusted. ([§3.3.1](../spec/trace-v0.2.md), [Step 5 status meanings](verification.md#step-5--appraise-the-claims)) | The statement covers the specific contradiction observed and nothing else: it does not extend to other fields or axes, and appraisal output does not identify which adversary class from [§2.2](../spec/trace-v0.2.md) caused the contradiction. | The contradicting artifacts themselves (attestation bytes, publisher attestations, issuer identities outside the trusted set), the comparisons performed, the final `appraisal`. | Treat as incident input; contact the issuer or operator; corrected records need re-issue, not re-reading. |
| **Unresolvable below the floor.** Achieved `appraisal.provenance_depth_verified` sits below the deployment profile floor, so `appraisal.status` is `contraindicated` by the floor rule. Nothing resolved-and-contradicted. | Verification reached `surface`; this deployment's floor is `transitive`; the record is refused by the floor, with no contradicting evidence found. ([floor rule in §3.3.1](../spec/trace-v0.2.md)) | Unverifiable is not incriminating: "the strictness lives in the floor, not in a finding against the record" ([build provenance depth](build-provenance-depth.md#choosing-a-depth)). Do not report this as though evidence had contradicted the record - the depth vectors encode the two outcomes separately ([verification.md](verification.md#verifying-build-provenance-depth)), and reporting should keep them apart. | The attempted evidence locations and errors; the floor configuration; `appraisal.provenance_depth_verified`; `appraisal.status`. | Obtain the missing evidence, or let the rejection stand; record which of the two happened. |
Expand Down
2 changes: 1 addition & 1 deletion docs/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ For action receipts, a verifier should distinguish five common outcomes:
| `receipt_valid_rejected` | The receipt is well-formed, trusted, bound to the call, and reports controller or policy rejection. This is valid negative evidence. |
| `receipt_missing_required` | The profile required a receipt, but none was present for the consequential action. |
| `receipt_invalid` | The receipt is present but fails signature, digest, freshness, ordering, or call-binding checks against a key the verifier holds. |
| `receipt_unverified` | The receipt names an issuer key the verifier has not pinned, and nothing else failed. Per section 3.3.1 of the spec this is unverified, not invalid: the receipt confers no trust and proves no wrongdoing, surfaced with an advisory rather than a failure. |
| `receipt_unverified` | The receipt names an issuer key the verifier has not pinned, and nothing else failed. Per section 3.3.2 of the spec this is unverified, not invalid: the receipt confers no trust and proves no wrongdoing, surfaced with an advisory rather than a failure. |

The key boundary is that a valid rejection is not malformed evidence. It is
evidence that the downstream authority declined the action. A valid acceptance
Expand Down
6 changes: 3 additions & 3 deletions examples/action-receipts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ only: the JSON snippets are not TRACE Trust Records and are not validated by
`schema/trace-claim.json`.

The examples exercise the boundary from
[spec section 3.3.2](../../spec/trace-v0.2.md#332-action-receipts-for-embodied-workflows-informative):
[spec section 3.3.3](../../spec/trace-v0.2.md#333-action-receipts-for-embodied-workflows-informative):

1. session evidence verifies the Trust Record and committed transcript;
2. action issuance evidence verifies that a consequential action request was
Expand Down Expand Up @@ -93,7 +93,7 @@ three operations:
| `11-call-id-mismatch.json` | `receipt_invalid` | unknown | An authentic receipt bound to a different call. |
| `12-session-id-mismatch.json` | `receipt_invalid` | unknown | An authentic receipt from a different session. |
| `13-evidence-hash-mismatch.json` | `receipt_invalid` | unknown | The receipt is authentic but the detached evidence was swapped after signing. |
| `14-receipt-issuer-key-unknown.json` | `receipt_unverified` with advisory | unknown | The issuer key is not in the verifier's pinned set. Unverifiable is not invalid (spec §3.3.1): no trust is conferred and no forgery is proven, surfaced as an `issuer_key_unknown` advisory. |
| `14-receipt-issuer-key-unknown.json` | `receipt_unverified` with advisory | unknown | The issuer key is not in the verifier's pinned set. Unverifiable is not invalid (spec §3.3.2): no trust is conferred and no forgery is proven, surfaced as an `issuer_key_unknown` advisory. |
| `15-receipt-from-future.json` | `receipt_invalid` | unknown | Issued after the verification time, so an upper bound on age never rejects it. |
| `16-decision-not-in-enum.json` | `receipt_invalid` | unknown | An unrecognised decision verb, which must not read as accept or reject. |
| `17-missing-receipt-explicit-null.json` | `receipt_missing_required` | unknown | The receipt supplied as an explicit `null`: 03's absence through a different door, misread by presence-checking implementations. |
Expand All @@ -116,7 +116,7 @@ previously exercised. Every one was a check a conforming implementation could ha
omitted entirely while passing this set. Two matter beyond tidiness: without
`issuer_key_unknown` a receipt authenticates itself — a signature verifies against
whatever key it names, and only the pinned set decides which keys the verifier can
check at all; per spec §3.3.1 the outcome is `receipt_unverified`, not
check at all; per spec §3.3.2 the outcome is `receipt_unverified`, not
`receipt_invalid`, but a verifier that never consults its pinned set would report such
a receipt as fully valid, which is what the vector distinguishes. Without
`evidence_hash_mismatch` the signature covers a digest whose document may have been
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def main() -> None:
f["evidence"]["terminal_state"] = "rejected" # after the hash was taken
fixtures.append(("13-evidence-hash-mismatch.json", f))

# issuer_key_unknown — the one non-failure in this set. Spec section 3.3.1: a
# issuer_key_unknown — the one non-failure in this set. Spec section 3.3.2: a
# receipt whose issuer key is unknown to the verifier is unverified, not invalid.
# An unpinned key means the signature cannot be checked, which confers no trust and
# proves no forgery, so the expected block is written by hand rather than through
Expand Down
2 changes: 1 addition & 1 deletion spec/trace-v0.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ requirement, such as `required`, `optional`, or `none`, without folding action
evidence into the supply-chain provenance axis.

An action receipt profile can build on the external execution evidence rules in
section 3.3.1 by requiring the verifier to:
section 3.3.2 by requiring the verifier to:

1. recompute the receipt's action or evidence digest from the canonical action
preimage;
Expand Down
2 changes: 1 addition & 1 deletion tests/test_action_receipt_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _evidence_hash_mismatch(f: dict[str, Any]) -> bool:


def _issuer_key_unknown(f: dict[str, Any]) -> bool:
# Spec section 3.3.1: a receipt whose issuer key is unknown to the verifier is
# Spec section 3.3.2: a receipt whose issuer key is unknown to the verifier is
# unverified, not invalid. An unpinned key is an inability to check, not
# evidence of forgery, so this is an advisory rather than a failure; the
# structural checks still run, and any of them failing is positive evidence
Expand Down
Loading