From 45d0b3d07af36654e4e6a92f8b2b7fcdf6845c4c Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Tue, 28 Jul 2026 11:26:53 -0700 Subject: [PATCH] fix(models): transparency is optional below Level 2 The model required a non-empty transparency URI on every record. That was stricter than both of the repo's other normative artifacts: schema/trace-claim.json requires the field but sets no minLength, and the conformance suite runs TR-ANC at Level 2 only, where its own message reads "transparency field is required at Level 2". A Level 0 or Level 1 record is not anchored, so it has no receipt to name, and the model left that unrepresentable. AGT hit it: its documented Phase 1 design emits an unanchored record and could not construct one. None now means unanchored. Empty string stays rejected, deliberately: "" is not a URI, and a field that looks populated but resolves to nothing is worse in a trust record than an absent one. Co-Authored-By: Claude Opus 5 (1M context) --- src/agentrust_trace/models.py | 15 ++++++++++++++- tests/test_models.py | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/agentrust_trace/models.py b/src/agentrust_trace/models.py index d0270b1..9bb7b2b 100644 --- a/src/agentrust_trace/models.py +++ b/src/agentrust_trace/models.py @@ -157,7 +157,20 @@ class TrustRecord(BaseModel): delegation: Delegation | None = None build_provenance: BuildProvenance appraisal: Appraisal - transparency: Annotated[str, Field(min_length=1)] + transparency: Annotated[str, Field(min_length=1)] | None = None + """SCITT receipt URI resolving to the inclusion proof on the transparency log. + + Optional in the model, and required by conformance at **Level 2**, where + `TR-ANC` runs (`agentrust-trace-tests`). A Level 0 or Level 1 record is not + anchored, so it has no receipt to name, and the model must be able to + represent that. Enforcing a non-empty value here regardless of level made the + model stricter than both the conformance suite and `schema/trace-claim.json`, + which sets no minimum length. + + Use `None` for an unanchored record rather than an empty string: `""` is not a + URI, and a field that looks populated but resolves to nothing is worse than an + absent one. + """ cnf: ConfirmationKey signature: Annotated[str, Field(pattern=r"^[A-Za-z0-9_-]+$")] | None = None """Optional embedded signature (base64url, no padding) by the cnf key over the diff --git a/tests/test_models.py b/tests/test_models.py index 9970975..3689090 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -204,3 +204,28 @@ def test_delegation_extra_field_rejected() -> None: } with pytest.raises(ValidationError): TrustRecord.model_validate(data) + + +def test_transparency_optional_for_unanchored_records() -> None: + """A Level 0/1 record has no receipt to name, so its absence must be representable. + + Conformance requires `transparency` at Level 2, where TR-ANC runs. Requiring a + non-empty value in the model regardless of level made it stricter than both the + conformance suite and schema/trace-claim.json, and left an unanchored record + unrepresentable. + """ + data = _load("intel-tdx.json") + data.pop("transparency", None) + assert TrustRecord.model_validate(data).transparency is None + + data = _load("intel-tdx.json") + data["transparency"] = None + assert TrustRecord.model_validate(data).transparency is None + + +def test_transparency_rejects_empty_string() -> None: + """`None` means unanchored; `""` is a URI-shaped lie and stays rejected.""" + data = _load("intel-tdx.json") + data["transparency"] = "" + with pytest.raises(ValidationError): + TrustRecord.model_validate(data)