delegation.parent_record_hash is the field a verifier walks to reconstruct a delegation chain, and nothing normative says what it is a digest of.
At e7e2eca:
- The schema gives it a format and a sentence:
"pattern": "^sha(256:[0-9a-f]{64}|384:[0-9a-f]{96})$", described as "SHA-256 or SHA-384 digest of the parent hop's Trust Record."
spec/trace-v0.2.md does not mention the field at all (zero occurrences).
docs/rfcs/a2a-delegation-profile.md §4.1 does state a preimage, and D-3 (parent_not_found) is defined in terms of it — but that document says "Status: Draft proposal. Binds nothing."
"the parent hop's Trust Record" has at least three natural readings, and they give different digests:
- The published file's bytes.
- RFC 8785 (JCS) over the complete record, signature included.
- RFC 8785 over the record with
signature removed — the same body the signature itself covers.
I computed all three against our own delegated-parent record. Readings 1 and 2 agree on it and match the child's link; reading 3 gives a different digest. So two implementers reading only the schema can both act in good faith and produce chains that resolve to dangling links against each other, with no diagnostic on either side saying why.
The complete-record reading is the right one, and it should be normative
§4.1 already takes it, for a reason worth promoting out of a draft: a digest over the signed body alone commits to what the parent said and not to who said it, so any holder of any key can re-sign bytes that satisfy the same link. That is the difference between a chain that binds hops and one that binds statements.
Asking for that sentence to move somewhere that binds — the schema description, or spec/trace-v0.2.md — rather than for a new rule.
One reading is a trap rather than a disagreement
Plain json.dumps(obj, sort_keys=True, separators=(",", ":")) over the complete record produces the same digest as JCS on records like ours, so an implementer who reaches for it interoperates and has no reason to look further.
It stops matching the first time a record carries a non-ASCII character, and the cause is a default rather than a choice: json.dumps escapes non-ASCII to \uXXXX unless ensure_ascii=False is passed. Putting 模型-π in model.model_id and v1-é in policy.version, both of which the schema accepts:
JCS 91eca6bbe63ebe2ffeba09c0...
json.dumps (default) fd74a7636b2665d69185150c... diverges
json.dumps (ensure_ascii=False) 91eca6bbe63ebe2ffeba09c0... agrees
A silent agreement that ends on a field nobody thought was load-bearing is worse than a mismatch on day one. If the normative text names RFC 8785 explicitly rather than "canonical JSON", that whole class goes away.
Why this surfaced here
We publish a delegated parent/child vector pair whose link is built to the complete-record reading, and the aggregate over them carries member digests under the same convention. When an independent implementer re-derived the link, the preimage had to be inferred from our vectors rather than read from the specification — which works exactly once, for whoever has our vectors in front of them.
Same shape as #232, #240 and #244: a rule an implementer needs, that no binding surface states.
Credit: @lywinged enumerated six encodings against our parent record and isolated both the four-way split and the json.dumps trap.
delegation.parent_record_hashis the field a verifier walks to reconstruct a delegation chain, and nothing normative says what it is a digest of.At
e7e2eca:"pattern": "^sha(256:[0-9a-f]{64}|384:[0-9a-f]{96})$", described as "SHA-256 or SHA-384 digest of the parent hop's Trust Record."spec/trace-v0.2.mddoes not mention the field at all (zero occurrences).docs/rfcs/a2a-delegation-profile.md§4.1 does state a preimage, and D-3 (parent_not_found) is defined in terms of it — but that document says "Status: Draft proposal. Binds nothing.""the parent hop's Trust Record" has at least three natural readings, and they give different digests:
signatureremoved — the same body the signature itself covers.I computed all three against our own delegated-parent record. Readings 1 and 2 agree on it and match the child's link; reading 3 gives a different digest. So two implementers reading only the schema can both act in good faith and produce chains that resolve to dangling links against each other, with no diagnostic on either side saying why.
The complete-record reading is the right one, and it should be normative
§4.1 already takes it, for a reason worth promoting out of a draft: a digest over the signed body alone commits to what the parent said and not to who said it, so any holder of any key can re-sign bytes that satisfy the same link. That is the difference between a chain that binds hops and one that binds statements.
Asking for that sentence to move somewhere that binds — the schema description, or
spec/trace-v0.2.md— rather than for a new rule.One reading is a trap rather than a disagreement
Plain
json.dumps(obj, sort_keys=True, separators=(",", ":"))over the complete record produces the same digest as JCS on records like ours, so an implementer who reaches for it interoperates and has no reason to look further.It stops matching the first time a record carries a non-ASCII character, and the cause is a default rather than a choice:
json.dumpsescapes non-ASCII to\uXXXXunlessensure_ascii=Falseis passed. Putting模型-πinmodel.model_idandv1-éinpolicy.version, both of which the schema accepts:A silent agreement that ends on a field nobody thought was load-bearing is worse than a mismatch on day one. If the normative text names RFC 8785 explicitly rather than "canonical JSON", that whole class goes away.
Why this surfaced here
We publish a delegated parent/child vector pair whose link is built to the complete-record reading, and the aggregate over them carries member digests under the same convention. When an independent implementer re-derived the link, the preimage had to be inferred from our vectors rather than read from the specification — which works exactly once, for whoever has our vectors in front of them.
Same shape as #232, #240 and #244: a rule an implementer needs, that no binding surface states.
Credit: @lywinged enumerated six encodings against our parent record and isolated both the four-way split and the
json.dumpstrap.