Skip to content

fix(models): transparency is optional below Level 2 - #109

Merged
imran-siddique merged 1 commit into
mainfrom
fix/transparency-optional-below-level-2
Jul 28, 2026
Merged

fix(models): transparency is optional below Level 2#109
imran-siddique merged 1 commit into
mainfrom
fix/transparency-optional-below-level-2

Conversation

@imran-siddique

Copy link
Copy Markdown
Contributor

Found while fixing microsoft/agent-governance-toolkit#3454, which could not construct a Trust Record at all.

Three artifacts disagreed, and the model was the outlier

Artifact On transparency
schema/trace-claim.json required, no minLength, so "" passes
agentrust-trace-tests runner TR-ANC runs at Level 2 only; its message reads "transparency field is required at Level 2"
models.py min_length=1 unconditionally

A Level 0 or Level 1 record is not anchored to a transparency log, so it has no receipt URI to name, and the model left that state unrepresentable. AGT hit it directly: its documented Phase 1 design emits an unanchored record, and after moving to 0.5.0 every emit raised ValidationError: transparency String should have at least 1 character.

The change

transparency: Annotated[str, Field(min_length=1)] | None = None

None 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. That distinction is what the second test pins.

The layering this restores is the one the suite already assumes: the model expresses structure, the conformance suite enforces level requirements. signature is already optional in the model for the same reason, with the spec requiring the binding.

Verification

91 pass. Confirmed against a real record that None and an omitted field both validate while "" raises, then confirmed AGT's own emitters produce a valid record against this build (156 governance tests, including the four that were failing).

Needs a 0.5.1 release: AGT pins >=0.5.0,<0.6.0 and cannot go green until this is on PyPI.

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) <noreply@anthropic.com>
@imran-siddique
imran-siddique merged commit e440fd0 into main Jul 28, 2026
8 checks passed
@imran-siddique
imran-siddique deleted the fix/transparency-optional-below-level-2 branch July 28, 2026 18:28
imran-siddique added a commit to microsoft/agent-governance-toolkit that referenced this pull request Jul 28, 2026
Both emitters hardcoded transparency="". agentrust-trace 0.5.0 rejects it,
so every TRACE record raised ValidationError and the agent-mesh and
docker-compose suites failed.

ADR-0032 chose the empty string for Phase 1 because SCITT anchoring is out
of scope there. None is the honest form of that same choice: a Level 0 or
Level 1 record has no receipt URI to name, and an empty string looks
populated while resolving to nothing. Conformance requires transparency at
Level 2 only, where TR-ANC runs, so a Phase 1 record stays conformant at
the level it claims.

The library was also stricter than the spec's own schema and conformance
suite; that is fixed in agentrust-trace 0.5.1
(agentrust-io/trace-spec#109), which this branch's pin already admits.

Two emitters, not one: trace_model.py and trace_sink.py both built the
field, and only running the tests surfaced the second.

Also moves the two verifier.agentrust.io test fixtures to
agentrust-io.com, which is the same wrong-domain correction as the profile
URI in this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
MohammadHaroonAbuomar pushed a commit to microsoft/agent-governance-toolkit that referenced this pull request Jul 30, 2026
* fix(trace): emit the TRACE v0.2 profile URI

AGT emitted tag:agentrust.io,2026:trace-v0.1. RFC 4151 permits a tag URI
only where the minting authority controlled the named domain on the date
in the URI, and agentrust.io was never controlled by the TRACE project;
it resolves to third-party parked addresses. The identifier asserted
authority over a name belonging to someone else.

TRACE v0.2 corrects it to tag:agentrust-io.com,2026:trace-v0.2 and
changes nothing else about the record format, so this is one constant
plus its tests.

ADR-0032 gets an amendment rather than an edit, per the ADR immutability
convention: the original Decision text and the v01 file name stay.

Records AGT has already emitted remain verifiable as v0.1 records against
agentrust-trace-tests 0.3.x, which stays published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* fix(deps): bump agentrust-trace to the v0.2-profile release

The emitter change alone failed CI:

  ValidationError: 1 validation error for TrustRecord
  eat_profile

Both packages pinned agentrust-trace>=0.2.0,<0.3.0, whose TrustRecord
Literal still requires the v0.1 profile, so emitting v0.2 could not
validate. Pins move to >=0.5.0,<0.6.0.

Also records the pin change in the amendment rather than editing the
References section, which cites v0.2.0 because that is what the ADR was
accepted against. That section is a historical record, not a statement of
the current pin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* fix(trace): emit transparency as None when unanchored

Both emitters hardcoded transparency="". agentrust-trace 0.5.0 rejects it,
so every TRACE record raised ValidationError and the agent-mesh and
docker-compose suites failed.

ADR-0032 chose the empty string for Phase 1 because SCITT anchoring is out
of scope there. None is the honest form of that same choice: a Level 0 or
Level 1 record has no receipt URI to name, and an empty string looks
populated while resolving to nothing. Conformance requires transparency at
Level 2 only, where TR-ANC runs, so a Phase 1 record stays conformant at
the level it claims.

The library was also stricter than the spec's own schema and conformance
suite; that is fixed in agentrust-trace 0.5.1
(agentrust-io/trace-spec#109), which this branch's pin already admits.

Two emitters, not one: trace_model.py and trace_sink.py both built the
field, and only running the tests surfaced the second.

Also moves the two verifier.agentrust.io test fixtures to
agentrust-io.com, which is the same wrong-domain correction as the profile
URI in this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* docs(adr): disclose the canonicalization break in the library jump

Review feedback from @MohammadHaroonAbuomar, and he is right.

The amendment said the jump "changes nothing else about the record format
... one constant and its tests". That is true of the specification change
and false of the library jump. AGT moves agentrust-trace 0.2.0 to 0.5.x,
which crosses 0.3.0, whose changelog reads:

  BREAKING: Canonicalization is now RFC 8785 (JCS). Trust records are NOT
  cross-verifiable with 0.2.0.

So records AGT signs after this bump cannot be verified by a 0.2.0-era
verifier: the signature covers a different byte sequence. Disclosed, along
with a second consequence he did not mention, that 0.3.0 also stopped
self-verifying from the embedded cnf.jwk by default, so anything verifying
AGT records by trusting the key inside them needs updating. 0.4.0 is
additive only and imposes nothing.

Also makes the version references precise. agentrust-trace 0.4.x (the
library) and agentrust-trace-tests 0.3.x (the conformance suite) are
different packages on different version lines, and citing one of each read
as an inconsistency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* refactor(trace): hoist the profile URI; correct stale install guidance

Review feedback from @MohammadHaroonAbuomar and Copilot, all of it fair.

The EAT profile URI was duplicated between trace_model.py and
trace_sink.py, and this PR had to edit six literals to move it, which is
the drift cost demonstrated rather than argued. It is now
TRACE_EAT_PROFILE in trace_model.py, imported by trace_sink.py. The tests
keep asserting the literal string on purpose: a test comparing against the
constant would pass even if the constant were changed wrongly, and this is
a wire-format value.

The ImportError guidance still told users to install
agentrust-trace>=0.2.0, which after the pin bump points at a version that
cannot satisfy the dependency and produces a confusing second failure. It
now names the real range.

Two ADR points:

The amendment heading used a double-spaced separator. I had believed that
was house style, but it is the convention in agent-manifest, not here; in
this repo it was the only heading of its kind, so Copilot was right and it
is normalized.

The References entry citing agentrust-trace v0.2.0 stays, because that is
what this ADR was accepted against and rewriting it would claim otherwise.
Annotated instead, so a reader is not sent to an uninstallable version, and
pointed at the amendment for what changed in between.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* fix(deps): raise the agentrust-trace floor to 0.5.1

@MohammadHaroonAbuomar caught a floor that admits a broken version. Both
pyprojects pinned >=0.5.0,<0.6.0 while the ADR states transparency=None
needs 0.5.1 or later. 0.5.0 still required a non-empty transparency, so any
resolve landing on it rejects the None this PR emits and reproduces the
exact ValidationError the PR exists to fix. CI passed only because it
happened to install the latest.

Verified rather than assumed: installed 0.5.0 in a clean venv and
model_validate rejects transparency=None; 0.5.1 accepts it.

Floors raised in agent-mesh and agent-governance-toolkit-core, and in the
runtime install hint at trace_sink.py:189, which quoted the same too-low
range. The ADR's two statements of the pin move with them and now say why
the floor is 0.5.1, so the next person does not relax it back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

---------

Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
SemTiOne pushed a commit to SemTiOne/agent-governance-toolkit that referenced this pull request Jul 30, 2026
* fix(trace): emit the TRACE v0.2 profile URI

AGT emitted tag:agentrust.io,2026:trace-v0.1. RFC 4151 permits a tag URI
only where the minting authority controlled the named domain on the date
in the URI, and agentrust.io was never controlled by the TRACE project;
it resolves to third-party parked addresses. The identifier asserted
authority over a name belonging to someone else.

TRACE v0.2 corrects it to tag:agentrust-io.com,2026:trace-v0.2 and
changes nothing else about the record format, so this is one constant
plus its tests.

ADR-0032 gets an amendment rather than an edit, per the ADR immutability
convention: the original Decision text and the v01 file name stay.

Records AGT has already emitted remain verifiable as v0.1 records against
agentrust-trace-tests 0.3.x, which stays published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* fix(deps): bump agentrust-trace to the v0.2-profile release

The emitter change alone failed CI:

  ValidationError: 1 validation error for TrustRecord
  eat_profile

Both packages pinned agentrust-trace>=0.2.0,<0.3.0, whose TrustRecord
Literal still requires the v0.1 profile, so emitting v0.2 could not
validate. Pins move to >=0.5.0,<0.6.0.

Also records the pin change in the amendment rather than editing the
References section, which cites v0.2.0 because that is what the ADR was
accepted against. That section is a historical record, not a statement of
the current pin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* fix(trace): emit transparency as None when unanchored

Both emitters hardcoded transparency="". agentrust-trace 0.5.0 rejects it,
so every TRACE record raised ValidationError and the agent-mesh and
docker-compose suites failed.

ADR-0032 chose the empty string for Phase 1 because SCITT anchoring is out
of scope there. None is the honest form of that same choice: a Level 0 or
Level 1 record has no receipt URI to name, and an empty string looks
populated while resolving to nothing. Conformance requires transparency at
Level 2 only, where TR-ANC runs, so a Phase 1 record stays conformant at
the level it claims.

The library was also stricter than the spec's own schema and conformance
suite; that is fixed in agentrust-trace 0.5.1
(agentrust-io/trace-spec#109), which this branch's pin already admits.

Two emitters, not one: trace_model.py and trace_sink.py both built the
field, and only running the tests surfaced the second.

Also moves the two verifier.agentrust.io test fixtures to
agentrust-io.com, which is the same wrong-domain correction as the profile
URI in this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* docs(adr): disclose the canonicalization break in the library jump

Review feedback from @MohammadHaroonAbuomar, and he is right.

The amendment said the jump "changes nothing else about the record format
... one constant and its tests". That is true of the specification change
and false of the library jump. AGT moves agentrust-trace 0.2.0 to 0.5.x,
which crosses 0.3.0, whose changelog reads:

  BREAKING: Canonicalization is now RFC 8785 (JCS). Trust records are NOT
  cross-verifiable with 0.2.0.

So records AGT signs after this bump cannot be verified by a 0.2.0-era
verifier: the signature covers a different byte sequence. Disclosed, along
with a second consequence he did not mention, that 0.3.0 also stopped
self-verifying from the embedded cnf.jwk by default, so anything verifying
AGT records by trusting the key inside them needs updating. 0.4.0 is
additive only and imposes nothing.

Also makes the version references precise. agentrust-trace 0.4.x (the
library) and agentrust-trace-tests 0.3.x (the conformance suite) are
different packages on different version lines, and citing one of each read
as an inconsistency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* refactor(trace): hoist the profile URI; correct stale install guidance

Review feedback from @MohammadHaroonAbuomar and Copilot, all of it fair.

The EAT profile URI was duplicated between trace_model.py and
trace_sink.py, and this PR had to edit six literals to move it, which is
the drift cost demonstrated rather than argued. It is now
TRACE_EAT_PROFILE in trace_model.py, imported by trace_sink.py. The tests
keep asserting the literal string on purpose: a test comparing against the
constant would pass even if the constant were changed wrongly, and this is
a wire-format value.

The ImportError guidance still told users to install
agentrust-trace>=0.2.0, which after the pin bump points at a version that
cannot satisfy the dependency and produces a confusing second failure. It
now names the real range.

Two ADR points:

The amendment heading used a double-spaced separator. I had believed that
was house style, but it is the convention in agent-manifest, not here; in
this repo it was the only heading of its kind, so Copilot was right and it
is normalized.

The References entry citing agentrust-trace v0.2.0 stays, because that is
what this ADR was accepted against and rewriting it would claim otherwise.
Annotated instead, so a reader is not sent to an uninstallable version, and
pointed at the amendment for what changed in between.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

* fix(deps): raise the agentrust-trace floor to 0.5.1

@MohammadHaroonAbuomar caught a floor that admits a broken version. Both
pyprojects pinned >=0.5.0,<0.6.0 while the ADR states transparency=None
needs 0.5.1 or later. 0.5.0 still required a non-empty transparency, so any
resolve landing on it rejects the None this PR emits and reproduces the
exact ValidationError the PR exists to fix. CI passed only because it
happened to install the latest.

Verified rather than assumed: installed 0.5.0 in a clean venv and
model_validate rejects transparency=None; 0.5.1 accepts it.

Floors raised in agent-mesh and agent-governance-toolkit-core, and in the
runtime install hint at trace_sink.py:189, which quoted the same too-low
range. The ADR's two statements of the pin move with them and now say why
the floor is 0.5.1, so the next person does not relax it back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>

---------

Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: SemTiOne <emphyst80@gmail.com>
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.

1 participant