Skip to content

feat(verify): record a caller-supplied appraiser's per-layer report on runtime.measurement (#279, #431) - #457

Merged
imran-siddique merged 3 commits into
agentrust-io:mainfrom
revenue7-eng:platform-measurement-outcomes
Oct 5, 2026
Merged

imran-siddique merged 3 commits into
agentrust-io:mainfrom
revenue7-eng:platform-measurement-outcomes

Conversation

@revenue7-eng

Copy link
Copy Markdown
Contributor

Implements the platform-measurement row of #279, in the shape agreed in #431.

What it does

verify_record has runtime.platform and one digest, runtime.measurement. For a measured-boot platform that digest is a composite over many layers, and a match says nothing about which layers were measured, which were appraised, or whether the evidence describes one boot. verify_record never sees the quote, log or reference values, so it cannot decide that itself.

Following citation.py, this adds agentrust_trace.platform_measurement and a platform_appraiser argument to verify_record. The appraiser is caller-supplied, called last with a copy of runtime, and returns the measurement it appraised and a status per layer. The result's new platform_measurement field carries the report:

  • appraised, with a LayerCheck per layer: established, or not_established with layer_not_measured, measured_not_appraised or evidence_spans_multiple_boots;
  • appraisal_rejected with appraiser_raised, appraiser_returned_invalid or measurement_mismatch (a report about another measurement is never attached);
  • not_attempted with no_appraiser when none is supplied.

The module asserts nothing about the platform, derives no layer outcome, upgrades nothing, and produces no appraisal.status. No outcome moves revocation, the thumbprint, citations or whether verify_record raises. The names are not accepted normative text. No schema or wire-format change.

The two conditions from #431

  1. Each cause has a fixture that produces it and a twin that differs only in that condition and does not. Sixteen signed vectors in examples/platform-measurement/: seven causes, each with a twin_of vector carrying the same signed record and the same context except the appraisal table. test_every_cause_has_a_twin_that_differs_only_in_its_condition fails if a cause loses its twin or a twin differs in anything else. Every vector without a source carries "synthetic": true; evidence_spans_multiple_boots exists only in the synthetic pair 013/014, because the board behind the real vectors resets its TPM in SPL and does not produce it.
  2. not_attempted stays distinct from a pass wherever the result is summarised. Nothing in this repository summarises a VerificationResult today; the rule is stated in the module docstring and held by test_P4b_not_attempted_is_never_a_pass, so a future summary cannot collapse it.

Evidence

Vectors 015 and 016 carry measurements from published quotes on a physical board with a discrete TPM (Rock 5A, Infineon SLB9670), registered AK:

Nothing in this repository reads that evidence; the vectors carry the appraiser's report as the input.

Checks

pytest (3406 passed, 48 skipped), ruff check src tests scripts, tools/check_dashes.py. The generator reproduces the set byte-for-byte and is picked up by test_generators_reproduce_fixtures; the set is registered in test_adequacy_all_sets and the new function and parameter in test_public_functions_raise_what_they_document.

@revenue7-eng
revenue7-eng requested review from a team, lywinged and rajnisht7 as code owners October 2, 2026 20:28
@imran-siddique

Copy link
Copy Markdown
Member

@revenue7-eng CI's Type check step fails on test (3.11), and test (3.12) was cancelled behind it:

src/agentrust_trace/platform_measurement.py:149: error: Argument 1 to "deepcopy" has incompatible type "Any | None"; expected "dict[str, Any]"

mypy does not narrow runtime from the measurement is None test. Changing line 145 to if not isinstance(runtime, dict) or measurement is None: clears it with the same behaviour; I checked locally that mypy src/agentrust_trace is clean and the 51 tests in test_platform_measurement.py pass. CI runs mypy alongside pytest and ruff. The review follows a green run.

@revenue7-eng

Copy link
Copy Markdown
Contributor Author

The 3.11 job failed at the mypy step: runtime was not narrowed to a dict before copy.deepcopy. Fixed in the latest commit; ruff, check_dashes, mypy (2.3.1, as pinned) and pytest pass locally on it.

…n runtime.measurement (agentrust-io#279, agentrust-io#431)

Signed-off-by: Andrey Lazarev <lazarev@tactiqedge.com>
Signed-off-by: Andrey Lazarev <lazarev@tactiqedge.com>
@revenue7-eng

revenue7-eng commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (after #462), per @lywinged's note there; the gate now reports "awaiting 1 independent maintainer approval" for 77c6562 instead of the HttpError. Both commits keep their DCO sign-off.

@imran-siddique

Copy link
Copy Markdown
Member

@revenue7-eng CI is green on 77c6562 and both #431 conditions hold. One gap: an appraiser returning {"measurement": <the record's>, "layers": {}} comes back appraised with zero layers, so a report that established nothing reads as an appraisal. Refuse an empty layers as appraiser_returned_invalid with member="layers", and add it beside the "extra": 1 case in test_platform_measurement.py. This merges once that is green.

…alid

Signed-off-by: Andrey Lazarev <lazarev@tactiqedge.com>
@revenue7-eng

Copy link
Copy Markdown
Contributor Author

Done in the latest commit: an empty layers is now appraiser_returned_invalid with member="layers", and the case sits beside "extra": 1 in test_platform_measurement.py.

@imran-siddique imran-siddique left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty layers case is refused as appraiser_returned_invalid with the regression beside "extra": 1, which was the one remaining condition.

@imran-siddique
imran-siddique merged commit 2a72793 into agentrust-io:main Oct 5, 2026
10 of 11 checks passed
@revenue7-eng
revenue7-eng deleted the platform-measurement-outcomes branch October 5, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants