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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,35 @@ Format: [Semantic Versioning](https://semver.org/). Spec versions follow `MAJOR.
`no_check_performed` when no revocation store is supplied from `verified` when
the supplied store was checked and clean for this call. Revoked-key refusal is
unchanged. No new revocation source or bundle support is introduced.
- **BREAKING: a verifier that reports `affirming` after a section 3.3 step 4 reference
match, while its appraisal policy requires a layer, must now establish that layer in
`appraisal.platform_measurement` (section 3.1.5, rule 2, #279).** Reporting `affirming`
with the block absent, or with a required layer not established or not listed, is no
longer conformant. Records do not change: the schema change is one new optional member
of `appraisal`, and no existing document changes verdict. v0.2 is a Draft, so the
30-day period in GOVERNANCE does not apply.
- **`appraisal.platform_measurement`: what a matching measurement covers, layer by layer
(section 3.1.5, #279).** A matching composite `runtime.measurement` does not say which
layers recorded anything, which were appraised before they ran, or whether the evidence
describes one boot, and the record could say only `appraisal.status: none` about it. A
verifier now writes, per layer, `established` or `not-established` with one of three
reasons: `layer-not-measured`, `measured-not-appraised` or `evidence-spans-multiple-boots`.
The first two are what a digest comparison reports as a pass; the third is what a replay
check reports as tampering. A layer the result does not list is not established, nor is
any layer of a record without the block; a replay mismatch the verifier cannot attribute
to one boot is not, on that basis alone, `contraindicated`; and a record carrying a result
does not report `status: none`. The block is a member of `appraisal` in its own right,
not an `appraisal.method` value, so it sits beside a re-execution result; its `measurement`
equals `runtime.measurement`, a `reason` is never `null`, and on `tpm2` a layer is named
`pcr:<n>` for a register from 0 to 23. Schema, packaged copy, reference model and
`docs/schema.md` carry it; the v0.3 draft schema is regenerated.
`tests/test_safe_integer_range.py` now admits a map whose every member is one object
schema, which it then holds to the same rule. Forty-four signed vectors in
`examples/platform-measurement-appraisal/`: five case-and-twin pairs, two records carrying
measurements from published quotes on a board with a discrete TPM, at least two
rejections per rule, and an accepting twin for every rejection that differs from it only
in the rule it tests. Proposed and written by @revenue7-eng in #279; carried by
@imran-siddique as sponsoring maintainer.

- **`verify_record` reports what a caller-supplied appraiser found about `runtime.measurement`,
per layer (#279 platform-measurement row, #431).** A matching composite measurement says
Expand Down
17 changes: 17 additions & 0 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Verifier judgment on the evidence in this record.
| `provenance_depth_verified` | string | no | Depth this verifier actually ran: `surface`, `builder` or `transitive` |
| `method` | string | no | The method this appraisal used. A closed set, because a verifier keys on it; this version defines `re-execution`, the result of re-running the record's `reproducibility` claim. `status` is untouched by it: the outcome is not folded into the EAR set |
| `re_execution` | object | when `method` is `re-execution` | The re-execution result, described below. Present exactly when `method` is `re-execution`, and absent otherwise |
| `platform_measurement` | object | no | Per-layer appraisal of `runtime.measurement`, described below. A member of `appraisal` in its own right, not a `method` value, so it can sit next to a re-execution result |

### `appraisal.re_execution` members {#trace-field-appraisal-re-execution}

Expand All @@ -219,6 +220,22 @@ The result of re-running a `reproducibility` claim, attributed to the party name
| `reason` | string | when `outcome` is `not-attempted` | Why no outcome could be reported. A named absence and a generic one are different findings |
| `verifier_code_identity` | string | no | Digest of the verifier's own implementation. Self-asserted and of no weight singly; a correlation key across results, since two verifiers at different implementations disagreeing over one closure is verifier drift |

### `appraisal.platform_measurement` members {#trace-field-appraisal-platform-measurement}

What a matching `runtime.measurement` covers, layer by layer, attributed to the party named as `verifier`. A composite that matches its reference does not say which layers recorded anything, which were appraised before they ran, or whether the evidence describes one boot; this block says it. `status` is untouched by it, except that a verifier does not report `affirming` while a layer its policy requires is not established or not listed, and a record carrying this block does not report `none`. Spec section 3.1.5.

| Field | Type | Required | Description |
|---|---|---|---|
| `measurement` | string | **yes** | The digest this appraisal is about. Must equal `runtime.measurement`: a result about another measurement is not about this record |
| `layers` | object | **yes** | At least one member, keyed by the platform's name for the layer. On `tpm2` the key is `pcr:` and a register number from 0 to 23 in decimal without leading zeros (`pcr:0`, `pcr:23`). Each member carries `outcome` and, when not established, `reason`. A layer not listed is not established, and a record without this block establishes no layer |

Each member of `layers`:

| Field | Type | Required | Description |
|---|---|---|---|
| `outcome` | string | **yes** | `established`, or `not-established`, which is never reported as `established` |
| `reason` | string | when `outcome` is `not-established` | `layer-not-measured`: the layer holds no measurement (its initial value, or on a TPM a separator and nothing else). `measured-not-appraised`: the measurements replay to the quoted value and nothing in the evidence shows they were appraised before they ran. `evidence-spans-multiple-boots`: the evidence does not establish that the quote and the event log describe the same single boot, and the log does not replay to the quoted value; not reported as tampering on that basis alone |

<a id="transparency"></a>

## `transparency` {#trace-field-transparency}
Expand Down
6 changes: 6 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ checking any archived record, including these.
result: two vectors per rule and five accepting records. Each file is a test-vector
envelope; the record is under `record`, and the digests the claim carries recompute
from the JSON under `context`. See that directory's README.
- `platform-measurement-appraisal/`: 44 signed Trust Records that pin the shape rules
spec section 3.1.5 states for `appraisal.platform_measurement`, two vectors per rule,
and what a relying party reads off an accepting record, layer by layer: five
case-and-twin pairs, an accepting twin for every rejection, and two records carrying
published measurements. Each file is a
test-vector envelope; the record is under `record`. See that directory's README.
- `canonicalization-boundary/`: three signed Trust Records that separate an
RFC 8785-conformant canonicalizer from `json.dumps(sort_keys=True)`, which
§3.2.2 requires and names as insufficient. Each file is a test-vector envelope;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"name": "layer-not-measured",
"description": "Synthetic. pcr:2 holds no measurement, so a match there confirms only that nothing was recorded. A reader that takes the matching composite as a pass reads it as established; it is not.",
"spec": "spec/trace-v0.2.md#315-appraisalplatform_measurement-what-a-matching-measurement-covers",
"synthetic": true,
"record": {
"eat_profile": "tag:agentrust-io.com,2026:trace-v0.2",
"iat": 1785000000,
"subject": "spiffe://edge.example/device/bench-001",
"model": {
"provider": "anthropic",
"model_id": "claude-sonnet-4-6"
},
"runtime": {
"platform": "tpm2",
"measurement": "sha256:e09f00095803b62c87420c503a6eed83f30be752bfc3b34cb48dde3d92e33d2a"
},
"policy": {
"bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"enforcement_mode": "enforce"
},
"data_class": "internal",
"build_provenance": {
"slsa_level": 0,
"digest": "sha256:00ea48600a3a4fcd35a35cea2296fcbe18cb2c92243ece1a5d1992d0dae41202"
},
"appraisal": {
"status": "warning",
"verifier": "https://verifier.example/platform-measurement",
"platform_measurement": {
"measurement": "sha256:e09f00095803b62c87420c503a6eed83f30be752bfc3b34cb48dde3d92e33d2a",
"layers": {
"pcr:0": {
"outcome": "established"
},
"pcr:1": {
"outcome": "established"
},
"pcr:2": {
"outcome": "not-established",
"reason": "layer-not-measured"
},
"pcr:3": {
"outcome": "established"
},
"pcr:4": {
"outcome": "established"
},
"pcr:5": {
"outcome": "established"
},
"pcr:6": {
"outcome": "established"
},
"pcr:7": {
"outcome": "established"
},
"pcr:8": {
"outcome": "established"
},
"pcr:9": {
"outcome": "established"
}
}
}
},
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "shaEEVtRR8nWCpp--gRa2Hu4AbHYpKKTdF3EuE5COU8"
}
},
"signature": "3DMAoBm5T6Qv_h_f2G8kJn-uIun6BNnG1PN3Nn9VgfnAYXEmDWKI1xlC0MQOmFumxhXJaocQfst3vZkouAZQBA"
},
"expected": {
"outcome": "accept",
"codes": [],
"reading": {
"pcr:2": {
"outcome": "not-established",
"reason": "layer-not-measured"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"name": "layer-not-measured-twin",
"description": "Twin of 01: the same record with a measured layer at pcr:2, so the layer is established. The only difference is that one condition.",
"spec": "spec/trace-v0.2.md#315-appraisalplatform_measurement-what-a-matching-measurement-covers",
"synthetic": true,
"twin_of": "01",
"record": {
"eat_profile": "tag:agentrust-io.com,2026:trace-v0.2",
"iat": 1785000000,
"subject": "spiffe://edge.example/device/bench-001",
"model": {
"provider": "anthropic",
"model_id": "claude-sonnet-4-6"
},
"runtime": {
"platform": "tpm2",
"measurement": "sha256:e09f00095803b62c87420c503a6eed83f30be752bfc3b34cb48dde3d92e33d2a"
},
"policy": {
"bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"enforcement_mode": "enforce"
},
"data_class": "internal",
"build_provenance": {
"slsa_level": 0,
"digest": "sha256:00ea48600a3a4fcd35a35cea2296fcbe18cb2c92243ece1a5d1992d0dae41202"
},
"appraisal": {
"status": "warning",
"verifier": "https://verifier.example/platform-measurement",
"platform_measurement": {
"measurement": "sha256:e09f00095803b62c87420c503a6eed83f30be752bfc3b34cb48dde3d92e33d2a",
"layers": {
"pcr:0": {
"outcome": "established"
},
"pcr:1": {
"outcome": "established"
},
"pcr:2": {
"outcome": "established"
},
"pcr:3": {
"outcome": "established"
},
"pcr:4": {
"outcome": "established"
},
"pcr:5": {
"outcome": "established"
},
"pcr:6": {
"outcome": "established"
},
"pcr:7": {
"outcome": "established"
},
"pcr:8": {
"outcome": "established"
},
"pcr:9": {
"outcome": "established"
}
}
}
},
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "shaEEVtRR8nWCpp--gRa2Hu4AbHYpKKTdF3EuE5COU8"
}
},
"signature": "5IcGR5xaMxWsj9WhK4Na-MsXY7VI-NfzfB27TJU_RgruvBAn0e43M8C1xLtVRDT47oa_BSgLAEtTnmync0dvDg"
},
"expected": {
"outcome": "accept",
"codes": [],
"reading": {
"pcr:2": {
"outcome": "established"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"name": "measured-not-appraised",
"description": "Synthetic. pcr:2's measurements replay to the quoted value and nothing in the evidence shows they were appraised before they ran. Not a finding that appraisal was off.",
"spec": "spec/trace-v0.2.md#315-appraisalplatform_measurement-what-a-matching-measurement-covers",
"synthetic": true,
"record": {
"eat_profile": "tag:agentrust-io.com,2026:trace-v0.2",
"iat": 1785000000,
"subject": "spiffe://edge.example/device/bench-001",
"model": {
"provider": "anthropic",
"model_id": "claude-sonnet-4-6"
},
"runtime": {
"platform": "tpm2",
"measurement": "sha256:2642b489b77585eb684f423df157d47ed4ed5fefaf87d88e75464a9ee6e5745a"
},
"policy": {
"bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"enforcement_mode": "enforce"
},
"data_class": "internal",
"build_provenance": {
"slsa_level": 0,
"digest": "sha256:00ea48600a3a4fcd35a35cea2296fcbe18cb2c92243ece1a5d1992d0dae41202"
},
"appraisal": {
"status": "warning",
"verifier": "https://verifier.example/platform-measurement",
"platform_measurement": {
"measurement": "sha256:2642b489b77585eb684f423df157d47ed4ed5fefaf87d88e75464a9ee6e5745a",
"layers": {
"pcr:0": {
"outcome": "established"
},
"pcr:1": {
"outcome": "established"
},
"pcr:2": {
"outcome": "not-established",
"reason": "measured-not-appraised"
},
"pcr:3": {
"outcome": "established"
},
"pcr:4": {
"outcome": "established"
},
"pcr:5": {
"outcome": "established"
},
"pcr:6": {
"outcome": "established"
},
"pcr:7": {
"outcome": "established"
},
"pcr:8": {
"outcome": "established"
},
"pcr:9": {
"outcome": "established"
}
}
}
},
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "shaEEVtRR8nWCpp--gRa2Hu4AbHYpKKTdF3EuE5COU8"
}
},
"signature": "YwT0zJpR6Ax4h4TL76e5Qn8z3CyMUYir17WR86sEiyG02ivicREoYcvXpy_euxVhB0NB69_zFszyrUULMhKtDA"
},
"expected": {
"outcome": "accept",
"codes": [],
"reading": {
"pcr:2": {
"outcome": "not-established",
"reason": "measured-not-appraised"
}
}
}
}
Loading
Loading