Skip to content

docs(schema): name both digest algorithms wherever the schema admits both - #438

Merged
imran-siddique merged 3 commits into
agentrust-io:mainfrom
opento-suggestions:docs/digest-algorithms-in-schema-reference
Oct 13, 2026
Merged

imran-siddique merged 3 commits into
agentrust-io:mainfrom
opento-suggestions:docs/digest-algorithms-in-schema-reference

Conversation

@opento-suggestions

@opento-suggestions opento-suggestions commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

All thirteen digest fields in schema/trace-claim.json carry the same pattern shape, ^sha(256:[0-9a-f]{64}|384:[0-9a-f]{96})$, so each takes sha256: or sha384:. Seven of the thirteen rows on docs/schema.md described their field more narrowly than that. model.weights_digest, policy.bundle_hash, tool_transcript.hash and build_provenance.digest named sha256: alone; the two appraisal.re_execution digests and appraisal.platform_measurement.measurement named no algorithm. The other six named both, in three different forms: four as sha256: or sha384:, delegation.parent_record_hash as sha256:/sha384:, and runtime.measurement by naming the platform each algorithm belongs to. The corrected rows take the form four of them already use.

The page under-describes a constraint whose source it names in its own opening line, schema/trace-claim.json. A producer reading one of the narrower rows writes the narrower record, and a verifier author reading one may refuse a record the schema accepts. Section 3.1.3 is where the specification settles a question of exactly this kind: having given the sha256: construction for delegation.parent_record_hash, it adds with sha384: and SHA-384 as the permitted alternative, per the schema pattern, which is the specification treating the pattern as the authority on which algorithms a field admits.

tests/test_schema_doc_coverage.py could not catch it. The file guards that every schema field has a row in its own section and that every enum and const value appears in its field's row. It never read what a row claims about a pattern, so a row could document a field into existence and still misdescribe it. It now also fails when a row does not name every algorithm its field's pattern admits, and the failure names the field and the missing algorithm.

That boundary is deliberate and it has a history worth naming rather than leaving a reviewer to find. The file arrived in #365, which answered the coverage half of #247, and the scope recorded there is field-and-enum coverage checked directly against the page. This change widens that scope. I am not arguing it does not: I am arguing the widened edge is still on the decidable side of the line the docstring draws, and the module docstring moves with it so the file says where its edge now is. The docstring's reason for not checking descriptions is that whether prose agrees with the spec is not mechanically decidable, and a guard reaching past what it can decide produces failures nobody can act on. That reason holds and is left standing. It does not cover this case: agreement with the field's own pattern is a comparison against a machine-readable constraint rather than a judgement about wording, which is why the failure can name the field and the missing algorithm instead of asking a reader to adjudicate. The docstring now records the distinction, rather than leaving the file opening with a disclaimer of a check it performs.

The check offers the pattern a value rather than searching its text. The alternation factors the prefix out, so the pattern contains neither sha256 nor sha384 as text; a search for either finds no algorithm, concludes the field constrains none, and skips it. sha512: is offered too, so the check is known to be capable of a negative, and every one of the thirteen refuses it.

The field set comes from the walk this file already uses rather than a second enumeration. Resolving each field's subschema is a separate lookup that follows properties and items only, while that walk also descends if, then and allOf; a member declared solely inside a conditional branch therefore raises here rather than being passed over, so the day one appears this fails loudly instead of exempting the field without saying so.

Evidence that the guard fires. Reverting each of the seven corrected rows one at a time reds it, and each time the report names that field alone. That includes build_provenance.digest, which shares the member name digest with references[].digest and reproducibility.input_closure[].digest, so attribution goes through the file's own SECTION_OF_PATH map rather than a bare name lookup.

Stated limitations. Editorial only: no schema, wire format, normative text or code behaviour changes, and no record the schema accepts stops being accepted. The corrected rows describe what the schema already admitted, so this grants a producer no new latitude. The guard covers the schema reference page alone; docs/integration/agt.md and docs/integration/sandbox-runtime.md also mention SHA-256 against these fields, but as statements about what one integration emits rather than about what the field admits, so they are correct as they stand and are left alone. runtime.measurement already named both and is untouched. Section 3.2.1's SHA-256 minimum; SHA-384 required for FIPS-aligned profiles is not relied on here: no FIPS-aligned profile is defined in this repository, and that bullet sits among the envelope signing rules, so it is context for why both algorithms exist rather than grounds for this change. One limit of the check itself, since it is not visible from the diff: sha256, sha384 and sha512 are the three values it offers a pattern, so a fourth algorithm added to a pattern later would be admitted by the schema and not seen here. The check reads the pattern rather than trusting a list, which is why sha512: can be offered and refused by all thirteen, but what it can offer is bounded by that constant. It sits at the top of the file under its own name for that reason.

Merging main. #437 landed first and its CHANGELOG.md bullet conflicted with this one at the same anchor, as expected; the merge keeps both, main's entries first. Main also brought #477, which added appraisal.platform_measurement.measurement after this branch was cut, with the same pattern as the other digest fields and a row naming no algorithm. The guard reddened on it after the merge and named that field alone, which is the case it was written for, and the row is corrected in its own commit. The counts above are those of the merged tree; the first commit's message says twelve and six because that was true of the tree it was cut from.

Verification was run on Windows 10 with Python 3.13.5 (CI's 3.11 and 3.12 were not run), against 63f4d15: 5 failed, 2390 passed, 6 skipped at the untouched commit, and 5 failed, 2394 passed, 6 skipped after, the same five red in both. They are the known Windows generator failures, which reproduce on the untouched tree and come from write_text giving CRLF where the fixture comparison is byte-for-byte:

tests/test_generators_reproduce_fixtures.py::test_generator_reproduces_its_committed_fixtures[conformance]
tests/test_generators_reproduce_fixtures.py::test_generator_reproduces_its_committed_fixtures[gap-disclosure]
tests/test_generators_reproduce_fixtures.py::test_generator_reproduces_its_committed_fixtures[canonicalization-boundary]
tests/test_generators_reproduce_fixtures.py::test_generator_reproduces_its_committed_fixtures[delegation-link]
tests/test_generators_reproduce_fixtures.py::test_generator_reproduces_its_committed_fixtures[verifier-compatibility]

ruff check src tests scripts passes at 0.16.9, which is the pin in requirements/dev.txt; mypy src/agentrust_trace is clean under strict; tools/check_dashes.py is clean. After the merge (head b1251d6, main at 4c7d6ec), the steps of ci.yml were run on Linux: Ubuntu 24.04 arm64 in a container, dependencies from the hash-pinned requirements/dev.txt as CI installs them, Node 24.21.0 for the independent verifier. Python 3.11.17 and 3.12.15 each: 3843 passed, 48 skipped, none failed. Every skip is a test that needs a cross-repository dependency or a saved fixture not in this repository. ruff, mypy and tools/check_dashes.py are clean on both.

Type of change

  • Editorial (typo, link fix, clarification: no normative effect)
  • Non-breaking spec change (new optional field, new platform profile, informative addition)
  • Breaking spec change (requires 14-day comment period and Project Lead sign-off)
  • Schema change
  • Example addition

The editorial box covers the seven rows. The new guard in tests/ has no box: it adds a test, not a requirement.

Spec section

No section changes. Section 3.1.3 is cited as the place the specification treats the schema pattern as the authority on a field's permitted digest algorithms. The Authority and conformance claims section, which is unnumbered and precedes section 1, is why this is offered as a defect in a supporting artifact rather than as a proposal: docs/*.md explains the specification and does not carry requirements of its own, so a page that under-describes a constraint is corrected rather than argued.

Checklist

  • DCO sign-off on all commits (git commit -s)
  • CHANGELOG.md updated (for any normative change): updated, though there is no normative change; the entry records the corrected rows and the new guard
  • Breaking changes marked with <!-- CHANGED: #NNN: description --> in spec text: no spec text changes
  • Backward compatibility statement included (for breaking changes): not a breaking change; the rows now describe what the schema already accepted, so no record changes status

Tool-assisted: the build, the witness rounds and this description. I answer for all of it.

…both

All twelve digest fields in schema/trace-claim.json carry the same
pattern shape, ^sha(256:[0-9a-f]{64}|384:[0-9a-f]{96})$, so each takes
sha256: or sha384:. Six of the twelve rows on the schema reference page
described their field more narrowly: model.weights_digest,
policy.bundle_hash, tool_transcript.hash and build_provenance.digest
named sha256: alone, and the two appraisal.re_execution digests named no
algorithm. The other six named both, four of them in the form the
corrected rows now use.

A producer reading one of the narrower rows writes the narrower record,
and a verifier author reading one may refuse a record the schema
accepts. Section 3.1.3 is where the specification settles a question of
this kind: after giving the sha256: construction for
delegation.parent_record_hash it names SHA-384 as the permitted
alternative, per the schema pattern. The page under-described a
constraint its own opening names the schema as the source of.

test_schema_doc_coverage.py guarded that every field has a row and every
fixed value appears in its field's row. It never read what a row claims
about a pattern, so a row could document a field into existence and
misdescribe it. It now also fails when a row does not name every
algorithm its field's pattern admits.

The module docstring drew that boundary deliberately, on the ground that
whether a description agrees with the spec is not mechanically decidable.
This check does not cross it. Agreement with the field's own pattern is
a comparison against a machine-readable constraint, not a judgement
about prose, which is why the failure can name the field and the missing
algorithm. The docstring now records the distinction rather than leaving
the file disclaiming a check it performs.

The check asks the pattern with a probe value rather than searching its
text. The alternation factors the prefix out, so it contains neither
sha256 nor sha384 as text; a search for either finds no algorithm,
concludes the field constrains none and skips it. The first version did
that and reported no gaps anywhere. A test pins the behaviour.

The field set comes from the walk this file already uses. Resolving each
field's subschema follows properties and items only, so a member
declared solely inside a conditional branch raises rather than being
passed over silently.

Reverting each corrected row in turn reds the guard and names that field
alone, including build_provenance.digest, which shares its member name
with two other digest fields.

Editorial: no schema, wire-format or normative change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: opento-suggestions <opentosuggestionsofficial@gmail.com>
imran-siddique
imran-siddique previously approved these changes Sep 30, 2026

@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.

Approved. Merging #437 left this conflicting on CHANGELOG.md only; a rebase onto main keeping both entries is all it needs, and I'll merge once it's green.

rajnisht7
rajnisht7 previously approved these changes Sep 30, 2026

@rajnisht7 rajnisht7 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.

Reviewed the head 7ca1d25, looks good, the six rows now correctly name both algorithms which the schema already admits and the new guard in test_schema_doc_coverage.py is also a good addition, probe-based rather than text search so it actually works with the factored pattern

a few non-blocking points:

  • _DIGEST_ALGORITHMS is hardcoded to sha256/384. Fine for now, but a future algorithm added to the pattern wouldn’t be detected until the constant is updated. Already acknowledged in the PR body.

  • The check only looks at pattern now issue for now as every digest field is pattern-only but if something ever moved to enum/oneOf/etc. this wouldnt catch it

@imran-siddique

Copy link
Copy Markdown
Member

@opento-suggestions both approvals stand, and the only thing between this and merge is a CHANGELOG.md conflict: the #190 CitationCheck entry landed in the same Unreleased block. Keeping both entries, main's first, resolves it. I did that locally on main at db953284 and tests/test_schema_doc_coverage.py passes 22 of 22. Could you merge main into the branch and push?

opento-suggestions and others added 2 commits October 12, 2026 15:34
…s-in-schema-reference

Conflict in CHANGELOG.md only: the agentrust-io#190 CitationCheck entry and the agentrust-io#324
MCP retry example entry landed in the same Unreleased block as this
branch's entry. Kept all three, main's first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: opento-suggestions <opentosuggestionsofficial@gmail.com>
…measurement

agentrust-io#477 added appraisal.platform_measurement.measurement after this branch
was cut. Its pattern is the same ^sha(256:[0-9a-f]{64}|384:[0-9a-f]{96})$
as the other digest fields, and its row named no algorithm, so merging
main reddened the guard this branch adds: the field and both algorithms
were named, and the counterfactual test failed because its baseline was
no longer clean.

The row now takes the form the other corrected rows use. That makes
thirteen digest fields, of which seven rows were narrower than the
pattern, and the CHANGELOG entry carries those counts. The commit
message of 7ca1d25 says twelve and six, which was true of the tree it
was cut from.

Reverting this row alone reds the guard and names that field alone.
sha512: is refused by its pattern, as by the other twelve.

Editorial: no schema, wire-format or normative change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: opento-suggestions <opentosuggestionsofficial@gmail.com>
@opento-suggestions

Copy link
Copy Markdown
Contributor Author

@imran-siddique merged main into the branch and pushed (b4ca325). The CHANGELOG.md conflict resolved as you described: all entries kept, main's first.

One more thing came in with main. #477 added appraisal.platform_measurement.measurement after this branch was cut, with the same digest pattern as the others and a row naming no algorithm. The guard this PR adds reddened on it after the merge and named that field alone, so b1251d6 corrects that row in the same form as the other corrected rows. The CHANGELOG.md entry and the description now say thirteen fields and seven rows, and reverting that row alone reds the guard again.

The push dismissed both approvals, so the gate is waiting on a re-review of b1251d6. The change since 7ca1d25 is the merge plus that one row and the counts. Verified on Ubuntu 24.04 with Python 3.11 and 3.12: 3843 passed, 48 skipped, none failed.

@rajnisht7 rajnisht7 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.

Reviewed the head b1251d6, changes looks good docs now matches what the schema actually allows for all thirteen digest fields

@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.

Re-approving at b1251d6. Since my 09-30 approval the branch gained the main merge, the appraisal.platform_measurement.measurement row the new guard caught, and the updated counts; the push dismissed the earlier approval, so this is not a second review round.

@imran-siddique
imran-siddique merged commit c08974c into agentrust-io:main Oct 13, 2026
13 of 16 checks passed
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.

3 participants