1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! The two primitives every signed canonical in this crate is built from: the
//! serializer that produces the exact bytes a signature covers, and the
//! envelope that appends the provenance fields afterwards.
//!
//! Extracted from [`crate::approval`] (`#1842`) once [`crate::mandate`],
//! `crate::grant`, and [`crate::question`] adopted the same pattern
//! (`#1845`), so one declaration governs every such module rather than a copy
//! per module drifting apart. `crate::grant` itself went with the approvals
//! passkey (#2407 slice 5); the pattern it helped establish did not.
//!
//! See `docs/decisions/0009-field-declaration-order-is-the-signed-contract.md` for the
//! decision this module implements and the alternatives it rejects.
//!
//! Both items are `pub(crate)` deliberately. Every signed canonical in the tree
//! is built here, so nothing outside this crate needs them. `crates/passkey`
//! used to be the one exception — `derive.rs`'s
//! `persona_credential_binding_canonical` built its own canonical and could not
//! call these, satisfying the same invariant through a `BTreeMap` instead. That
//! module went with the approvals passkey (2026-08-10). The remaining passkey
//! challenge helper only hashes a canonical that its caller already built.
//!
//! Should a second crate ever build one again, widening these is still not the
//! fix it looks like. A `passkey → crypto` edge would invert the foundation
//! dependency rule; sharing one declaration needs this module moved below both,
//! which is the deferred sealed-`CanonicalBody` work in ADR 0009 — not a `pub`
//! keyword here.
use Serialize;
use crateSigner;
/// Serialize a canonical body to the exact bytes a signature covers.
///
/// Every signed canonical in this crate routes through here, and every one of
/// them is a `#[derive(Serialize)]` struct rather than a [`serde_json::Value`]
/// (`#1842`, `#1845`). The distinction is the whole point: a `Value`'s object
/// is a `BTreeMap` (keys sorted) by default and an `IndexMap` (insertion
/// order) the moment anything in the build graph turns on
/// `serde_json/preserve_order`, so serializing one makes the signed bytes —
/// and therefore the signature — a property of the *dependency graph of
/// whatever binary happens to be signing or verifying*. A struct has no such
/// map: `serialize_struct` writes fields in declaration order, always. The key
/// order of every canonical is therefore fixed by the source, and a verifier
/// with a leaner dependency set (a standalone query plane, an audit tool, a
/// conformance harness) computes the same bytes this control plane signs.
///
/// The declaration order of each struct's fields IS the signed contract.
/// Reordering a field is a signed-payload change, exactly as renaming one is.
///
/// # Panics
///
/// Never for any canonical in this crate, and the reason is worth stating
/// precisely rather than as "never in practice", because two different
/// properties carry it.
///
/// By *field type*: the canonicals are plain structs of strings, integers,
/// booleans, and sequences of those, and `serde_json` fails only on a non-string
/// map key or a non-finite float, neither of which any canonical can hold.
///
/// By *shape*, which the type system does not enforce: serializing an
/// [`Envelope<T>`] flattens `T`, and flatten errors with "can only flatten
/// structs and maps" if `T` does not serialize as one. So
/// `canonical_bytes(&Envelope::<u64> { .. })` would panic here. Every body in
/// this crate is a struct; a caller introducing a non-struct body is the one way
/// to reach this panic, which is why [`Envelope`] documents the requirement.
pub
/// A canonical body plus the two provenance fields appended after signing.
///
/// `signed_by` and `signature_hex` are NOT covered by the signature (it cannot
/// cover itself), so they live here rather than in any canonical struct.
/// Flattening the body keeps the persisted payload's field order equal to the
/// canonical's followed by the two provenance fields — the order the
/// build-then-insert construction produced, now fixed by this declaration
/// rather than by a map (`#1842`). Flatten forwards each of the body's fields as
/// a map entry in declaration order, writing straight to the JSON output, so it
/// never builds a [`serde_json::Value`] and never consults a map type.
///
/// `T` carries two requirements the bound cannot express, both of which every
/// canonical in this crate meets:
///
/// - **`T` must serialize as a struct or a map.** Flatten rejects anything else
/// at runtime, which [`canonical_bytes`] turns into a panic on the signing
/// path.
/// - **`T` must not have a field named `signed_by` or `signature_hex`.** Flatten
/// does not deduplicate, so such a field would emit a duplicate JSON key, and
/// which of the two a reader keeps depends on the map type it parses with —
/// reintroducing the build dependence one level up.
pub