BREAKING CHANGE: no free text in the digested core - #337
Merged
Conversation
The draft excludes it (AAE -02 §2.5.2/§2.5.3) and the kernel still carried it. This closes the gap the other way round from the usual: the specification was ahead, the code follows. ## What moves `reason` leaves all three digested cores and every predicate entry inside them: app/enforcement/enforce_check.py verdict core, aae:enforce-core:v1 app/enforcement/ratify.py ratification core, aae:enforce-ratify-core:v1 sdk .../_ext_core.py AER decision core, aae:aer-core:v1 `_pred()` still returns six fields, because the response wants the reason. The new `_trace_for_core()` projects the five reproducible ones — predicate, field, value, bound, result — and only that projection goes into a core. Both ratify.py and _ext_core.py import it from the kernel rather than repeating the field list, so the set cannot drift apart. The AER core does not come along because of `reason` alone: it carries `enforce_version` as a field of its own, so the version bump would have broken it regardless. ## Why A reason string is written for a person reading a log. Two implementations do not have to agree on its wording, and nothing forces them to — so it has no place in a value they must agree on byte for byte. Leaving it in meant a verifier could reach the correct verdict from the correct inputs and still fail to reproduce the digest, for no reason a reader could act on. The reason itself is not lost: it stays on the verdict and on every predicate in the response, which is where a human reads it. ## The break, and how it is visible Every core_digest issued so far becomes unreproducible against this kernel. ENFORCE_VERSION, RATIFY_VERSION and AER_VERSION go 2.0 -> 3.0 and sit in their respective cores, so whoever holds a record marked 2.0 can see from the field that it needs the older package. Unaffected: action_binding, mandate_digest, transaction_digest, the evidence item and query digests, delegator_aae_hash, and moltrust:aae-verdict:v1 in the evaluator (a different machine, whose signatures live in the database). Verdicts and decisions themselves do not move. PERMIT stays PERMIT, PENDING still requires an explicit hold, forbid still outranks an allowing grant, and all three ratification guards behave as before. What changed is what gets digested, not what gets decided. ## SDK 0.4.0 -> 0.5.0 Both kernel copies regenerated from the server files rather than edited in parallel; test_core_parity holds the line-for-line identity. examples/aer/decision.json rebuilt with the bundled generator. The README banner and a BREAKING changelog entry carry the migration line. Corrected in passing: the changelog listed 0.4.0 as unreleased. It is on PyPI. ## Tests +3 cases. That a reason is reported but not digested, on the verdict core and on the ratification core; and that swapping the reason text on an identical decision leaves the digest untouched — which is the property the change exists for, and which no test could have stated before. Three tests encoded the old contract and are updated, not deleted: the core field sets, and the one asserting the core trace equals the response trace. It no longer does, and the test now says exactly how they differ. Core 143 passed, SDK 218 passed. The 14 errors are endpoint tests without fastapi, identical on origin/main. ruff findings identical to baseline. ## Not here The conformance vectors. All 26 expected core digests change, and they recompute against this kernel once it is merged — that is v1.4.0 in the vector repository, a separate step. No merge, no deploy, no publish.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The draft excludes it (AAE -02 §2.5.2/§2.5.3) and the kernel still carried it. This closes the gap the other way round from the usual: the specification was ahead, and the code follows.
What moves
reasonleaves all three digested cores, and every predicate entry inside them:app/enforcement/enforce_check.pyaae:enforce-core:v1app/enforcement/ratify.pyaae:enforce-ratify-core:v1sdk/…/_ext_core.pyaae:aer-core:v1_pred()still returns six fields, because the response wants the reason. A new_trace_for_core()projects the five reproducible ones —predicate,field,value,bound,result— and only that projection reaches a core.ratify.pyand_ext_core.pyimport it from the kernel rather than repeating the field list, so the three cannot drift apart.The AER core does not come along because of
reasonalone. It carriesenforce_versionas a field of its own, so the version bump would have broken it either way. Leaving it out would have produced a half-converted package.Why
A reason string is written for a person reading a log. Two implementations do not have to agree on its wording, and nothing forces them to — so it has no place in a value they must agree on byte for byte. With it in, a verifier could reach the correct verdict from the correct inputs and still fail to reproduce the digest, for no cause a reader could act on.
The reason is not lost. It stays on the verdict and on every predicate in the response, which is where a human reads it:
The break, and how it is visible
Every
core_digestissued so far becomes unreproducible against this kernel.ENFORCE_VERSION,RATIFY_VERSIONandAER_VERSIONgo2.0→3.0and sit in their respective cores, so whoever holds a record marked2.0sees from the field that it needs the older package.Unaffected:
action_binding,mandate_digest,transaction_digest, the evidence item and query digests,delegator_aae_hash, andmoltrust:aae-verdict:v1in the evaluator — a different machine, whose signatures live in the database.Verdicts and decisions do not move. PERMIT stays PERMIT, PENDING still needs an explicit hold,
forbidstill outranks an allowing grant, all three ratification guards behave as before. What changed is what gets digested, not what gets decided.SDK 0.4.0 → 0.5.0
Both kernel copies regenerated from the server files rather than edited in parallel;
test_core_parityholds the line-for-line identity (exactly one import line differing in each).examples/aer/decision.jsonrebuilt with the bundled generator. README banner and a BREAKING changelog entry carry the migration line.Corrected in passing: the changelog listed 0.4.0 as unreleased. It is on PyPI.
Tests
+3 cases. That a reason is reported but not digested, on the verdict core and on the ratification core. And the one the change exists for: swapping the reason text on an identical decision leaves the digest untouched — a property no test could have stated before, because it was false.
Three tests encoded the old contract and are updated, not deleted: the two core field sets, and the one asserting the core trace equals the response trace. It no longer does, and the test now says exactly how they differ.
origin/maintests/test_enforce_check.py+tests/test_ratify.pysdk/python/testsThe 14 errors are endpoint tests without
fastapiinstalled, the same in both runs.Published examples
developers.htmlcarries no concrete digest values — zero hits forsha256:[0-9a-f]{16}— so no web change is needed. The SDK README quoted the 0.4.0 break in its banner; that banner now describes this one.Not in this PR
The conformance vectors. All 26 expected core digests change, and they recompute against this kernel once it is merged — v1.4.0 in
aae-conformance-vectors, a separate step, because computing them against an unmerged kernel would pin values nobody can reproduce.No merge, no deploy, no publish.