@ziffer-io/types 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +120 -0
- package/README.md +58 -0
- package/THIRD-PARTY-NOTICES +18 -0
- package/dist/generated.d.ts +1696 -0
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +193 -0
- package/dist/generated.js.map +1 -0
- package/dist/generated_wire.d.ts +4466 -0
- package/dist/generated_wire.d.ts.map +1 -0
- package/dist/generated_wire.js +20 -0
- package/dist/generated_wire.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,4466 @@
|
|
|
1
|
+
import type { FidelityClass, Reversibility, Risk, SuiteId, TenantId, Tier, WebauthnAlg } from './generated.js';
|
|
2
|
+
/**
|
|
3
|
+
* The two acts an acknowledgement can be. Closed enum: the reference refuses
|
|
4
|
+
* any other value at ACK-1 by name, before the signature is checked, because
|
|
5
|
+
* a third value would be an act with no defined consequence and the release
|
|
6
|
+
* path would have to guess at it.
|
|
7
|
+
* Generated from acknowledgement.schema.json#/$defs/ack_decision.
|
|
8
|
+
*/
|
|
9
|
+
export type AcknowledgementDecision = 'CONFIRM' | 'REPUDIATE';
|
|
10
|
+
/**
|
|
11
|
+
* What an Attestation Object may declare as its suite: a machine suite
|
|
12
|
+
* (`SuiteId`, the set CR-4's floor is registered in) or a human key type
|
|
13
|
+
* (`WebauthnAlg`, HM-4's two, which CR-8 puts outside that floor). The two
|
|
14
|
+
* are declared once each, in the SIGNED root, and pointed at from here.
|
|
15
|
+
*
|
|
16
|
+
* WHY A UNION AND NOT A THIRD LIST. A single enum naming all five values is
|
|
17
|
+
* the obvious way to write this field and it is the defect this repository
|
|
18
|
+
* publishes corrections for. The names would then exist in three places:
|
|
19
|
+
* CR-4 compares an object's suite against `min_suite`, which is drawn from
|
|
20
|
+
* `signature.schema.json`, and HM-3 compares it against a `webauthn`
|
|
21
|
+
* registry entry's `alg`, which is drawn from `attesters.schema.json`. A
|
|
22
|
+
* comparison between a copy and its original passes on a vocabulary nobody
|
|
23
|
+
* checked -- `decision_receipt.alg` records the same argument for `SuiteId`
|
|
24
|
+
* alone, and CR-8's restriction is exactly what forbids the cheaper fix of
|
|
25
|
+
* widening `SuiteId` to five values: `webauthn-es256` and `webauthn-ed25519`
|
|
26
|
+
* MUST NOT appear as a bundle, receipt or door suite, and every other holder
|
|
27
|
+
* of `SuiteId` is one of those three positions.
|
|
28
|
+
*
|
|
29
|
+
* WHY THE GENERATOR ACCEPTS IT. `tools/codegen.py` reads a `oneOf` as a
|
|
30
|
+
* union it must be able to DECIDE, and refuses one it would have to resolve
|
|
31
|
+
* by trying branches until something fits. A tagged union of objects proves
|
|
32
|
+
* its discriminator from the schema; this one is proved a different way and
|
|
33
|
+
* the proof is just as mechanical: the branches are named enums and the
|
|
34
|
+
* generator halts unless their value sets are pairwise DISJOINT, so exactly
|
|
35
|
+
* one branch can match any given string and the decode is by value rather
|
|
36
|
+
* than by order. Adding a value to either declaration that already exists in
|
|
37
|
+
* the other turns the generator red rather than emitting a type whose
|
|
38
|
+
* meaning depends on branch order.
|
|
39
|
+
*
|
|
40
|
+
* BOTH BRANCHES CROSS ROOTS, AND ONLY A NAMED SCALAR MAY (ACP-78 M1). The
|
|
41
|
+
* signed side owns the vocabulary and the wire carries it, the same
|
|
42
|
+
* direction as `SuiteId`, `Tier` and `FidelityClass`. This type is
|
|
43
|
+
* nevertheless declared HERE rather than in the bundle root, because the
|
|
44
|
+
* union is a fact about a MESSAGE: no signed table holds a value that may be
|
|
45
|
+
* either, and a registry entry's `alg` is always exactly one of the two.
|
|
46
|
+
* Generated from attestation_object.schema.json#/$defs/attestation_alg.
|
|
47
|
+
*
|
|
48
|
+
* The two branches are declared once each, elsewhere, and the generator
|
|
49
|
+
* proved their value sets disjoint before emitting this union, so narrowing
|
|
50
|
+
* it by value is total: a string belongs to at most one branch.
|
|
51
|
+
*/
|
|
52
|
+
export type AttestationAlg = SuiteId | WebauthnAlg;
|
|
53
|
+
/**
|
|
54
|
+
* AU-9's seven event classes, in pipeline order: a door refusing a
|
|
55
|
+
* submission (F1.1), a Decision passing or failing §9.3 (F6.1), a receipt
|
|
56
|
+
* refused at the Executor with the offending receipt attached (EX-3), a
|
|
57
|
+
* floor-HIGH action held (DR-1), an irreversible below-HIGH action's notice
|
|
58
|
+
* committed before execution (DR-13), a held action released or refused
|
|
59
|
+
* (DR-7/9/10, counted under DR-12), and what the target reported (EX-2).
|
|
60
|
+
* Generated from audit_record.schema.json#/$defs/event_class.
|
|
61
|
+
*/
|
|
62
|
+
export type AuditEventClass = 'ingress_refused' | 'decision' | 'verification_failed' | 'hold' | 'notice' | 'release' | 'execution';
|
|
63
|
+
/**
|
|
64
|
+
* EX-2's four execution outcomes, plus `refused` for a `release` record
|
|
65
|
+
* whose DR-* check failed closed. Which values a class admits is §11.1's
|
|
66
|
+
* table, enforced by the consumer under AU-9; the enum is the union so one
|
|
67
|
+
* type serves both records.
|
|
68
|
+
* Generated from audit_record.schema.json#/$defs/outcome.
|
|
69
|
+
*/
|
|
70
|
+
export type AuditOutcome = 'executed' | 'failed' | 'not_attempted' | 'indeterminate' | 'refused';
|
|
71
|
+
/**
|
|
72
|
+
* A byte string carried as text under WE-4: the ASCII `"b64:"` followed by
|
|
73
|
+
* RFC 4648 §4 base64, WITH padding. The URL-safe alphabet (RFC 4648 §5) and
|
|
74
|
+
* unpadded encodings are different values and MUST be rejected rather than
|
|
75
|
+
* normalized.
|
|
76
|
+
*
|
|
77
|
+
* The prefix is part of the value, not a label on it. §11.2 pins `sha256:`
|
|
78
|
+
* to the character and records why it had to -- an implementer who fed
|
|
79
|
+
* forward raw digest bytes built a different chain while following the text
|
|
80
|
+
* -- and WE-4 exists because the same hole was open on every `b64:` value
|
|
81
|
+
* through v1.3.17, where the string appeared only inside §9.2's diagnostic
|
|
82
|
+
* JSON and no clause said whether the prefix was part of what gets hashed
|
|
83
|
+
* and signed. Two spellings of one byte string are two values, and at a
|
|
84
|
+
* verifier the mismatch is indistinguishable from a forgery.
|
|
85
|
+
*
|
|
86
|
+
* This type carries no length bound: WE-4 governs the encoding, and the size
|
|
87
|
+
* of any particular value is the business of the clause that defines it.
|
|
88
|
+
* Generated from signing_request.schema.json#/$defs/b64_bytes.
|
|
89
|
+
*
|
|
90
|
+
* The schema constrains this to `^b64:(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$`, which is NOT enforced here.
|
|
91
|
+
*/
|
|
92
|
+
export type Base64Bytes = string;
|
|
93
|
+
/**
|
|
94
|
+
* The three outcomes of the section 8.4 grading fold: ALLOW executes, ATTEST
|
|
95
|
+
* routes to a human quorum, DENY refuses.
|
|
96
|
+
*
|
|
97
|
+
* NOT ORDERED. These are outcomes, not a severity scale, and a derived
|
|
98
|
+
* comparison over them would invite `>= ALLOW`-shaped code that has no
|
|
99
|
+
* meaning. P-4 makes DENY the default for no matching rule, unknown
|
|
100
|
+
* capability, unclassified resource or unavailable Context Store, each with
|
|
101
|
+
* its own reason code -- so DENY is where the fold lands when it knows
|
|
102
|
+
* nothing, not the far end of a ranking.
|
|
103
|
+
*
|
|
104
|
+
* EO-1: HIGH MUST NOT be auto-allowed by any configuration; there is no flag
|
|
105
|
+
* that makes step 11 emit ALLOW for HIGH.
|
|
106
|
+
* Generated from decision_receipt.schema.json#/$defs/decision_outcome.
|
|
107
|
+
*/
|
|
108
|
+
export type DecisionOutcome = 'ALLOW' | 'ATTEST' | 'DENY';
|
|
109
|
+
/**
|
|
110
|
+
* The two doors of deferred release. `presentation` is the Attestation
|
|
111
|
+
* Presentation Service (the approval path); `notification` is DR-2's
|
|
112
|
+
* independent path. DELIBERATELY NOT ORDERED: neither outranks the other,
|
|
113
|
+
* and the two-compromise argument needs both.
|
|
114
|
+
* Generated from notification.schema.json#/$defs/door_role.
|
|
115
|
+
*/
|
|
116
|
+
export type DoorRole = 'presentation' | 'notification';
|
|
117
|
+
/**
|
|
118
|
+
* A 128-bit nonce: `"b64:"` followed by RFC 4648 §4 base64 WITH padding,
|
|
119
|
+
* carried, hashed and signed as that string (WE-4). The size is AT-1 for the
|
|
120
|
+
* attestation nonce and L-17 for the receipt nonce.
|
|
121
|
+
*
|
|
122
|
+
* THE PATTERN IS TWO RULES, AND THEY REFUSE UNDER DIFFERENT NAMES. `b64:`
|
|
123
|
+
* plus the RFC 4648 §4 alphabet with padding is WE-4, the wire type; the
|
|
124
|
+
* twenty-four characters are the 128 bits. A verifier folding them into one
|
|
125
|
+
* check answers `WE-4` for a 64-bit nonce, which is a perfectly well-formed
|
|
126
|
+
* `b64:` value — the wrong clause name, and the cross-language differential
|
|
127
|
+
* compares names. `reference/src/acp_executor.py` and
|
|
128
|
+
* `crates/acp-decision/src/quorum.rs` both apply the type rule first and the
|
|
129
|
+
* size rule second, in that order, because an object wrong in both ways must
|
|
130
|
+
* stop at the same rule everywhere.
|
|
131
|
+
*
|
|
132
|
+
* WHY THE PREFIX IS PART OF THE VALUE. `attestation_id` is SHA-256 over the
|
|
133
|
+
* canonical CBOR of the Attestation Object (AT-8a), so an issuer carrying
|
|
134
|
+
* `b64:AAAA…` and a verifier carrying `AAAA…` derive TWO IDS FOR ONE OBJECT
|
|
135
|
+
* and claim TWO LEDGER SLOTS — one human approval, spent twice. That is Z4
|
|
136
|
+
* and T-14 reopening through a type nobody had written down, reached with no
|
|
137
|
+
* optional field and therefore untouched by AT-8b, which closes the field
|
|
138
|
+
* set one layer above this. §11.2 pins `sha256:` to the character for the
|
|
139
|
+
* same reason and records what happened without it: an implementer who fed
|
|
140
|
+
* forward raw digest bytes built a different chain while following the text.
|
|
141
|
+
*
|
|
142
|
+
* HISTORY, KEPT BECAUSE IT IS THE ARGUMENT. When this schema was first
|
|
143
|
+
* written the prefix was pinned HERE and nowhere else — `b64:` occurred
|
|
144
|
+
* exactly twice in ZIFFER-SPEC-001, both times inside §9.2's diagnostic
|
|
145
|
+
* JSON, and no clause said whether it was part of the value. Writing the
|
|
146
|
+
* schema is what surfaced that (ACP-87); it is now fixed at the root as WE-4
|
|
147
|
+
* in v1.3.18, so this pattern restates the specification rather than
|
|
148
|
+
* exceeding it. A type that exists only in a schema is a type the
|
|
149
|
+
* specification does not have, and a second implementation written from the
|
|
150
|
+
* document alone would never have seen it.
|
|
151
|
+
*
|
|
152
|
+
* The three definitions of this type — here, the Python reference and the
|
|
153
|
+
* Rust verifier — DID diverge, and no gate noticed: this pattern pinned
|
|
154
|
+
* twenty-four characters from its first day while the reference accepted any
|
|
155
|
+
* length, and no fixture ever fed a wrong-length nonce.
|
|
156
|
+
* `tools/nonce-type-vectors.json` is now the one corpus all three are
|
|
157
|
+
* evaluated against, and `tools/check-nonce-type.py` resolves this `$def`
|
|
158
|
+
* rather than one field's inline pattern, so the corpus is evaluated against
|
|
159
|
+
* the type every nonce field shares.
|
|
160
|
+
*
|
|
161
|
+
* IT IS ONE TYPE AND IT LIVES HERE. ACP-78 M1 found FIVE definitions of it
|
|
162
|
+
* inside `spec/schemas/wire/` alone — an inline pattern on this object's
|
|
163
|
+
* `att_nonce` and on `PendingRelease.receipt_nonce`, and named copies in the
|
|
164
|
+
* audit record and the ledger claim — with the sentence above, saying the
|
|
165
|
+
* definitions must move together, sitting inside two of them. `$ref` it; do
|
|
166
|
+
* not restate it.
|
|
167
|
+
* Generated from attestation_object.schema.json#/$defs/nonce128.
|
|
168
|
+
*
|
|
169
|
+
* The schema constrains this to `^b64:[A-Za-z0-9+/]{22}==$`, which is NOT enforced here.
|
|
170
|
+
*/
|
|
171
|
+
export type Nonce128 = string;
|
|
172
|
+
/**
|
|
173
|
+
* A party identity: the operator an action is attributed to, and the same
|
|
174
|
+
* value DR-9 compares an acknowledger against.
|
|
175
|
+
*
|
|
176
|
+
* NO CLAUSE TYPES THIS AND THAT IS THE POINT OF SAYING SO HERE.
|
|
177
|
+
* ZIFFER-SPEC-001 carries `operator`, attester identities and notification
|
|
178
|
+
* recipients as bare strings everywhere and states no syntax, no length and
|
|
179
|
+
* no namespace for any of them. The bound is DERIVED, not restated: an
|
|
180
|
+
* operator must resolve in the bundle's attester registry for AT-2
|
|
181
|
+
* approver-distinctness and DR-9 to be decidable, and
|
|
182
|
+
* `spec/schemas/bundle/attesters.schema.json` bounds that registry's keys at
|
|
183
|
+
* 1..128 — itself a schema-level choice with no clause behind it. So this is
|
|
184
|
+
* a schema constraint the specification does not make, in the ACP-87 shape:
|
|
185
|
+
* a type that exists only in a schema is a type the specification does not
|
|
186
|
+
* have.
|
|
187
|
+
*
|
|
188
|
+
* IT IS DECLARED ONCE BECAUSE IT WAS DECLARED FIVE TIMES. ACP-78 M1 found
|
|
189
|
+
* this value written inline in five places across this root with TWO
|
|
190
|
+
* different bounds — 128 here, in `ContextRead.capability_grant`, in the
|
|
191
|
+
* receipt and in the pending-release record, and 2,048 in the audit record,
|
|
192
|
+
* where its author derived the bound from L-04 instead. Two lengths for one
|
|
193
|
+
* identity means a party who is nameable in an audit record and unnameable
|
|
194
|
+
* in the receipt that produced it.
|
|
195
|
+
* Generated from attestation_object.schema.json#/$defs/operator_id.
|
|
196
|
+
*/
|
|
197
|
+
export type OperatorId = string;
|
|
198
|
+
/**
|
|
199
|
+
* EL-2's value domain: an integer or a string, and nothing else. A rule's
|
|
200
|
+
* environment binds each `params` entry to one of these; a value of any
|
|
201
|
+
* other JSON type -- a non-integer number, a boolean, null, an array, an
|
|
202
|
+
* object -- MUST refuse the whole Proposal (EL-2, closes the v1.3.17
|
|
203
|
+
* `22`/`22.0` defect). The integer is bounded by the width the deployment
|
|
204
|
+
* declares under AC-1a; the generated types carry `i64`, and a value above
|
|
205
|
+
* it is out of the domain rather than merely large.
|
|
206
|
+
* Generated from proposal.schema.json#/$defs/param_value.
|
|
207
|
+
*
|
|
208
|
+
* EL-2's domain and nothing else. A value of any other type refuses the
|
|
209
|
+
* Proposal; it is never coerced into one of these two.
|
|
210
|
+
*/
|
|
211
|
+
export type ParamValue = number | string;
|
|
212
|
+
/**
|
|
213
|
+
* An instant on the wire: EXACTLY `YYYY-MM-DDTHH:MM:SSZ`, to one second. No
|
|
214
|
+
* offset, no fractional part, no lowercase `z`. Every temporal value in this
|
|
215
|
+
* root carries this type except the acknowledgement's, which ACK-6 declares
|
|
216
|
+
* numeric.
|
|
217
|
+
*
|
|
218
|
+
* WHY THE SPELLING IS PINNED AND NOT LEFT TO RFC 3339 AS WRITTEN. RFC 3339
|
|
219
|
+
* accepts `2026-08-09T14:02:11Z`, `2026-08-09t14:02:11z`,
|
|
220
|
+
* `2026-08-09T16:02:11+02:00` and `2026-08-09T14:02:11.000Z` as four
|
|
221
|
+
* renderings of ONE instant. Each is a distinct string, and these values go
|
|
222
|
+
* inside canonical CBOR and are hashed: the receipt's `issued_at` sits in
|
|
223
|
+
* the signed body, the attestation object's `expires_at` sits in the
|
|
224
|
+
* preimage of `attestation_id` (AT-1, AT-8a), and `occurred_at` sits in the
|
|
225
|
+
* AU-1 chain preimage. Four spellings of one instant are four ids for one
|
|
226
|
+
* object and four chain hashes for one history -- Z4 and T-14 reopening
|
|
227
|
+
* through a type nobody had written down, which is precisely what `nonce128`
|
|
228
|
+
* above records happening to `b64:`. Canonicalization cannot fix it: the
|
|
229
|
+
* ambiguity is in the VALUE, not the encoding. So the offset MUST be `Z`,
|
|
230
|
+
* the letters MUST be upper case, there MUST be no fractional part, and a
|
|
231
|
+
* verifier MUST REJECT any other rendering rather than normalizing it --
|
|
232
|
+
* normalizing here would make this verifier accept every spelling and hand
|
|
233
|
+
* the divergence to the next implementation, which is the reasoning
|
|
234
|
+
* `nonce128` gives for refusing a stripped `b64:` prefix.
|
|
235
|
+
*
|
|
236
|
+
* WHY IT IS A STRING AND NOT AN EPOCH NUMBER. AT-8a forbids a float in a
|
|
237
|
+
* canonical structure because a float is not deterministically encoded, and
|
|
238
|
+
* `canon()` enforces that at the TOP level only -- a nested float serialises
|
|
239
|
+
* happily in both languages. The one structure in this root whose times are
|
|
240
|
+
* numbers is the acknowledgement, and the reference put `1787598182.1470501`
|
|
241
|
+
* into it on every production path (`crates/acp-core/src/generated_wire.rs`
|
|
242
|
+
* records the observation beside `AcknowledgementObject.issued_at`). A
|
|
243
|
+
* string of this shape has exactly one encoding.
|
|
244
|
+
*
|
|
245
|
+
* HISTORY, KEPT BECAUSE IT IS THE ARGUMENT. Through v1.3.27 this type was
|
|
246
|
+
* written out on `audit_record.occurred_at` and NOWHERE ELSE; the other six
|
|
247
|
+
* temporal fields carried a bare `{"type": "string", "format": "date-time"}`
|
|
248
|
+
* with no pinned spelling, and BOTH implementations read all seven as POSIX
|
|
249
|
+
* numbers. So an Attestation Object that satisfied the schema was refused at
|
|
250
|
+
* `9.3-7b-iii` by both verifiers, and an object both verifiers accepted did
|
|
251
|
+
* not validate -- no client building from the schema could satisfy a
|
|
252
|
+
* floor-HIGH quorum. The cross-language differential could not see it,
|
|
253
|
+
* because the two implementations agreed with each other and disagreed with
|
|
254
|
+
* the document: the eighth lesson in `dossier/05-TEST-EVIDENCE.md`, one
|
|
255
|
+
* artifact over. Nothing validates a fixture against these schemas (ACP-52),
|
|
256
|
+
* so nothing else could either. Filed as ACP-167 by the first executable
|
|
257
|
+
* consumer to build a floor-HIGH fixture from the schema rather than from
|
|
258
|
+
* the reference, and closed in v1.3.28 by moving the IMPLEMENTATIONS --
|
|
259
|
+
* because AT-1, section 9.2's rendered receipt and seven schemas all said
|
|
260
|
+
* RFC 3339, and only the code said otherwise. `anchor.schema.json` had
|
|
261
|
+
* already written the fix down: "a divergence to close in one place, not
|
|
262
|
+
* two".
|
|
263
|
+
*
|
|
264
|
+
* THE PARSER ALREADY EXISTED, ONE ROOT OVER, AND THIS TYPE IS ITS GRAMMAR
|
|
265
|
+
* WRITTEN DOWN. PB-1 has to decide when a bundle stops being valid, so
|
|
266
|
+
* `bundle/manifest.schema.json` carries `expires_at` as RFC 3339 and both
|
|
267
|
+
* implementations parse it strictly: `Timestamp::parse` in Rust and
|
|
268
|
+
* `Timestamp.parse` in the reference, refusing offsets, fractional seconds
|
|
269
|
+
* and lowercase `z` in the same words, checking the day against the actual
|
|
270
|
+
* length of the month, and refusing a leap second because `datetime` does.
|
|
271
|
+
* They are already differential partners. This $def is that grammar, so the
|
|
272
|
+
* wire root gets ONE more reference to an existing type rather than a second
|
|
273
|
+
* parser -- a second parser for one format being the two-definitions defect
|
|
274
|
+
* this repository has published three corrections for. THE FRACTIONAL PART
|
|
275
|
+
* IS WHY THIS PARAGRAPH IS HERE: `occurred_at`'s pattern through v1.3.27
|
|
276
|
+
* permitted `(\.\d{1,9})?`, so `...:11Z` and `...:11.0Z` were two spellings
|
|
277
|
+
* of one instant inside the AU-1 chain preimage -- the exact hazard four
|
|
278
|
+
* paragraphs up, in the one field that had a pattern at all. v1.3.28 narrows
|
|
279
|
+
* it to the parsers' grammar. Nothing emitted a fractional second, because
|
|
280
|
+
* both implementations were emitting numbers.
|
|
281
|
+
*
|
|
282
|
+
* IT IS ONE TYPE AND IT LIVES HERE, for the reason `nonce128` states one
|
|
283
|
+
* $def above.
|
|
284
|
+
* Generated from attestation_object.schema.json#/$defs/rfc3339_instant.
|
|
285
|
+
*
|
|
286
|
+
* The schema constrains this to `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$`, which is NOT enforced here.
|
|
287
|
+
*/
|
|
288
|
+
export type Rfc3339Instant = string;
|
|
289
|
+
/**
|
|
290
|
+
* The identifier half of SR-1's content address, carried on every Proposal
|
|
291
|
+
* by SR-5 and on the receipt that decided it by §9.2.
|
|
292
|
+
*
|
|
293
|
+
* ONE DECLARATION, AND WHY IT IS NOT SHARED WITH `task_type`. Before ACP-78
|
|
294
|
+
* M1 this pattern was written out three times in this root: here, on the
|
|
295
|
+
* receipt, and on the payload's `task_type`, which is a DIFFERENT value that
|
|
296
|
+
* happens to share a character class. The first two are one type and are now
|
|
297
|
+
* one declaration. The third is deliberately left as its own inline pattern,
|
|
298
|
+
* because V-11 requires `task_type` to `agree with` `schema_id` and does not
|
|
299
|
+
* say what agreement is -- string identity, or a registry lookup. Giving
|
|
300
|
+
* both fields one type would answer V-11 by writing a schema, which is the
|
|
301
|
+
* move this root exists to refuse. Two types with the same pattern are two
|
|
302
|
+
* types; one type with two declarations is the defect.
|
|
303
|
+
*
|
|
304
|
+
* THE PATTERN IS §6.3's, CHARACTER FOR CHARACTER, AND THE REFERENCE DOES NOT
|
|
305
|
+
* SATISFY IT: `reference/suites/conformance.py` builds `schema_id: "fw.v1"`
|
|
306
|
+
* and `.` is outside the class. It is kept as written, because widening a
|
|
307
|
+
* schema's own pattern to admit the fixture that violates it proves only
|
|
308
|
+
* that the instrument is adjustable. Whether a pattern stated inside a
|
|
309
|
+
* NORMATIVE EXAMPLE binds an implementation at all is unresolved and
|
|
310
|
+
* reported rather than assumed.
|
|
311
|
+
* Generated from proposal.schema.json#/$defs/schema_id.
|
|
312
|
+
*
|
|
313
|
+
* The schema constrains this to `^[a-z0-9_-]{1,32}$`, which is NOT enforced here.
|
|
314
|
+
*/
|
|
315
|
+
export type SchemaId = string;
|
|
316
|
+
/**
|
|
317
|
+
* The version half of SR-1's content address (SR-5), declared once here and
|
|
318
|
+
* `$ref`d by the receipt, which carries the same triple. It is the axis SR-3
|
|
319
|
+
* decides change direction over: a change is NARROWING only where the new
|
|
320
|
+
* admissible set is a provable subset of the old in EVERY dimension of the
|
|
321
|
+
* bounded lattice -- character class, length bound, enum set, numeric range
|
|
322
|
+
* -- and anything not provably narrowing in every dimension is widening and
|
|
323
|
+
* takes the distinct-reviewer path. v1.2.0 left `narrowing` undefined, which
|
|
324
|
+
* let a net-widening change self-classify as narrowing and bypass review.
|
|
325
|
+
*
|
|
326
|
+
* The pattern is §6.3's and carries the same unresolved question as
|
|
327
|
+
* `schema_id`: it is stated inside a section titled `Reference Schemas
|
|
328
|
+
* (Normative Examples)`, and whether an example binds is not settled
|
|
329
|
+
* anywhere.
|
|
330
|
+
* Generated from proposal.schema.json#/$defs/schema_version.
|
|
331
|
+
*
|
|
332
|
+
* The schema constrains this to `^\d+\.\d+\.\d+$`, which is NOT enforced here.
|
|
333
|
+
*/
|
|
334
|
+
export type SchemaVersion = string;
|
|
335
|
+
/**
|
|
336
|
+
* `"sha256:"` followed by a lowercase-hex SHA-256 digest — 71 characters.
|
|
337
|
+
* The type is pinned rather than left to the implementer because §11.2
|
|
338
|
+
* records an implementer who fed forward raw digest bytes and built a
|
|
339
|
+
* different chain while following the text.
|
|
340
|
+
* Generated from attestation_object.schema.json#/$defs/sha256.
|
|
341
|
+
*
|
|
342
|
+
* The schema constrains this to `^sha256:[0-9a-f]{64}$`, which is NOT enforced here.
|
|
343
|
+
*/
|
|
344
|
+
export type Sha256Hash = string;
|
|
345
|
+
/**
|
|
346
|
+
* A signed acknowledgement of a floor-HIGH action being held for release.
|
|
347
|
+
* Carried on legs F5.4 and F5.5 of the leg register (ZIFFER-DEPLOY-001 Annex
|
|
348
|
+
* A): F5.4 is the DR-9 positive acknowledgement that releases an
|
|
349
|
+
* IRREVERSIBLE action, F5.5 is the DR-4 repudiation that vetoes one. It is
|
|
350
|
+
* the same artifact on both, discriminated by `decision`.
|
|
351
|
+
*
|
|
352
|
+
* THE DISCLOSURE THAT GOVERNS EVERY LINE BELOW: ACK-1 THROUGH ACK-6 ARE NOT
|
|
353
|
+
* CLAUSES OF ZIFFER-SPEC-001. The string `ACK-` does not occur in that
|
|
354
|
+
* document at all. They are reference-level rules, written for v1.3.12 in
|
|
355
|
+
* `reference/src/acp_ack.py` to close T-31, and the two places that already
|
|
356
|
+
* say so in as many words are the leg register (`DR-9, ACK-1..6
|
|
357
|
+
* (reference-level)`) and `spec/schemas/bundle/attesters.schema.json`
|
|
358
|
+
* (`ACK-4 (reference-level ... not a spec clause)`). ZIFFER-SPEC-001 states
|
|
359
|
+
* the OBLIGATIONS this object discharges -- DR-9 requires positive
|
|
360
|
+
* acknowledgement from at least one notified party who is not the operator,
|
|
361
|
+
* DR-4 admits repudiation from any notified party, DR-5 admits it only from
|
|
362
|
+
* a notified party and forbids the repudiation path from depending on the
|
|
363
|
+
* approval chain -- and states NOTHING about the artifact that carries them:
|
|
364
|
+
* no field list, no canonicalization, no validity bound, no identity type,
|
|
365
|
+
* no nonce type. Every field below therefore cites the reference-level ACK-*
|
|
366
|
+
* rule that put it there together with the ZIFFER-SPEC-001 clause it serves.
|
|
367
|
+
* A second implementation written from ZIFFER-SPEC-001 alone would not
|
|
368
|
+
* produce this object and would still be conformant with the document. That
|
|
369
|
+
* is the same hole ACP-87 was filed for one layer down, and it is wider
|
|
370
|
+
* here: there the type of one field was unstated, here the whole artifact
|
|
371
|
+
* is.
|
|
372
|
+
*
|
|
373
|
+
* ONE ARTIFACT OR TWO, AND WHY THIS SCHEMA IS ONE. The leg register makes
|
|
374
|
+
* them two rows with DIFFERENT clause sets -- F5.4 carries `DR-9, ACK-1..6`,
|
|
375
|
+
* F5.5 carries only `DR-4, DR-5` and names no ACK-* rule at all. The
|
|
376
|
+
* reference makes them ONE object: there is exactly one closed field set,
|
|
377
|
+
* and `confirm()` and `repudiate()` run the identical `_verify_ack` and
|
|
378
|
+
* differ only in which value of `decision` they will accept afterwards. Read
|
|
379
|
+
* literally, the register says a repudiation must satisfy DR-4 and DR-5 and
|
|
380
|
+
* nothing else -- which is the v1.3.11 bare-name form (T-31) still standing
|
|
381
|
+
* on the veto leg, unsigned, while the confirmation leg was fixed. It does
|
|
382
|
+
* not stand in the code. The register and the reference disagree, the
|
|
383
|
+
* disagreement is recorded rather than silently resolved (that is the ACP-57
|
|
384
|
+
* shape), and this schema follows the code because the code is what runs.
|
|
385
|
+
*
|
|
386
|
+
* WHY THE FIELD SET IS CLOSED. ACK-1: an acknowledgement is an object with a
|
|
387
|
+
* closed schema -- exactly these fields, no optional ones, no extension
|
|
388
|
+
* point. An object carrying an unknown field or omitting a declared one MUST
|
|
389
|
+
* be rejected, never normalized and never defaulted. That is why
|
|
390
|
+
* `additionalProperties` is false at both levels and why `required` lists
|
|
391
|
+
* every property, and it is why `x-acp-absent` appears here only on the
|
|
392
|
+
* signature map: an absent-value rule on a field of a closed object would be
|
|
393
|
+
* a defect rather than a control. The reason is Z4, stated for the
|
|
394
|
+
* Attestation Object and applying unchanged here because this object is on
|
|
395
|
+
* the same binding path: given ANY optional field, the object
|
|
396
|
+
* present-as-null and the object with the field omitted are each valid
|
|
397
|
+
* canonical encodings, hash to TWO DISTINCT ids, and therefore claim TWO
|
|
398
|
+
* ledger slots. One acknowledgement, spent twice; ACK-5's single use
|
|
399
|
+
* defeated without forging anything. Canonicalization cannot fix it, because
|
|
400
|
+
* the ambiguity is in the field set rather than the encoding.
|
|
401
|
+
*
|
|
402
|
+
* ENCODING, AND THE CLAUSE THAT IS MISSING. ACK-1 says the object is
|
|
403
|
+
* `canonically encoded` and NO clause anywhere says WHICH canonicalization.
|
|
404
|
+
* ZIFFER-SPEC-001 pins the other two: RFC 8785 (JCS) for the Proposal,
|
|
405
|
+
* canonical CBOR (RFC 8949 4.2) for the receipt and the Attestation Object
|
|
406
|
+
* (WE-1, WE-2, AT-8a). AT-8a's whole rationale is that object hashing moved
|
|
407
|
+
* onto the binding path, so any encoder disagreement between issuer and
|
|
408
|
+
* verifier becomes a freshness and single-use defect. This object is on that
|
|
409
|
+
* same path by construction: its id is a hash over its canonical encoding
|
|
410
|
+
* and the Consumption Ledger consumes that id (ACK-5). Two encoders means
|
|
411
|
+
* two ids for one acknowledgement, so a re-encoded acknowledgement claims a
|
|
412
|
+
* fresh slot and replays -- Y1b reopening in the machinery a later fix
|
|
413
|
+
* introduced, which is the RES-8/9/10 recurrence pattern this repository has
|
|
414
|
+
* now published five times. The rule AT-8a states for the Attestation Object
|
|
415
|
+
* is the rule this object needs and does not have. Filed, not invented here.
|
|
416
|
+
*
|
|
417
|
+
* WHAT IS NOT A FIELD. The acknowledgement id. ACK-5 recomputes it as a hash
|
|
418
|
+
* over the canonical object and consumes it in the ledger; it is never
|
|
419
|
+
* transmitted and never read from the message. That is Y1b's lesson applied
|
|
420
|
+
* before it could be rediscovered, and a schema property for it would invite
|
|
421
|
+
* exactly the read that was closed as Y1. There is likewise no field naming
|
|
422
|
+
* the pending action other than `proposal_hash`, no field asserting that the
|
|
423
|
+
* acknowledger was notified (that is the Executor's own record, tested under
|
|
424
|
+
* DR-5), and no field carrying the reversibility class (RV-3: recomputed
|
|
425
|
+
* from the signed bundle, never read from a message).
|
|
426
|
+
*
|
|
427
|
+
* SUITE 12 CLASSIFICATION. Every field here is class T at the SCHEMA layer,
|
|
428
|
+
* because a schema constrains shape and shape is not authority. What lifts
|
|
429
|
+
* them is stated on each property and enforced elsewhere: `acknowledger`
|
|
430
|
+
* becomes B once the signature over the canonical object verifies against
|
|
431
|
+
* the key the SIGNED BUNDLE registers for that name (ACK-2/ACK-4, and row 27
|
|
432
|
+
* of the classification table records that it was T through v1.3.11 -- that
|
|
433
|
+
* was T-31); `proposal_hash` becomes B against the Executor's own recomputed
|
|
434
|
+
* hash (ACK-3); `policy_bundle_hash` and `bundle_epoch` are compared against
|
|
435
|
+
* the bundle the Executor already holds and are never used to SELECT a
|
|
436
|
+
* bundle.
|
|
437
|
+
*
|
|
438
|
+
* WHAT THIS SCHEMA DOES NOT CONSTRAIN, STATED BEFORE ANYTHING IT DOES. (1)
|
|
439
|
+
* ORDER. The reference consumes the ledger slot inside `_verify_ack`, BEFORE
|
|
440
|
+
* the `decision`, DR-5 membership and DR-9 non-operator tests run -- so
|
|
441
|
+
* submitting a captured, validly signed REPUDIATE object to the confirmation
|
|
442
|
+
* endpoint burns its id, the genuine repudiation is then refused as a replay
|
|
443
|
+
* (CL-3), and a REVERSIBLE unsampled floor-HIGH action releases on silence
|
|
444
|
+
* under DR-7 with its veto suppressed. Executed, not reasoned about:
|
|
445
|
+
* `confirm(REPUDIATE)` refuses at ACK-1, `repudiate` then refuses at CL-3,
|
|
446
|
+
* `release` then succeeds. DR-5 exists to stop a veto being suppressed by
|
|
447
|
+
* the approval chain, and no shape constraint can express this. (2) The
|
|
448
|
+
* ACK-6 window LENGTH bound, and that the acknowledgement falls inside the
|
|
449
|
+
* hold window it is acknowledging. (3) CR-3's requirement that the signature
|
|
450
|
+
* key set exactly match the declared suite, and CR-4's containment against
|
|
451
|
+
* the bundle floor -- both are decided against the signed bundle, which a
|
|
452
|
+
* schema cannot see. (4) DR-3 delivery and DR-8 deliverability. (5) Whether
|
|
453
|
+
* any human read anything: A-8 is untouched, and acknowledgement remains an
|
|
454
|
+
* input to the model rather than evidence of comprehension.
|
|
455
|
+
* Generated from acknowledgement.schema.json.
|
|
456
|
+
*/
|
|
457
|
+
export interface Acknowledgement {
|
|
458
|
+
/**
|
|
459
|
+
* The signed object. Carried in its own member rather than flattened beside
|
|
460
|
+
* the signature because the id and the signature are both taken over THIS
|
|
461
|
+
* map alone (ACK-2, ACK-5): flattening would put the signature inside the
|
|
462
|
+
* bytes it signs. The reference refuses an acknowledgement with no `obj` at
|
|
463
|
+
* ACK-1 by name, because the v1.3.11 form that this member's absence
|
|
464
|
+
* identifies is a bare string -- T-31, an identity anyone could type.
|
|
465
|
+
*/
|
|
466
|
+
readonly obj: AcknowledgementObject;
|
|
467
|
+
/**
|
|
468
|
+
* CR-2: a signature under a suite is a MAP from primitive name to signature
|
|
469
|
+
* value, one entry per primitive of the suite named in `obj.alg`. A bare
|
|
470
|
+
* scalar signature MUST NOT be accepted under any suite, not even a
|
|
471
|
+
* single-primitive one -- format leniency is a downgrade in disguise, and
|
|
472
|
+
* the reference refuses a non-map before it looks at any value.
|
|
473
|
+
*
|
|
474
|
+
* OPEN MAP, DELIBERATELY. The suite registry lives in the SIGNED BUNDLE
|
|
475
|
+
* (CR-1) and CR-7 makes migration forward-only, requiring a deployment to
|
|
476
|
+
* verify structures signed under a HIGHER suite than its floor. A closed
|
|
477
|
+
* `{classical, pq}` object here would hard-code one suite into the wire
|
|
478
|
+
* format and make CR-7 unimplementable; it would also be a second registry
|
|
479
|
+
* of primitive names beside the bundle's, which is the two-definitions
|
|
480
|
+
* defect.
|
|
481
|
+
*
|
|
482
|
+
* CR-3 IS CONJUNCTIVE AND IS NOT EXPRESSIBLE HERE. Verification succeeds
|
|
483
|
+
* only if EVERY primitive of the declared suite verifies and the supplied
|
|
484
|
+
* set EXACTLY matches it -- no missing entries and no extra ones, an
|
|
485
|
+
* accepted extra primitive being an undeclared code path chosen by the party
|
|
486
|
+
* under verification. Whether this map's key set matches depends on
|
|
487
|
+
* `obj.alg` resolved against the bundle, so it is checked in code and not
|
|
488
|
+
* here.
|
|
489
|
+
*
|
|
490
|
+
* THE VALUE TYPE IS NOT PINNED BY ANY CLAUSE, AND THE TWO IMPLEMENTATIONS OF
|
|
491
|
+
* IT DISAGREE. WE-4 pins `b64:` plus padded RFC 4648 4 base64 for `any
|
|
492
|
+
* signature or byte string carried as text`, while
|
|
493
|
+
* `reference/src/acp_executor.py` hex-encodes every signature value with the
|
|
494
|
+
* comment that signatures travel through JSON and raw bytes do not, and
|
|
495
|
+
* `spec/schemas/bundle/attesters.schema.json` carries public keys as bare
|
|
496
|
+
* base64 with no prefix at all. Three encodings of one concept in one
|
|
497
|
+
* repository. A pattern is therefore NOT asserted here, because asserting
|
|
498
|
+
* one would decide a normative question by writing a schema -- exactly what
|
|
499
|
+
* ACP-87 was filed rather than done.
|
|
500
|
+
*
|
|
501
|
+
* ABSENT-ENTRY RULE, carried as documentation and NOT enforced by this type,
|
|
502
|
+
* because this is a message and not signed policy. An entry absent from this
|
|
503
|
+
* table is a REFUSAL under CR-3, never a default value: a primitive named by
|
|
504
|
+
* the declared suite and absent from this map is a hybrid signature verified
|
|
505
|
+
* on fewer primitives than it claims, which is the OR composition CR-3
|
|
506
|
+
* forbids: a hybrid verifier that accepts when EITHER half checks out is
|
|
507
|
+
* weaker than its weaker member, so an attacker who breaks one primitive is
|
|
508
|
+
* unconstrained by the other. Refuse, never treat a missing primitive as
|
|
509
|
+
* satisfied and never verify the subset that is present.
|
|
510
|
+
*
|
|
511
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
512
|
+
* type: values minLength 1.
|
|
513
|
+
*/
|
|
514
|
+
readonly sig: Readonly<Record<string, string>>;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* The lenient projection of Acknowledgement: every field optional, every enum
|
|
518
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
519
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
520
|
+
*/
|
|
521
|
+
export interface RawAcknowledgement {
|
|
522
|
+
readonly obj?: RawAcknowledgementObject;
|
|
523
|
+
readonly sig?: Readonly<Record<string, string>>;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* The signed acknowledgement object -- the bytes the signature covers and
|
|
527
|
+
* the bytes the id is derived from. ACK-1's closed field set, exactly
|
|
528
|
+
* `ACK1_FIELDS` in `reference/src/acp_ack.py`, in the order that file
|
|
529
|
+
* declares them. Adding a field here is a BREAKING change, never an additive
|
|
530
|
+
* one, for the Z4 reason on the root: a field set with an optional member
|
|
531
|
+
* gives one object two ids and two ledger slots.
|
|
532
|
+
* Generated from acknowledgement.schema.json#/$defs/acknowledgement_object.
|
|
533
|
+
*/
|
|
534
|
+
export interface AcknowledgementObject {
|
|
535
|
+
/**
|
|
536
|
+
* THE BINDING, and the reason the object exists rather than a name. ACK-3:
|
|
537
|
+
* this MUST equal the hash the Executor recomputed for the action it is
|
|
538
|
+
* about to release; an acknowledgement of a different action is not an
|
|
539
|
+
* acknowledgement of this one. Without it a captured acknowledgement for a
|
|
540
|
+
* routine action is replayed onto a dangerous one, which is Y1 one layer up
|
|
541
|
+
* -- there a genuine quorum raised for P1 verified against an
|
|
542
|
+
* attacker-chosen P2.
|
|
543
|
+
*
|
|
544
|
+
* The Executor compares against ITS OWN recomputed hash, never against a
|
|
545
|
+
* hash the same message supplied: a transmitted identifier is a name for a
|
|
546
|
+
* binding, not evidence of one (RES-8).
|
|
547
|
+
*/
|
|
548
|
+
readonly proposalHash: Sha256Hash;
|
|
549
|
+
/**
|
|
550
|
+
* The signed bundle the acknowledger was acting under. ACK-3 requires it to
|
|
551
|
+
* equal the hash of the bundle the Executor holds, so an acknowledgement
|
|
552
|
+
* raised under a superseded policy cannot release an action graded under the
|
|
553
|
+
* current one. It selects nothing: the Executor already has its bundle and
|
|
554
|
+
* this value is compared to it, because a hash that chose which policy to
|
|
555
|
+
* load would let the acknowledging party choose the policy that judges them.
|
|
556
|
+
*/
|
|
557
|
+
readonly policyBundleHash: Sha256Hash;
|
|
558
|
+
/**
|
|
559
|
+
* Epoch of that bundle, compared for equality by ACK-3 alongside the hash.
|
|
560
|
+
* Carried as well as the hash because the two fail differently and a reader
|
|
561
|
+
* should see which: a hash mismatch means a different bundle, an epoch
|
|
562
|
+
* mismatch means a different version of the policy basis. Epoch MONOTONICITY
|
|
563
|
+
* is not a property of this field -- it is enforced at the KMS against a
|
|
564
|
+
* durable high-water mark (RAD-3), never from a value a message carries.
|
|
565
|
+
*
|
|
566
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
567
|
+
* type: minimum 0.
|
|
568
|
+
*/
|
|
569
|
+
readonly bundleEpoch: number;
|
|
570
|
+
/**
|
|
571
|
+
* The identity acknowledging, INSIDE the signature. ACK-4 is the whole of
|
|
572
|
+
* T-31's lesson: the identity used for the DR-9 non-operator test and the
|
|
573
|
+
* DR-5 notified-membership test is taken FROM THE SIGNED BYTES, never from
|
|
574
|
+
* the call that delivered the object. Through v1.3.11 it was a bare string
|
|
575
|
+
* checked only for set membership and inequality with the operator -- the
|
|
576
|
+
* release gate held no key material at all, and an irreversible action
|
|
577
|
+
* released with `human_verified` true and zero signatures verified.
|
|
578
|
+
*
|
|
579
|
+
* ACK-2 makes the name resolvable rather than authoritative: it selects a
|
|
580
|
+
* key from the attester registry inside the SIGNED bundle, and an identity
|
|
581
|
+
* with no registered key cannot acknowledge anything. The signature
|
|
582
|
+
* establishes who signed; this field only says which enrolled key to check
|
|
583
|
+
* it against. It does not authorise anyone to name themselves.
|
|
584
|
+
*
|
|
585
|
+
* BOUNDED AT 128 BECAUSE THE REGISTRY IT MUST RESOLVE IN IS.
|
|
586
|
+
* `spec/schemas/bundle/attesters.schema.json` bounds its `propertyNames` at
|
|
587
|
+
* 1..128, so a longer value cannot name an enrolled attester and the same
|
|
588
|
+
* bound belongs on both sides of the lookup. It is also the bound
|
|
589
|
+
* `AttestationObject.operator` carries, which matters because DR-9 compares
|
|
590
|
+
* this value to that one: two bounds on one identity space would make a name
|
|
591
|
+
* that is a valid acknowledger and an invalid operator, and the non-operator
|
|
592
|
+
* test compares them.
|
|
593
|
+
*
|
|
594
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
595
|
+
* type: minLength 1, maxLength 128.
|
|
596
|
+
*/
|
|
597
|
+
readonly acknowledger: string;
|
|
598
|
+
/**
|
|
599
|
+
* Which of the two acts this object is: CONFIRM is the DR-9 positive
|
|
600
|
+
* acknowledgement that permits an IRREVERSIBLE floor-HIGH action to release;
|
|
601
|
+
* REPUDIATE is the DR-4 veto that blocks release with a critical alert and
|
|
602
|
+
* requires a fresh quorum rather than a replay.
|
|
603
|
+
*
|
|
604
|
+
* HOISTED INTO $defs rather than written inline for the reason
|
|
605
|
+
* `attester_role` gives in the bundle schemas: an inline enum has no name,
|
|
606
|
+
* and a generator inventing one invents a name that drifts.
|
|
607
|
+
*
|
|
608
|
+
* DELIBERATELY NOT ORDERED. A repudiation does not outrank a confirmation on
|
|
609
|
+
* some scale; they are different acts with different consequences, and
|
|
610
|
+
* DR-4's precedence -- a repudiated action fails closed at release however
|
|
611
|
+
* many confirmations it also collected -- is enforced in the release step
|
|
612
|
+
* against `repudiated_by`, not by comparing two enum members.
|
|
613
|
+
*
|
|
614
|
+
* THIS FIELD IS INSIDE THE SIGNATURE, AND THAT IS THE POINT. It is the
|
|
615
|
+
* discriminator between the two legs, so an attacker who could rewrite it
|
|
616
|
+
* would turn a veto into an approval. Being an ACK-1 field it is
|
|
617
|
+
* signature-covered, exactly as CR-5 requires of `alg` and AT-1 of
|
|
618
|
+
* `operator` after Y4. A verifier MUST still check it against the endpoint
|
|
619
|
+
* that received the object: a valid REPUDIATE object is not a confirmation
|
|
620
|
+
* merely because it was posted to the confirmation path.
|
|
621
|
+
*/
|
|
622
|
+
readonly decision: AcknowledgementDecision;
|
|
623
|
+
/**
|
|
624
|
+
* Freshness material, so that two acknowledgements of the same action under
|
|
625
|
+
* the same bundle by the same party are distinct objects with distinct ids.
|
|
626
|
+
* ACK-5 consumes the derived id in the ledger, so this is what stops a
|
|
627
|
+
* second honest acknowledgement colliding with the first.
|
|
628
|
+
*
|
|
629
|
+
* ITS TYPE AND ITS ENTROPY ARE UNSTATED, AND THE REFERENCE MAKES IT
|
|
630
|
+
* PREDICTABLE. AT-1 requires a 128-bit FRESH nonce for the Attestation
|
|
631
|
+
* Object and WE-4 pins that value's wire type; no clause says anything about
|
|
632
|
+
* this one, and `make_ack` builds it as the acknowledger's own name joined
|
|
633
|
+
* to a millisecond timestamp (observed: `ack-op_1121-1787598182147`). It is
|
|
634
|
+
* therefore neither random nor unique -- two acknowledgements by one party
|
|
635
|
+
* in one millisecond collide -- and, because every other field of the object
|
|
636
|
+
* is already known to the operator, the derived ledger id is guessable to
|
|
637
|
+
* within the timestamp resolution. The ledger id is the single-use key, so a
|
|
638
|
+
* guessed id can be claimed before the honest acknowledgement arrives, and
|
|
639
|
+
* the honest one is then refused as a replay.
|
|
640
|
+
*
|
|
641
|
+
* `minLength: 1` and nothing more, deliberately. A zero-length nonce
|
|
642
|
+
* contributes no freshness at all, which is a shape claim a schema may make;
|
|
643
|
+
* a length or an alphabet would be this schema inventing the normative rule
|
|
644
|
+
* that is missing, and inventing it here is how a type comes to exist in a
|
|
645
|
+
* schema and nowhere else -- the defect the sibling wire schema records
|
|
646
|
+
* against its own `att_nonce`.
|
|
647
|
+
*
|
|
648
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
649
|
+
* type: minLength 1.
|
|
650
|
+
*/
|
|
651
|
+
readonly ackNonce: string;
|
|
652
|
+
/**
|
|
653
|
+
* When the acknowledgement was raised, in seconds since the Unix epoch.
|
|
654
|
+
* ACK-6 requires `issued_at <= now <= expires_at` at the consumer, evaluated
|
|
655
|
+
* against NTS-disciplined wall time within the L-20 skew allowance (leg F9.1
|
|
656
|
+
* carries that dependency).
|
|
657
|
+
*
|
|
658
|
+
* THIS IS THE ONE STRUCTURE IN THE WIRE FORMAT WHOSE TIMES ARE NUMBERS, AND
|
|
659
|
+
* THE REFERENCE PUTS FLOATS IN IT. Section 9.2's receipt and AT-1's
|
|
660
|
+
* `expires_at` are RFC 3339 strings; only the acknowledgement is numeric,
|
|
661
|
+
* and `make_ack` defaults to `time.time()`, so a float reaches the canonical
|
|
662
|
+
* bytes on every production path. AT-8a forbids a float in a canonical
|
|
663
|
+
* structure because a float is not deterministically encoded, and `canon()`
|
|
664
|
+
* enforces that only at the top level -- a nested float is serialised,
|
|
665
|
+
* observed as `{"ack_nonce":...,"issued_at":1787598182.1470501,...}`. Two
|
|
666
|
+
* runtimes formatting that value differently derive two ids for one
|
|
667
|
+
* acknowledgement, which is precisely the encoding split AT-8a exists to
|
|
668
|
+
* prevent and precisely the ledger-slot defect Y1b closed.
|
|
669
|
+
*
|
|
670
|
+
* `integer` is pinned here because it is the only one of the three encodings
|
|
671
|
+
* present in this repository -- float, integer, RFC 3339 -- that AT-8a
|
|
672
|
+
* permits. Which of the remaining two the acknowledgement SHOULD carry is a
|
|
673
|
+
* normative question ZIFFER-SPEC-001 does not answer, and pinning the
|
|
674
|
+
* narrower of what exists is not the same as answering it.
|
|
675
|
+
*
|
|
676
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
677
|
+
* type: minimum 0.
|
|
678
|
+
*/
|
|
679
|
+
readonly issuedAt: number;
|
|
680
|
+
/**
|
|
681
|
+
* When the acknowledgement stops being valid, in seconds since the Unix
|
|
682
|
+
* epoch, on the same clock and with the same float caveat as `issued_at`.
|
|
683
|
+
*
|
|
684
|
+
* THE WINDOW LENGTH IS ENFORCED BY THE CONSUMER, NOT READ FROM THE MESSAGE.
|
|
685
|
+
* ACK-6 requires `expires_at - issued_at` to be bounded, and that bound is
|
|
686
|
+
* checked by the Executor -- the Y2 lesson, where receipt validity LENGTH
|
|
687
|
+
* was checked nowhere and lived only in the value the signing party wrote,
|
|
688
|
+
* permitting long-lived pre-positioned receipts. A schema cannot express a
|
|
689
|
+
* relation between two fields, so this constraint is stated and not
|
|
690
|
+
* asserted.
|
|
691
|
+
*
|
|
692
|
+
* THE BOUND ITSELF HAS NO CLAUSE. The reference uses 120 seconds and its
|
|
693
|
+
* comment says the figure mirrors L-14 -- but L-14 is the ALLOW receipt
|
|
694
|
+
* validity ceiling, a limit on a different artifact with a different
|
|
695
|
+
* lifetime, and the section 13 limits table has no row for an
|
|
696
|
+
* acknowledgement window. A borrowed number is not a normative bound;
|
|
697
|
+
* recorded rather than restated as one.
|
|
698
|
+
*
|
|
699
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
700
|
+
* type: minimum 0.
|
|
701
|
+
*/
|
|
702
|
+
readonly expiresAt: number;
|
|
703
|
+
/**
|
|
704
|
+
* The signature suite `sig` is under. CR-1: every signed structure MUST
|
|
705
|
+
* carry an explicit `alg` naming a suite REGISTERED IN THE SIGNED BUNDLE,
|
|
706
|
+
* and an unregistered or unknown suite MUST fail closed. CR-5: it is
|
|
707
|
+
* signature-covered, so an issuer cannot rewrite the suite without
|
|
708
|
+
* invalidating the object -- an acknowledger cannot downgrade its own suite.
|
|
709
|
+
*
|
|
710
|
+
* NO ENUM, DELIBERATELY. The suite set is a bundle property, so an enum here
|
|
711
|
+
* would be a second registry of suite names beside the signed one, and two
|
|
712
|
+
* definitions of one object is the defect this repository is built around.
|
|
713
|
+
* It would also break CR-7, which makes migration forward-only and requires
|
|
714
|
+
* a deployment to verify structures signed under a suite HIGHER than its
|
|
715
|
+
* floor: a suite the schema had not been updated to list would be refused at
|
|
716
|
+
* the wrong layer, for the wrong reason, with the wrong clause name.
|
|
717
|
+
*
|
|
718
|
+
* CR-4 IS CONTAINMENT AND IS DECIDED AGAINST THE BUNDLE. The floor is
|
|
719
|
+
* satisfied when the named suite's primitive set CONTAINS every primitive of
|
|
720
|
+
* the bundle floor's suite -- not by rank. The rank table this replaced was
|
|
721
|
+
* a total order over sets that are not comparable, and it let a deployment
|
|
722
|
+
* whose signed floor said hash-based post-quantum silently accept a lattice
|
|
723
|
+
* one: not a stronger suite accepted, a DIFFERENT hardness assumption
|
|
724
|
+
* substituted, with the floor check reporting satisfaction. The reference
|
|
725
|
+
* checks the floor BEFORE verifying the signature, so a below-floor
|
|
726
|
+
* acknowledgement refuses at CR-4 rather than at ACK-2.
|
|
727
|
+
*
|
|
728
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
729
|
+
* type: minLength 1.
|
|
730
|
+
*/
|
|
731
|
+
readonly alg: string;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* The lenient projection of AcknowledgementObject: every field optional, every enum
|
|
735
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
736
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
737
|
+
*/
|
|
738
|
+
export interface RawAcknowledgementObject {
|
|
739
|
+
readonly proposalHash?: string;
|
|
740
|
+
readonly policyBundleHash?: string;
|
|
741
|
+
readonly bundleEpoch?: number;
|
|
742
|
+
readonly acknowledger?: string;
|
|
743
|
+
readonly decision?: string;
|
|
744
|
+
readonly ackNonce?: string;
|
|
745
|
+
readonly issuedAt?: number;
|
|
746
|
+
readonly expiresAt?: number;
|
|
747
|
+
readonly alg?: string;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* The external commitment to one per-tenant audit chain head: what an
|
|
751
|
+
* anchoring party receives under AU-3, and what a verifier running outside
|
|
752
|
+
* the production trust domain compares its own recomputation against under
|
|
753
|
+
* AU-4. Carried on leg F6.4 of the leg register (ZIFFER-DEPLOY-001 Annex A);
|
|
754
|
+
* leg F6.3 carries the `chain_hash` this object commits to.
|
|
755
|
+
*
|
|
756
|
+
* WHY THE OBJECT EXISTS AT ALL. AU-3 says it in one line: an unanchored
|
|
757
|
+
* in-database chain is tamper-DECORATED, not tamper-EVIDENT. The chain lives
|
|
758
|
+
* inside the production trust domain and a compromised audit writer can
|
|
759
|
+
* rewrite it; what such a writer cannot rewrite is an anchor already
|
|
760
|
+
* published. Everything below serves one comparison - recompute the head
|
|
761
|
+
* from the records under AU-1, then check it against what was anchored. AU-4
|
|
762
|
+
* makes a head that fails to extend a previously anchored head a critical
|
|
763
|
+
* integrity alert, and AU-7 makes coverage by an anchor a precondition of
|
|
764
|
+
* releasing a floor-HIGH action rather than a report written afterwards.
|
|
765
|
+
*
|
|
766
|
+
* NOTHING BELOW IS EVIDENCE ON ITS OWN (RES-8 / RES-9 / RES-10). Every field
|
|
767
|
+
* here is a value the publisher sends. The guarantee comes from two things
|
|
768
|
+
* outside this object: the store is append-only and sits outside the
|
|
769
|
+
* production trust domain (AU-4, ZIFFER-DEPLOY-001 DP-11), and the anchor is
|
|
770
|
+
* verifiable by any party holding the anchor public keys (AU-3a). A verifier
|
|
771
|
+
* that read a head from the chain it is auditing, or accepted an anchor
|
|
772
|
+
* because the audited party said it existed, has taken a derived security
|
|
773
|
+
* value from the party under verification - the class this architecture has
|
|
774
|
+
* hit five times, C2 -> X1 -> Y1 -> Z3 -> W1, each time inside machinery the
|
|
775
|
+
* previous fix introduced.
|
|
776
|
+
*
|
|
777
|
+
* WHY THE FIELD SET IS CLOSED, AND WHY THAT IS A SECURITY PROPERTY. Every
|
|
778
|
+
* field is REQUIRED and `additionalProperties` is false. An anchor is a
|
|
779
|
+
* signed structure (CR-1), so an optional field gives one semantic anchor
|
|
780
|
+
* two canonical encodings, two signed preimages and two values - the Z4
|
|
781
|
+
* defect that AT-8b closes for the Attestation Object one schema over. Here
|
|
782
|
+
* the consequence is worse than a duplicate id, because it lands on an
|
|
783
|
+
* honest party: a publisher that omits the field and a verifier that encodes
|
|
784
|
+
* it as absent-present compute different bytes, the signature fails, and
|
|
785
|
+
* AU-4 raises a critical integrity alert with no attacker anywhere. That is
|
|
786
|
+
* exactly what the AU-1 correction records happening to this subsystem
|
|
787
|
+
* already, from a formula rather than a field.
|
|
788
|
+
*
|
|
789
|
+
* WHAT THIS SCHEMA DOES NOT MODEL, STATED SO THE FILE IS NOT READ WIDER THAN
|
|
790
|
+
* IT IS.
|
|
791
|
+
* (1) The signature envelope. CR-2 makes a signature a map from primitive to
|
|
792
|
+
* signature value and CR-3 requires the supplied set to match the declared
|
|
793
|
+
* suite exactly. That map is keyed by the primitive names whose one home is
|
|
794
|
+
* `spec/schemas/bundle/signature.schema.json`, in the other schema root. A
|
|
795
|
+
* `$ref` crossing roots resolves now (ACP-78 M1, for a named scalar), so
|
|
796
|
+
* what still blocks the envelope is the OPEN MAP and not the reference: the
|
|
797
|
+
* generator's only rule for one is `x-acp-absent`, which states what the
|
|
798
|
+
* SIGNED POLICY says when a lookup misses, and a message carries no
|
|
799
|
+
* authority to answer that -- in the wire root such a rule is now carried as
|
|
800
|
+
* documentation and no accessor is generated from it. So the envelope is
|
|
801
|
+
* disclosed as absent rather than invented in a shape the specification
|
|
802
|
+
* never gave it. This follows the precedent of the sibling Attestation
|
|
803
|
+
* Object schema, which models the AT-1 body and not the surrounding `kind` /
|
|
804
|
+
* `attester` / `sig`.
|
|
805
|
+
* (2) AU-3's mechanism and its evidence. AU-3 permits a public transparency
|
|
806
|
+
* log with inclusion proofs, a threshold signature from at least 3
|
|
807
|
+
* independent parties, or RFC 3161 timestamping. Those three have
|
|
808
|
+
* incompatible evidence shapes - a Merkle inclusion proof, a set of party
|
|
809
|
+
* signatures, a CMS TimeStampToken - and no clause says which the anchor
|
|
810
|
+
* carries, how a verifier learns which was used, or which of the anchor
|
|
811
|
+
* public keys AU-3a means. An outside auditor cannot verify this object from
|
|
812
|
+
* its fields alone, and saying so is more useful than inventing a
|
|
813
|
+
* discriminator.
|
|
814
|
+
* (3) Ordering and rate. AU-3's <= 10 min interval (L-19) and its
|
|
815
|
+
* at-every-bundle-activation rule are properties of a SEQUENCE of anchors;
|
|
816
|
+
* AU-4's extension check is a relation between two of them; AU-7's
|
|
817
|
+
* anchor-before-release is an ordering between an anchor and an execution.
|
|
818
|
+
* None is a property of one object and none is expressible here. They are
|
|
819
|
+
* per-implementation obligations (`spec/vectors/OBLIGATIONS.md`), and
|
|
820
|
+
* `reference/suites/audit_suite.py` is where the reference discharges them.
|
|
821
|
+
* (4) AU-5 retention (>= 24 months or per regulation), AU-6 outage
|
|
822
|
+
* behaviour, and §11.3's eight reconciliation checks. All are about what a
|
|
823
|
+
* deployment does with anchors, never about what one is.
|
|
824
|
+
*
|
|
825
|
+
* ENCODING, AND A GAP. §11.2 fixes `canonical()` for §11 as the single
|
|
826
|
+
* canonicaliser AT-8a requires - canonical CBOR, RFC 8949 §4.2 - and
|
|
827
|
+
* requires chain values to be carried, stored, anchored and fed forward as
|
|
828
|
+
* the 71-character `sha256:` string. That is the only canonicaliser §11
|
|
829
|
+
* names, so it is the reading this schema is written against. But no clause
|
|
830
|
+
* states the anchor's own signed preimage: whether the signature covers this
|
|
831
|
+
* whole object, the `chain_hash` alone, or a mechanism-specific structure.
|
|
832
|
+
* AU-3a cannot hold until it is pinned, for the reason AU-1 already records
|
|
833
|
+
* - two implementations signing different bytes each refuse the other's
|
|
834
|
+
* anchor, and at the verifier a refusal is indistinguishable from tampering.
|
|
835
|
+
* JSON here is diagnostic form, never an accepted input.
|
|
836
|
+
* Generated from anchor.schema.json.
|
|
837
|
+
*/
|
|
838
|
+
export interface Anchor {
|
|
839
|
+
/**
|
|
840
|
+
* The tenant whose chain this anchor commits to. AU-1 makes chains
|
|
841
|
+
* per-tenant and AU-2 makes `(tenant_id, seq)` unique and gapless, so a head
|
|
842
|
+
* with no tenant names no chain: a verifier holding anchors from many
|
|
843
|
+
* tenants cannot tell which recomputation to compare against, and AU-4's
|
|
844
|
+
* extension check has no predecessor to look for.
|
|
845
|
+
*
|
|
846
|
+
* It is a NAME for something already inside the chain, not a second source
|
|
847
|
+
* of it. AU-8 derives genesis from a tenant-creation record over
|
|
848
|
+
* `{tenant_id, created_at, bundle_epoch, schema_version}`, so the tenant is
|
|
849
|
+
* committed from `chain_hash_0` onward and a verifier that trusted this
|
|
850
|
+
* field over the chain would be trusting the publisher about which chain it
|
|
851
|
+
* published.
|
|
852
|
+
*
|
|
853
|
+
* The specification pins no form for a tenant identifier - §9.2 renders one
|
|
854
|
+
* as `ten_04qf` in diagnostic JSON and no clause types it. The only stated
|
|
855
|
+
* bound is L-04 (max string field length 2,048 chars, no exceptions), so it
|
|
856
|
+
* is applied here rather than inventing a narrower one. That L-04's
|
|
857
|
+
* enforcement point is the Input/Output Schema registry and is being read as
|
|
858
|
+
* a general wire bound is an assumption; it is recorded rather than made
|
|
859
|
+
* quietly.
|
|
860
|
+
*/
|
|
861
|
+
readonly tenantId: TenantId;
|
|
862
|
+
/**
|
|
863
|
+
* The sequence number of the record whose head is anchored. AU-2 makes
|
|
864
|
+
* `(tenant_id, seq)` unique and gapless per tenant, and gaplessness is what
|
|
865
|
+
* makes deletion detectable at all - a sequence with holes has no missing
|
|
866
|
+
* record, only a shorter one.
|
|
867
|
+
*
|
|
868
|
+
* Zero is the genesis anchor. AU-8 requires it to be published IMMEDIATELY
|
|
869
|
+
* on tenant creation rather than on AU-3's <= 10 min schedule, because
|
|
870
|
+
* without an anchored genesis the destruction of an entire tenant chain is
|
|
871
|
+
* detectable only after the first scheduled anchor, and a chain destroyed
|
|
872
|
+
* inside that window leaves no evidence it ever existed.
|
|
873
|
+
*
|
|
874
|
+
* An anchor at `seq` commits transitively to every record from 0 to `seq`,
|
|
875
|
+
* because AU-1 chains each head over the previous one. That is derived from
|
|
876
|
+
* the chain construction rather than promised by this field, and it is what
|
|
877
|
+
* §11.3(g) relies on when it asks for an anchor COVERING a floor-HIGH
|
|
878
|
+
* execution record.
|
|
879
|
+
*
|
|
880
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
881
|
+
* type: minimum 0.
|
|
882
|
+
*/
|
|
883
|
+
readonly seq: number;
|
|
884
|
+
/**
|
|
885
|
+
* The anchored head: `chain_hash_seq` as AU-1 constructs it, `H({prev,
|
|
886
|
+
* record})` over the canonical encoding of a two-key map whose key names are
|
|
887
|
+
* part of the preimage. Carried as the 71-character string §11.2 pins, never
|
|
888
|
+
* as the 32 raw digest bytes - that type is pinned in the specification
|
|
889
|
+
* because an implementer who fed forward raw bytes built a different chain
|
|
890
|
+
* while following the text.
|
|
891
|
+
*
|
|
892
|
+
* A NAME FOR A BINDING, NOT EVIDENCE OF ONE. The verifier's job under AU-4
|
|
893
|
+
* is to recompute the head from the records and compare, so this value is
|
|
894
|
+
* one half of a comparison whose other half the verifier computes itself. It
|
|
895
|
+
* is trustworthy only to the degree that the store holding it is append-only
|
|
896
|
+
* and outside the production trust domain; the field carries no authority of
|
|
897
|
+
* its own.
|
|
898
|
+
*
|
|
899
|
+
* THE AU-1 CORRECTION IS WHY THAT SENTENCE IS WORTH WRITING. Through v1.3.13
|
|
900
|
+
* the clause read `SHA-256(chain_hash_{n-1} || canonical(record_n))`, a
|
|
901
|
+
* concatenation, while the reference hashed the canonical encoding of the
|
|
902
|
+
* two-key map. Different preimage, different chain from record 1 onward. The
|
|
903
|
+
* damage was not that an implementer failed to match: an honest third party
|
|
904
|
+
* implementing the clause literally recomputes a different head on an
|
|
905
|
+
* untampered chain, so AU-3a's independent verifiability did not hold, and
|
|
906
|
+
* AU-4 routed an honest verifier into this system's own critical-alert
|
|
907
|
+
* channel with no attacker present. The clause moved and the implementations
|
|
908
|
+
* did not, and the reasoning was published rather than taken silently.
|
|
909
|
+
*/
|
|
910
|
+
readonly chainHash: Sha256Hash;
|
|
911
|
+
/**
|
|
912
|
+
* When the anchor was published. §11.3(g) requires every floor-HIGH
|
|
913
|
+
* execution record to be matched to an anchor DATED AT OR BEFORE release,
|
|
914
|
+
* and AU-7 makes that ordering a release precondition rather than a report,
|
|
915
|
+
* so the comparison needs a date and this is it.
|
|
916
|
+
*
|
|
917
|
+
* WHO STAMPS IT IS THE WHOLE QUESTION, AND NO CLAUSE ANSWERS IT. If the
|
|
918
|
+
* anchoring party stamps it, the field is an external attestation of time
|
|
919
|
+
* and AU-7 means what it says. If the publisher stamps it, the party under
|
|
920
|
+
* verification dates its own alibi: a compromised audit writer backdates the
|
|
921
|
+
* anchor it has just published, and §11.3(g) - one of the mechanisms by
|
|
922
|
+
* which several disclosed residuals are said to be DETECTED rather than
|
|
923
|
+
* prevented - compares two numbers written by the same hand. Detection that
|
|
924
|
+
* can be erased is not detection, in AU-7's own words, and a timestamp the
|
|
925
|
+
* audited party chooses is erasure with an extra step. The Python reference
|
|
926
|
+
* is the second reading: `AnchorService.publish(tenant, seq, head, now)` in
|
|
927
|
+
* `reference/src/acp_audit.py` takes `now` from its caller, and the caller
|
|
928
|
+
* is `AuditChain.anchor_now`, inside the production trust domain. Recorded
|
|
929
|
+
* and filed; a schema cannot settle it.
|
|
930
|
+
*
|
|
931
|
+
* THE TYPE IS NOT THE SPECIFICATION'S EITHER. No clause types an anchor
|
|
932
|
+
* timestamp. RFC 3339 is used because §9.2's `issued_at` and `expires_at`
|
|
933
|
+
* are RFC 3339 instants and §12.5 disciplines every receipt-issuing and
|
|
934
|
+
* receipt-verifying clock with NTS inside the L-20 skew allowance, so a
|
|
935
|
+
* second time format in the one subsystem whose value is retrospective would
|
|
936
|
+
* be a second encoding of one thing. The reference stores a POSIX float, so
|
|
937
|
+
* the two disagree today; that is a divergence to close in one place, not
|
|
938
|
+
* two.
|
|
939
|
+
*/
|
|
940
|
+
readonly anchoredAt: Rfc3339Instant;
|
|
941
|
+
/**
|
|
942
|
+
* The signature suite the anchor was signed under. CR-1 names the audit
|
|
943
|
+
* anchor explicitly among the structures that MUST carry an explicit `alg`
|
|
944
|
+
* identifying a suite registered in the signed bundle, and an unregistered
|
|
945
|
+
* or unknown suite MUST fail closed.
|
|
946
|
+
*
|
|
947
|
+
* IT IS IN THE BODY BECAUSE OF WHAT HAPPENS WHEN IT IS NOT. PB-8 records the
|
|
948
|
+
* reasoning for the bundle SIGNATURE file and it transfers unchanged: the
|
|
949
|
+
* declared suite must be inside what the signature covers, or an attacker
|
|
950
|
+
* relabels a hybrid structure as classical, the verifier obligingly requires
|
|
951
|
+
* one primitive, and CR-3's conjunctive guarantee is decorative at the point
|
|
952
|
+
* it is loaded. An anchor is the structure a downgrade is worth most against
|
|
953
|
+
* - receipts live 120 s (L-14) and attestations 60 min (AT-1), while the
|
|
954
|
+
* audit chain's non-repudiation value is retrospective and measured in
|
|
955
|
+
* years, which is why §1 tells deployments to prioritise post-quantum
|
|
956
|
+
* signing on anchoring and bundle signing over receipt signing.
|
|
957
|
+
*
|
|
958
|
+
* THE SUITE-NAME TYPE HAS ONE HOME AND IT IS NOT THIS FILE. The enumerated
|
|
959
|
+
* names are `SuiteId` at
|
|
960
|
+
* `spec/schemas/bundle/signature.schema.json#/$defs/suite_id`, in the other
|
|
961
|
+
* schema root, alongside the primitive names CR-2's signature map is keyed
|
|
962
|
+
* by. Declaring the enum a second time here would be two definitions of one
|
|
963
|
+
* object - the defect that generator exists to prevent, committed in a file
|
|
964
|
+
* that cites it. Where a type shared by both roots lives is now decided
|
|
965
|
+
* (ACP-78 M1): a named SCALAR is `$ref`d across roots and imported rather
|
|
966
|
+
* than redeclared, which is what the Decision Receipt's `alg` does. This
|
|
967
|
+
* field deliberately does NOT do that, and the reason is the conflict
|
|
968
|
+
* recorded below rather than a missing mechanism: AU-3 requires an EXTERNAL
|
|
969
|
+
* anchoring mechanism while CR-1/CR-4 require the deployment's registered
|
|
970
|
+
* floor, no public transparency log or timestamping authority signs under
|
|
971
|
+
* either registered suite, and typing this field as `SuiteId` would decide
|
|
972
|
+
* that conflict by writing a schema. So the constraint is stated and the
|
|
973
|
+
* type is pointed at rather than made in passing in a schema about anchors.
|
|
974
|
+
*
|
|
975
|
+
* CR-4 IS SATISFIED BY CONTAINMENT, NEVER BY RANK, and the floor is read
|
|
976
|
+
* from the signed bundle rather than from this field. A verifier that
|
|
977
|
+
* ordered suites would place hybrid above `slhdsa128s` and serve a lattice
|
|
978
|
+
* signature to a deployment that chose hash-based precisely to rest on no
|
|
979
|
+
* lattice assumption. CR-6 SHOULD-recommends SLH-DSA for audit anchoring,
|
|
980
|
+
* and `slhdsa128s` is declared and not implemented in either implementation
|
|
981
|
+
* (ZIFFER-DEPLOY-001 DP-27), so a deployment following CR-6 to the letter
|
|
982
|
+
* cannot run today. That tension is the specification's and is named here
|
|
983
|
+
* rather than resolved by a schema.
|
|
984
|
+
*
|
|
985
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
986
|
+
* type: minLength 1, maxLength 2048.
|
|
987
|
+
*/
|
|
988
|
+
readonly alg: string;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* The lenient projection of Anchor: every field optional, every enum
|
|
992
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
993
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
994
|
+
*/
|
|
995
|
+
export interface RawAnchor {
|
|
996
|
+
readonly tenantId?: string;
|
|
997
|
+
readonly seq?: number;
|
|
998
|
+
readonly chainHash?: string;
|
|
999
|
+
readonly anchoredAt?: string;
|
|
1000
|
+
readonly alg?: string;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* One receipt per attestation (CL-3, T-14). A second receipt bearing the
|
|
1004
|
+
* same attester signatures and a fresh nonce is rejected at §9.3 step 7
|
|
1005
|
+
* because the id is already consumed.
|
|
1006
|
+
*
|
|
1007
|
+
* THE OBLIGATION THIS CLAIM DEPENDS ON IS NOT AT THE LEDGER. §9.3 step 7b(v)
|
|
1008
|
+
* requires the Executor to RECOMPUTE `attestation_id :=
|
|
1009
|
+
* SHA-256(canonical(obj))` per AT-1 and to claim the recomputed value, never
|
|
1010
|
+
* an id read from the receipt -- that is Y1b, and the Attestation Object
|
|
1011
|
+
* schema carries no `attestation_id` property for the same reason. The
|
|
1012
|
+
* ledger cannot tell a recomputed id from a transmitted one; both arrive
|
|
1013
|
+
* here as the same 71 characters. So this field is class T AT THE LEDGER and
|
|
1014
|
+
* the control lives entirely at the Executor. Recording that here is the
|
|
1015
|
+
* point: a reader who takes ledger consumption as the whole of the T-14
|
|
1016
|
+
* defence has the same misreading v1.3.2 shipped, where CL-3 guaranteed one
|
|
1017
|
+
* receipt per attestation and never asked whether it was the RIGHT receipt.
|
|
1018
|
+
* Generated from ledger_claim.schema.json#/$defs/attestation_id_claim.
|
|
1019
|
+
*/
|
|
1020
|
+
export interface AttestationIdClaim {
|
|
1021
|
+
readonly tenantId: TenantId;
|
|
1022
|
+
readonly artifactClass: 'attestation_id';
|
|
1023
|
+
/**
|
|
1024
|
+
* The recomputed `attestation_id` (AT-1): SHA-256 over the canonical CBOR
|
|
1025
|
+
* encoding of the whole Attestation Object (AT-8a). Carried with its
|
|
1026
|
+
* `sha256:` prefix per §11.2, which pins the type to the character and
|
|
1027
|
+
* records why it had to -- an implementer who fed forward raw digest bytes
|
|
1028
|
+
* built a different chain while following the text. Here the same slip is
|
|
1029
|
+
* worse than a different chain: a prefixed issuer and an unprefixed verifier
|
|
1030
|
+
* derive two ids for one object and claim two ledger slots, which is Z4 and
|
|
1031
|
+
* T-14 reopening with no optional field anywhere in reach.
|
|
1032
|
+
*/
|
|
1033
|
+
readonly identifier: Sha256Hash;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* The lenient projection of AttestationIdClaim: every field optional, every enum
|
|
1037
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
1038
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
1039
|
+
*/
|
|
1040
|
+
export interface RawAttestationIdClaim {
|
|
1041
|
+
readonly tenantId?: string;
|
|
1042
|
+
readonly artifactClass?: string;
|
|
1043
|
+
readonly identifier?: string;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* A signed human act bound to a `proposal_hash`. Carried on leg F4.4 of the
|
|
1047
|
+
* leg register (ZIFFER-DEPLOY-001 Annex A). F4.3 named the signing act
|
|
1048
|
+
* inside the approval domain, which crosses no domain boundary and is
|
|
1049
|
+
* therefore not a leg (DP-83); ACP-86 retired the id rather than reusing it,
|
|
1050
|
+
* and AT-1 and AT-8a are cited on F4.4, where the object arrives.
|
|
1051
|
+
*
|
|
1052
|
+
* WHY THIS SCHEMA IS CLOSED, AND WHY THAT IS THE SECURITY PROPERTY RATHER
|
|
1053
|
+
* THAN A STYLE CHOICE. AT-8b: every AT-1 field is REQUIRED; there are no
|
|
1054
|
+
* optional fields, no defaultable fields, and no extension points. An object
|
|
1055
|
+
* carrying an unknown field, or omitting any AT-1 field, MUST be rejected —
|
|
1056
|
+
* never normalized, never defaulted. That is why `additionalProperties` is
|
|
1057
|
+
* false and why `required` lists every property, and it is the one place in
|
|
1058
|
+
* this repository where an absent-value rule would be a DEFECT rather than a
|
|
1059
|
+
* control: `x-acp-absent` deliberately appears nowhere below.
|
|
1060
|
+
*
|
|
1061
|
+
* The reason is Z4. AT-8a pinned the canonicalizer but not the field set.
|
|
1062
|
+
* Given ANY optional field, an object present-as-null and the same object
|
|
1063
|
+
* with the field omitted are EACH valid canonical CBOR, hash to TWO DISTINCT
|
|
1064
|
+
* ids, and therefore claim TWO ledger slots — one attestation, consumed
|
|
1065
|
+
* twice. T-14 attestation amplification reopens through the very mechanism
|
|
1066
|
+
* Y1b closed. Canonicalization cannot fix it: the ambiguity is in the field
|
|
1067
|
+
* set, not the encoding. Mechanized as `Z4_OptionalFieldYieldsTwoIds`.
|
|
1068
|
+
*
|
|
1069
|
+
* CONSEQUENCE FOR ANYONE EDITING THIS FILE: adding a field here is a
|
|
1070
|
+
* BREAKING change requiring a `receipt_version` increment, never an additive
|
|
1071
|
+
* one (AT-8b). A future revision that adds an optional field to be helpful
|
|
1072
|
+
* reopens T-14.
|
|
1073
|
+
*
|
|
1074
|
+
* ENCODING. The wire form is canonical CBOR, RFC 8949 §4.2 (AT-8a) — the
|
|
1075
|
+
* same canonicalization as the receipt (WE-1/WE-2). JCS (RFC 8785) MUST NOT
|
|
1076
|
+
* be used for this object; JCS is the Proposal's rule and mixing them is the
|
|
1077
|
+
* encoding split at the point it does the most damage. JSON here is the
|
|
1078
|
+
* diagnostic form the schema is written against, never an accepted input. An
|
|
1079
|
+
* object whose RECEIVED encoding is not the canonical one MUST be rejected
|
|
1080
|
+
* and MUST NOT be re-serialized and accepted.
|
|
1081
|
+
*
|
|
1082
|
+
* WHAT IS NOT A FIELD. `attestation_id` is DERIVED, not carried: it is
|
|
1083
|
+
* SHA-256 over the canonical CBOR encoding of this object (AT-1, AT-8a) and
|
|
1084
|
+
* the Executor recomputes it (§9.3 step 7b(v)). Transmitting it for trust is
|
|
1085
|
+
* what v1.3.2 did, and under one compromised KMS a receipt bearing a genuine
|
|
1086
|
+
* quorum raised for proposal P1 verified against an attacker-chosen P2 — Y1.
|
|
1087
|
+
* A schema that gave `attestation_id` a property would invite exactly that
|
|
1088
|
+
* read, so it has none.
|
|
1089
|
+
*
|
|
1090
|
+
* SUITE 12 CLASSIFICATION. Every field below is class T at the schema layer
|
|
1091
|
+
* — this document constrains SHAPE, and shape is not authority. The
|
|
1092
|
+
* Executor's obligations are elsewhere and are not restated here
|
|
1093
|
+
* (DP-3/DP-84): `proposal_hash` is bound (9.3-3), floor-only risk is
|
|
1094
|
+
* RECOMPUTED and never read from here (TR-8), and `required_count` is
|
|
1095
|
+
* subject to AT-9's two disjoint rules described on that property.
|
|
1096
|
+
* Generated from attestation_object.schema.json.
|
|
1097
|
+
*/
|
|
1098
|
+
export interface AttestationObject {
|
|
1099
|
+
/**
|
|
1100
|
+
* The suite this attestation was raised under. CR-1: every signed structure
|
|
1101
|
+
* carries an explicit `alg` naming a suite registered in the signed bundle,
|
|
1102
|
+
* and an unregistered or unknown name fails closed rather than being
|
|
1103
|
+
* resolved to a known one.
|
|
1104
|
+
*
|
|
1105
|
+
* IT IS AN AT-1 FIELD, WHICH IS WHY IT IS HERE AND NOT BESIDE THE SIGNATURE.
|
|
1106
|
+
* CR-5 makes `alg` signature-covered, so it sits inside the canonical CBOR
|
|
1107
|
+
* whose SHA-256 is `attestation_id` (AT-8a). An issuer who could relabel the
|
|
1108
|
+
* suite without changing the object would hand a verifier a hybrid entry to
|
|
1109
|
+
* check as a classical one, and CR-3's conjunctive rule would be decorative
|
|
1110
|
+
* at the point it is applied. PB-8 records the same reasoning for the bundle
|
|
1111
|
+
* signature file and anchor.schema.json restates it for the anchor; this is
|
|
1112
|
+
* the third artifact it governs and the one where the field set is closed by
|
|
1113
|
+
* AT-8b, so a verifier reading `alg` from anywhere else would be reading a
|
|
1114
|
+
* field the object may not carry.
|
|
1115
|
+
*
|
|
1116
|
+
* WHAT THE VERIFIER DOES WITH IT IS NOT ONE RULE BUT TWO, AND THE SCHEMA
|
|
1117
|
+
* CANNOT PICK. For a `hybrid` registry entry the name is a machine suite and
|
|
1118
|
+
* CR-4 compares it against the bundle's registered floor by CONTAINMENT of
|
|
1119
|
+
* primitives, never by rank. For a `webauthn` entry it is one of HM-4's two
|
|
1120
|
+
* key types, CR-8 puts a human leg outside that floor entirely, and HM-3
|
|
1121
|
+
* requires it to EQUAL the registry entry's `alg`, refusing a mismatch
|
|
1122
|
+
* before a byte of the assertion is examined. Which rule applies is decided
|
|
1123
|
+
* by the registry, which this object does not name, so the shape layer
|
|
1124
|
+
* admits both vocabularies and narrows nothing. See `attestation_alg` below
|
|
1125
|
+
* for why that is a union of two declarations and not a third list.
|
|
1126
|
+
*
|
|
1127
|
+
* ADDED IN v1.3.31 (ACP-299), AS A CORRECTION AND NOT A FEATURE. This schema
|
|
1128
|
+
* had no `alg` property through v1.3.30 while CR-5 called it an AT-1 field,
|
|
1129
|
+
* `reference/src/acp_executor.py` listed it in `AT1_FIELDS` and
|
|
1130
|
+
* `crates/acp-decision/src/quorum.rs` listed it in `AT1_FIELDS` and
|
|
1131
|
+
* re-parsed it to pick the suite. The field set is CLOSED in both directions
|
|
1132
|
+
* (AT-8b), so an object built from this file was refused by both verifiers
|
|
1133
|
+
* for missing a field, and an object either verifier accepted carried a
|
|
1134
|
+
* field this file called additional. The cross-language differential could
|
|
1135
|
+
* not see it, because the two implementations agreed with each other and
|
|
1136
|
+
* disagreed with the document -- the eighth lesson in
|
|
1137
|
+
* `dossier/05-TEST-EVIDENCE.md`, and exactly the shape ACP-167 had one field
|
|
1138
|
+
* over. `tools/check-attestation-object.py` is the executable consumer that
|
|
1139
|
+
* would have caught it and now does.
|
|
1140
|
+
*/
|
|
1141
|
+
readonly alg: AttestationAlg;
|
|
1142
|
+
/**
|
|
1143
|
+
* The canonical Proposal this attester signed. The binding the whole object
|
|
1144
|
+
* exists to carry: the Executor verifies the executed proposal hashes to
|
|
1145
|
+
* this value (9.3-3) rather than trusting that it does.
|
|
1146
|
+
*/
|
|
1147
|
+
readonly proposalHash: Sha256Hash;
|
|
1148
|
+
/**
|
|
1149
|
+
* The signed bundle in force when the attestation was raised.
|
|
1150
|
+
*/
|
|
1151
|
+
readonly policyBundleHash: Sha256Hash;
|
|
1152
|
+
/**
|
|
1153
|
+
* Epoch of that bundle. Monotonicity is enforced at the KMS against a
|
|
1154
|
+
* durable high-water mark (RAD-3), never from this value.
|
|
1155
|
+
*
|
|
1156
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1157
|
+
* type: minimum 0.
|
|
1158
|
+
*/
|
|
1159
|
+
readonly bundleEpoch: number;
|
|
1160
|
+
/**
|
|
1161
|
+
* The Context read the Decision was computed over (§8.8 `snapshot_hash()`).
|
|
1162
|
+
*/
|
|
1163
|
+
readonly contextSnapshotHash: Sha256Hash;
|
|
1164
|
+
/**
|
|
1165
|
+
* FLOOR-ONLY risk, not effective risk. AT-3 requires the attester to have
|
|
1166
|
+
* been shown floor tiers and the bundle `rule_id`s that raised the risk, NOT
|
|
1167
|
+
* Context-asserted values — that separation is what closes A-8's display
|
|
1168
|
+
* path. The Executor recomputes this from the signed bundle and the
|
|
1169
|
+
* canonical Proposal (TR-8); a disagreement with the value here is a
|
|
1170
|
+
* critical alert, because it means the signing substrate emitted a Decision
|
|
1171
|
+
* the bundle does not support.
|
|
1172
|
+
*/
|
|
1173
|
+
readonly floorOnlyRisk: Risk;
|
|
1174
|
+
/**
|
|
1175
|
+
* Attester roles the rule demanded. `uniqueItems` because a repeated role is
|
|
1176
|
+
* a second spelling of one requirement, and two spellings of one thing is
|
|
1177
|
+
* the defect this repository is built around.
|
|
1178
|
+
*
|
|
1179
|
+
* THE ARRAY MAY BE EMPTY, AND EMPTY HAS A MEANING: the rule demanded a
|
|
1180
|
+
* quorum and named no role, so any enrolled attester of the right kind
|
|
1181
|
+
* satisfies it. AT-8b keeps the FIELD required — an absent `required_roles`
|
|
1182
|
+
* is still a refusal, because an absent field and an empty array would be
|
|
1183
|
+
* two encodings of one object (Z4) — but nothing sets a lower bound on its
|
|
1184
|
+
* length.
|
|
1185
|
+
*
|
|
1186
|
+
* `minItems: 1` STOOD HERE THROUGH v1.3.31 AND WAS REMOVED IN v1.3.32
|
|
1187
|
+
* (ACP-308). It was a schema-level choice with no clause behind it — the
|
|
1188
|
+
* shape `operator_id` already records about its own length bound — and it
|
|
1189
|
+
* made the common case invalid: `tools/check-attestation-object.py` found on
|
|
1190
|
+
* its first run that the simulation builds a floor-HIGH object with
|
|
1191
|
+
* `required_roles: []` for every action whose rule names no role, which is
|
|
1192
|
+
* four of its five floor-HIGH actions. Neither verifier reads this field for
|
|
1193
|
+
* the quorum DECISION: the threshold is `quorum_k` from the signed bundle
|
|
1194
|
+
* (AT-9, PB-6, class R) and role membership is resolved against the
|
|
1195
|
+
* registry, so an empty list withholds nothing a verifier needed. Stating
|
|
1196
|
+
* the bound in AT-1 instead was the alternative, and it was rejected because
|
|
1197
|
+
* it would force every floor-HIGH rule an operator writes to invent a role
|
|
1198
|
+
* name for a requirement it does not have — a policy-authoring obligation
|
|
1199
|
+
* this document never asked for, imposed by a validator rather than by a
|
|
1200
|
+
* clause.
|
|
1201
|
+
*
|
|
1202
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1203
|
+
* type: uniqueItems; items minLength 1, maxLength 128.
|
|
1204
|
+
*/
|
|
1205
|
+
readonly requiredRoles: readonly string[];
|
|
1206
|
+
/**
|
|
1207
|
+
* The threshold the attester was shown and signed.
|
|
1208
|
+
*
|
|
1209
|
+
* AT-9 places TWO requirements on this field and NEITHER SUBSTITUTES FOR THE
|
|
1210
|
+
* OTHER. (1) It MUST NOT be an input to the quorum decision, in whole or in
|
|
1211
|
+
* part, INCLUDING where every entry agrees on it: the threshold is taken
|
|
1212
|
+
* from the signed bundle's `quorum_k` (PB-6) and is class R — recomputed.
|
|
1213
|
+
* `required_count` is signed by the attester, and the attester is exactly
|
|
1214
|
+
* the party under verification, so reading the threshold from it asks the
|
|
1215
|
+
* adversary how large a quorum to demand. (2) It MUST nonetheless equal
|
|
1216
|
+
* `quorum_k`, and a mismatch MUST fail closed with a critical alert —
|
|
1217
|
+
* because it is part of what the attester was SHOWN (AT-3), so a mismatch
|
|
1218
|
+
* means the humans approved under a policy the engine did not apply.
|
|
1219
|
+
*
|
|
1220
|
+
* An implementation that keeps only (1) satisfies INV-1-HIGH while silently
|
|
1221
|
+
* executing actions no attester agreed to. This clause exists because its
|
|
1222
|
+
* absence was exploited — see the v1.3.15 alert in §1(a).
|
|
1223
|
+
*
|
|
1224
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1225
|
+
* type: minimum 1.
|
|
1226
|
+
*/
|
|
1227
|
+
readonly requiredCount: number;
|
|
1228
|
+
/**
|
|
1229
|
+
* The operator the action is attributed to. Carried HERE, inside the
|
|
1230
|
+
* signature, so approver-distinctness (AT-2) and the §9.3 step-9 capability
|
|
1231
|
+
* recheck key on a signature-covered value rather than a receipt-body claim.
|
|
1232
|
+
* The receipt's top-level `operator` is diagnostic only. Mechanized as
|
|
1233
|
+
* `Y4_OperatorTamperDetected`.
|
|
1234
|
+
*/
|
|
1235
|
+
readonly operator: OperatorId;
|
|
1236
|
+
/**
|
|
1237
|
+
* A fresh 128-bit attestation nonce (AT-1), making the derived
|
|
1238
|
+
* `attestation_id` unique per attestation. Single use is enforced by ledger
|
|
1239
|
+
* consumption of that id (AT-5, CL-3), not by assertion — v1.2.0 stated
|
|
1240
|
+
* single-use as a property with no enforcing mechanism, which permitted
|
|
1241
|
+
* attestation amplification (T-14).
|
|
1242
|
+
*/
|
|
1243
|
+
readonly attNonce: Nonce128;
|
|
1244
|
+
/**
|
|
1245
|
+
* RFC 3339 instant, at most 60 minutes after issuance (AT-1). Expired
|
|
1246
|
+
* attestations are void and partial quorum at expiry is void (AT-5).
|
|
1247
|
+
* Evaluated against NTS-disciplined wall time within the L-20 skew
|
|
1248
|
+
* allowance, never against a monotonic clock — leg F9.1 carries that
|
|
1249
|
+
* dependency and DP-52 states the distinction.
|
|
1250
|
+
*/
|
|
1251
|
+
readonly expiresAt: Rfc3339Instant;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* The lenient projection of AttestationObject: every field optional, every enum
|
|
1255
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
1256
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
1257
|
+
*/
|
|
1258
|
+
export interface RawAttestationObject {
|
|
1259
|
+
readonly alg?: string;
|
|
1260
|
+
readonly proposalHash?: string;
|
|
1261
|
+
readonly policyBundleHash?: string;
|
|
1262
|
+
readonly bundleEpoch?: number;
|
|
1263
|
+
readonly contextSnapshotHash?: string;
|
|
1264
|
+
readonly floorOnlyRisk?: string;
|
|
1265
|
+
readonly requiredRoles?: readonly string[];
|
|
1266
|
+
readonly requiredCount?: number;
|
|
1267
|
+
readonly operator?: string;
|
|
1268
|
+
readonly attNonce?: string;
|
|
1269
|
+
readonly expiresAt?: string;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* VOCABULARY PINNED BY AU-9 (v1.3.27). `event_class` is one of seven values
|
|
1273
|
+
* declared in `$defs/event_class`; every record carries `event_class`,
|
|
1274
|
+
* `tenant_id`, `seq` and `occurred_at`; and each class carries EXACTLY the
|
|
1275
|
+
* further fields §11.1's table names for it -- a `decision` record the
|
|
1276
|
+
* twenty members this schema had always required, an `ingress_refused`
|
|
1277
|
+
* record the four a door can know, an `execution` record the EX-2 `outcome`
|
|
1278
|
+
* this schema had no place for. Every field below `seq` is therefore
|
|
1279
|
+
* optional AT THE SCHEMA LAYER and required BY CLASS, and the consumer
|
|
1280
|
+
* refuses a record whose set is not its class's before hashing it (AU-9),
|
|
1281
|
+
* because JSON Schema cannot say `required when event_class is X` in a form
|
|
1282
|
+
* this repository's generator has a rule for, and a consumer that hashed
|
|
1283
|
+
* first and checked second would have chained a record it then refused.
|
|
1284
|
+
* Until v1.3.27 this file was §11.1's decision record only: a door could not
|
|
1285
|
+
* build one, nothing recorded what RAN, and the timestamp's key name and the
|
|
1286
|
+
* class's value domain were both unpinned inside the AU-1 preimage -- so two
|
|
1287
|
+
* conformant implementations wrote different chain hashes for identical
|
|
1288
|
+
* histories (ACP-153, items 16-18; found by the first audit service).
|
|
1289
|
+
*
|
|
1290
|
+
* The record of one pipeline event. Carried on leg F6.1 of the leg register
|
|
1291
|
+
* (ZIFFER-DEPLOY-001 Annex A), Executor to Audit subsystem, cited there
|
|
1292
|
+
* against AU-2. It is `record_n` -- the object AU-1 places inside the chain
|
|
1293
|
+
* preimage -- and nothing else.
|
|
1294
|
+
*
|
|
1295
|
+
* WHY THIS IS A HASHED ARTIFACT AND NOT A LOG LINE. AU-1 (REVISED in
|
|
1296
|
+
* v1.3.15): `chain_hash_n = H({"prev": chain_hash_{n-1}, "record":
|
|
1297
|
+
* record_n})`, where §11.2 defines `H(x)` as the ASCII string `"sha256:"`
|
|
1298
|
+
* followed by the lowercase-hex SHA-256 of `canonical(x)`, and `canonical()`
|
|
1299
|
+
* is the single canonicaliser of AT-8a -- canonical CBOR, RFC 8949 §4.2. The
|
|
1300
|
+
* record therefore sits INSIDE a hash preimage, so its field set and its key
|
|
1301
|
+
* names are load-bearing in exactly the way AT-8b's are one artifact over.
|
|
1302
|
+
* AU-1 says as much about its own two keys: *the key names are part of the
|
|
1303
|
+
* preimage*. Two encodings of one event are two chain hashes, and from that
|
|
1304
|
+
* link onward two honest parties hold two different chains.
|
|
1305
|
+
*
|
|
1306
|
+
* WHAT THAT COSTS WHEN IT GOES WRONG, WHICH IS NOT A LOST COMPARISON. AU-3a
|
|
1307
|
+
* requires every anchor to be independently verifiable by any party holding
|
|
1308
|
+
* the anchor public keys, and AU-4 makes a head that fails to extend a
|
|
1309
|
+
* previously anchored head a CRITICAL INTEGRITY ALERT. So an encoding
|
|
1310
|
+
* ambiguity in this object routes an honest third-party verifier into this
|
|
1311
|
+
* system's own critical-alert channel with no attacker present. That is
|
|
1312
|
+
* ACP-57 -- the correction recorded in §1 and at AU-1, where the clause
|
|
1313
|
+
* specified a concatenation and `reference/src/acp_audit.py` had always
|
|
1314
|
+
* hashed a two-key map, so AU-3a did not hold as written. ACP-57 was a
|
|
1315
|
+
* defect in the clause immediately ABOVE this object. This schema is the
|
|
1316
|
+
* field set the same defect would live in, one layer down.
|
|
1317
|
+
*
|
|
1318
|
+
* WHY THE FIELD SET IS CLOSED, AND WHERE THAT CLOSURE EXCEEDS WHAT §11.1
|
|
1319
|
+
* STATES. Every property below is REQUIRED and `additionalProperties` is
|
|
1320
|
+
* false, because an optional field admits two canonical encodings of one
|
|
1321
|
+
* event -- present-as-null and omitted -- which is Z4 read at the chain
|
|
1322
|
+
* layer: not two ledger slots this time, but two chains, and the divergence
|
|
1323
|
+
* surfaces as AU-4's critical alert against an honest party. AT-8b closes
|
|
1324
|
+
* exactly this for the Attestation Object and L-26 records it as a limit; no
|
|
1325
|
+
* clause closes it here.
|
|
1326
|
+
*
|
|
1327
|
+
* AND THE SPECIFICATION DOES NOT SUPPORT THE CLOSED READING. §11.1 requires
|
|
1328
|
+
* *all relevant hashes*, and the word RELEVANT is the whole problem: it
|
|
1329
|
+
* makes the field set vary by event class, the document enumerates neither
|
|
1330
|
+
* the event classes nor which hashes are relevant to each, and it defines no
|
|
1331
|
+
* representation for an absent value. A conditional field set is Z4; a
|
|
1332
|
+
* closed one demands values a producer cannot always have (`raw_input_hash`
|
|
1333
|
+
* at the Executor is the clean case -- see that property). This schema takes
|
|
1334
|
+
* the closed reading because it is the only encoding-safe one, and records
|
|
1335
|
+
* the gap rather than resolving it by invention. Until §11.1 pins either a
|
|
1336
|
+
* per-event-class field set or an absent encoding, no schema for this object
|
|
1337
|
+
* can be both closed and conformant.
|
|
1338
|
+
*
|
|
1339
|
+
* §11.1 IS ALSO NOT THE WHOLE RECORD ON ITS OWN TERMS. SR-5 adds the
|
|
1340
|
+
* `(schema_id, schema_version, schema_hash)` triple and the fidelity class
|
|
1341
|
+
* to every audit record; B-2 requires every model-call record to assert that
|
|
1342
|
+
* provider-side tool features were disabled; EX-3 requires a
|
|
1343
|
+
* verification-failure record to carry the offending receipt; DR-11/L-29
|
|
1344
|
+
* require the confirmation-sampling draw to be recorded in audit; DR-12/L-30
|
|
1345
|
+
* require the unacknowledged-release counter to be. Five clauses add record
|
|
1346
|
+
* content that §11.1 does not list. The SR-5 triple is below because it is
|
|
1347
|
+
* stated of every audit record; the other four are event-class-specific and
|
|
1348
|
+
* are named here rather than guessed at.
|
|
1349
|
+
*
|
|
1350
|
+
* WHAT IS NOT A FIELD. `chain_hash` and `previous_hash` are AU-2's STORAGE
|
|
1351
|
+
* constraints on the row the receiver writes, not content of the record the
|
|
1352
|
+
* sender sends: the receiver computes them per AU-1 after this object
|
|
1353
|
+
* arrives (F6.1 carries the record, F6.2 carries the `chain_hash`). A record
|
|
1354
|
+
* carrying its own chain hash would be self-referential, and a record
|
|
1355
|
+
* carrying its own `previous_hash` would let the audited party name its own
|
|
1356
|
+
* link -- the audited party supplying the evidence of its own position,
|
|
1357
|
+
* RES-8 with the writer as the source. AU-8's tenant-creation record is
|
|
1358
|
+
* likewise not this object: it is a four-key map `{tenant_id, created_at,
|
|
1359
|
+
* bundle_epoch, schema_version}`, anchored immediately on tenant creation,
|
|
1360
|
+
* and modelling it here would put two objects in one schema. The anchor
|
|
1361
|
+
* (AU-3) is a third artifact on legs F6.3/F6.4. `audit_id` is absent because
|
|
1362
|
+
* §11.1 does not list it, even though §9.2's receipt carries one and R-1
|
|
1363
|
+
* returns one to a rejected caller.
|
|
1364
|
+
*
|
|
1365
|
+
* WHAT THIS SCHEMA DOES NOT CONSTRAIN, STATED SO THE CLAIM IS NOT READ WIDER
|
|
1366
|
+
* THAN IT IS. Every ordering property is invisible here. AU-7
|
|
1367
|
+
* anchor-before-release, AU-2's UNIQUE and GAPLESS `(tenant_id, seq)` and
|
|
1368
|
+
* its SERIALIZABLE or single-writer-per-tenant discipline, AU-6's outage
|
|
1369
|
+
* behaviour, AC-5's count-at-release, and all eight §11.3 reconciliation
|
|
1370
|
+
* checks are properties of a SEQUENCE of records and of the writer's
|
|
1371
|
+
* discipline. No instance check can see one of them: a schema-valid record
|
|
1372
|
+
* says nothing about whether it was anchored before its action was released.
|
|
1373
|
+
* They are per-implementation obligations (`spec/vectors/OBLIGATIONS.md`)
|
|
1374
|
+
* exercised in `reference/suites/audit_suite.py`, and a conformance claim
|
|
1375
|
+
* resting on this schema is a claim about shape only.
|
|
1376
|
+
*
|
|
1377
|
+
* SUITE 12 CLASSIFICATION. Every field below is class T at the schema layer,
|
|
1378
|
+
* and no consumer may promote any of them. An audit record is testimony
|
|
1379
|
+
* produced INSIDE the production trust domain -- the domain §11.3 places
|
|
1380
|
+
* under suspicion when it puts reconciliation in the anchor-verifier's
|
|
1381
|
+
* domain (AU-4) -- so a control input read from here would be the RES-8
|
|
1382
|
+
* defect with the audited party as the source. §9.2's authority note already
|
|
1383
|
+
* says `risk_level_effective`, `risk_level_floor_only` and `fidelity` are
|
|
1384
|
+
* diagnostic and audit fields only, and TR-8 requires the Executor to
|
|
1385
|
+
* recompute floor-only risk from the signed bundle and the canonical
|
|
1386
|
+
* Proposal. Reconciliation checks this record against ANCHORS and the
|
|
1387
|
+
* CONSUMPTION LEDGER, never against itself.
|
|
1388
|
+
*
|
|
1389
|
+
* ENCODING. Canonical CBOR, RFC 8949 §4.2, via §11.2's `H` and AT-8a's
|
|
1390
|
+
* single canonicaliser. The JSON here is the diagnostic form the schema is
|
|
1391
|
+
* written against, never an accepted input.
|
|
1392
|
+
*
|
|
1393
|
+
* NO `x-acp-absent` APPEARS BELOW, DELIBERATELY. No clause declares a
|
|
1394
|
+
* fail-safe default for a missing audit-record field. Inventing one would
|
|
1395
|
+
* let an unparseable record be silently completed and chained, which is the
|
|
1396
|
+
* opposite of what a tamper-evidence chain is for: P-4's default-DENY is
|
|
1397
|
+
* about policy evaluation and does not reach record parsing.
|
|
1398
|
+
* Generated from audit_record.schema.json.
|
|
1399
|
+
*/
|
|
1400
|
+
export interface AuditRecord {
|
|
1401
|
+
/**
|
|
1402
|
+
* The class of pipeline event this record is of (§11.1, first item). It is
|
|
1403
|
+
* what makes §11.1's *all relevant hashes* even readable -- relevance is
|
|
1404
|
+
* relevance TO AN EVENT CLASS -- so it is the field the rest of the record's
|
|
1405
|
+
* conditionality would hang from.
|
|
1406
|
+
*
|
|
1407
|
+
* THE VALUE DOMAIN IS AU-9's (v1.3.27): `ingress_refused`, `decision`,
|
|
1408
|
+
* `verification_failed`, `hold`, `notice`, `release`, `execution` -- one per
|
|
1409
|
+
* pipeline event the flow register puts a record on (F1.1, F6.1, DR-1,
|
|
1410
|
+
* DR-13, DR-7/9/10/12, EX-2, EX-3). Through v1.3.26 no clause enumerated it,
|
|
1411
|
+
* and this repository held three vocabularies for one field: the reference's
|
|
1412
|
+
* `type` with `hold`/`release`, an AU-1 suite case's `event`, and §11.1's
|
|
1413
|
+
* words. The consequence was not cosmetic: AU-1 hashes this record, so
|
|
1414
|
+
* different strings for one event were different chain hashes for identical
|
|
1415
|
+
* histories, and AU-3a's independent verifier could recompute neither. The
|
|
1416
|
+
* reference now writes THIS key with THESE values, and AU-9 makes a value
|
|
1417
|
+
* outside the set a refusal rather than a fourth vocabulary.
|
|
1418
|
+
*/
|
|
1419
|
+
readonly eventClass: AuditEventClass;
|
|
1420
|
+
/**
|
|
1421
|
+
* The tenant whose chain this record extends. §11.1 lists *tenant*; AU-1
|
|
1422
|
+
* makes chains PER-TENANT, AU-2 keys uniqueness and gaplessness on
|
|
1423
|
+
* `(tenant_id, seq)`, and AU-8 puts `tenant_id` in the genesis preimage, so
|
|
1424
|
+
* the identifier is the specification's own and not a name coined here.
|
|
1425
|
+
*
|
|
1426
|
+
* IT NAMES A CHAIN AND DOES NOT SELECT ONE (RES-8). A receiver that resolves
|
|
1427
|
+
* WHICH chain to append to from this field has let the writer choose its own
|
|
1428
|
+
* history; D-1 gives every tenant dedicated per-tenant executor credentials
|
|
1429
|
+
* and schemas precisely so the authenticated connection is what answers.
|
|
1430
|
+
* Carried anyway, because it is inside the AU-1 preimage: a record whose
|
|
1431
|
+
* tenant is implicit at write time and explicit at verification time is one
|
|
1432
|
+
* event with two encodings, and the chain would not survive that.
|
|
1433
|
+
*/
|
|
1434
|
+
readonly tenantId: TenantId;
|
|
1435
|
+
/**
|
|
1436
|
+
* The session the event occurred in (§11.1, third item). The specification
|
|
1437
|
+
* uses the bare word `session` in exactly one other place that types it at
|
|
1438
|
+
* all -- §8.5's accumulator `group_by` lists `["operator","session"]` -- so
|
|
1439
|
+
* the key name here is that word and no other. It is load-bearing beyond
|
|
1440
|
+
* forensics: §4.2 scopes cross-session persistence OUT of the accumulator
|
|
1441
|
+
* model (*fresh session credentials reset session-scoped accumulators*),
|
|
1442
|
+
* which makes the session boundary an attacker-visible reset, and the audit
|
|
1443
|
+
* record is the only place a reconciler can see how an operator's activity
|
|
1444
|
+
* was partitioned across sessions.
|
|
1445
|
+
*
|
|
1446
|
+
* `maxLength` is L-04. No format is pinned because no clause pins one.
|
|
1447
|
+
*
|
|
1448
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1449
|
+
* type: minLength 1, maxLength 2048.
|
|
1450
|
+
*/
|
|
1451
|
+
readonly session?: string;
|
|
1452
|
+
/**
|
|
1453
|
+
* The operator the event is attributed to (§11.1, fourth item; §8.5 groups
|
|
1454
|
+
* accumulators by the same key). It is the principal AT-2
|
|
1455
|
+
* approver-distinctness and DR-9 acknowledgement are stated about, and the
|
|
1456
|
+
* principal AC-5's deny-effect accumulators can lock out -- which is why W1
|
|
1457
|
+
* mattered: counters inflated by Decisions that never executed are a
|
|
1458
|
+
* targeted lockout of a legitimate operator.
|
|
1459
|
+
*
|
|
1460
|
+
* THIS IS A RECORD OF AN ATTRIBUTION, NOT THE ATTRIBUTION ITSELF. The
|
|
1461
|
+
* authoritative operator identity for approver-distinctness and the §9.3
|
|
1462
|
+
* step-9 capability recheck is `attestations[].obj.operator`, which is
|
|
1463
|
+
* signature-covered (Y4); §9.2 says in as many words that the receipt's
|
|
1464
|
+
* top-level `operator` is diagnostic only. An audit record is one level
|
|
1465
|
+
* further from the signature than that receipt field, so nothing may key a
|
|
1466
|
+
* control decision on this value.
|
|
1467
|
+
*/
|
|
1468
|
+
readonly operator?: OperatorId;
|
|
1469
|
+
/**
|
|
1470
|
+
* The fidelity class of the adapter the event descends from. Required twice
|
|
1471
|
+
* over: §11.1 lists it in the record, and SR-5 states that every Atom,
|
|
1472
|
+
* Proposal, Decision and audit record carries the `(schema_id,
|
|
1473
|
+
* schema_version, schema_hash)` triple AND the fidelity class.
|
|
1474
|
+
*
|
|
1475
|
+
* WHY IT IS AUDITED AT ALL. FC-1 makes the class the difference between
|
|
1476
|
+
* injection being grammatically impossible (F-HIGH) and containment resting
|
|
1477
|
+
* entirely on capability limits, confirmation and receipts (F-LOW), and FC-2
|
|
1478
|
+
* lets policy condition on it. A receipt asserting `fidelity: "F-HIGH"` for
|
|
1479
|
+
* a Proposal from an F-LOW-bound adapter MUST fail closed with a critical
|
|
1480
|
+
* alert (§14 suite 3), which is possible only because the true class is
|
|
1481
|
+
* recorded somewhere the adapter binding can be reconciled against. The key
|
|
1482
|
+
* name is §9.2's receipt field name `fidelity`; §11.1's phrase is *fidelity
|
|
1483
|
+
* class*.
|
|
1484
|
+
*/
|
|
1485
|
+
readonly fidelity?: FidelityClass;
|
|
1486
|
+
/**
|
|
1487
|
+
* First member of SR-5's triple, which every audit record carries. The
|
|
1488
|
+
* triple is what lets a replayed Decision be re-derived against the schema
|
|
1489
|
+
* that actually admitted the input: §11.3(e) re-derives sampled Decisions by
|
|
1490
|
+
* replay and calls a mismatch critical, and SR-6 retires schemas after 180
|
|
1491
|
+
* days, so a record naming only a version would become unresolvable. Same
|
|
1492
|
+
* key name as §9.2's receipt field.
|
|
1493
|
+
*
|
|
1494
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1495
|
+
* type: minLength 1, maxLength 2048.
|
|
1496
|
+
*/
|
|
1497
|
+
readonly schemaId?: string;
|
|
1498
|
+
/**
|
|
1499
|
+
* Second member of SR-5's triple. Carried beside `schema_hash` rather than
|
|
1500
|
+
* instead of it because a version is a NAME for a schema and the hash is the
|
|
1501
|
+
* evidence of which bytes were used -- SR-3's narrowing rules move the
|
|
1502
|
+
* admissible set between versions, so a replay that trusted the version
|
|
1503
|
+
* alone would re-derive against a different grammar than the one that ran.
|
|
1504
|
+
* No format is pinned; no clause pins one.
|
|
1505
|
+
*
|
|
1506
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1507
|
+
* type: minLength 1, maxLength 2048.
|
|
1508
|
+
*/
|
|
1509
|
+
readonly schemaVersion?: string;
|
|
1510
|
+
/**
|
|
1511
|
+
* Third member of SR-5's triple, and the only member of it that is evidence
|
|
1512
|
+
* rather than a label. §8.4 step 1 admits a Proposal only against a
|
|
1513
|
+
* `(schema_id, version, hash)` valid in the bundle, so this is the value
|
|
1514
|
+
* that ties the recorded event to bundle-attested bytes.
|
|
1515
|
+
*/
|
|
1516
|
+
readonly schemaHash?: Sha256Hash;
|
|
1517
|
+
/**
|
|
1518
|
+
* The hash of the external input the event descends from (§11.1, first of
|
|
1519
|
+
* the enumerated hashes).
|
|
1520
|
+
*
|
|
1521
|
+
* THIS FIELD IS WHERE §11.1 AND LEG F6.1 CONTRADICT EACH OTHER, AND THE
|
|
1522
|
+
* CONTRADICTION IS RECORDED RATHER THAN SMOOTHED. `raw_input_hash` occurs
|
|
1523
|
+
* exactly ONCE in ZIFFER-SPEC-001 -- in §11.1's own sentence -- and no
|
|
1524
|
+
* clause says what it covers, at which point of §5.1's flow it is taken, or
|
|
1525
|
+
* under which canonicalisation. For an F-LOW adapter the raw input is free
|
|
1526
|
+
* text, so *canonical* has no meaning for it at all. Worse for this leg: the
|
|
1527
|
+
* Executor never sees the raw input (§5.1 puts the Ingress Adapter and the
|
|
1528
|
+
* validators upstream of the Policy Engine, and D-2 gives the model path no
|
|
1529
|
+
* route to the Executor segment), and neither §9.2's receipt nor the
|
|
1530
|
+
* canonical Proposal carries the value -- so an Executor writing an F6.1
|
|
1531
|
+
* record can only forward a number it was handed, class T with no verifier
|
|
1532
|
+
* anywhere on the path, or omit it and split the encoding (Z4 at the chain
|
|
1533
|
+
* layer). The specification supports neither. Kept in the schema because
|
|
1534
|
+
* §11.1 states it unconditionally, and flagged because writing the field
|
|
1535
|
+
* down is what made the contradiction visible.
|
|
1536
|
+
*/
|
|
1537
|
+
readonly rawInputHash?: Sha256Hash;
|
|
1538
|
+
/**
|
|
1539
|
+
* The canonical Proposal (B-1a) the event is about (§11.1). It is the join
|
|
1540
|
+
* key for most of §11.3: (a) matches execution records to receipts, and
|
|
1541
|
+
* DS-6f pins one immutable `proposal_hash -> origin_nonce` binding per
|
|
1542
|
+
* Proposal in the Consumption Ledger with INDEFINITE retention, which the
|
|
1543
|
+
* reconciler reads from the ledger and never from a record like this one.
|
|
1544
|
+
* Canonicalisation for the Proposal is JCS (RFC 8785) per WE-1/WE-2, not the
|
|
1545
|
+
* CBOR that encodes this record -- the two canonicalisers coexist by
|
|
1546
|
+
* artifact, and mixing them is the encoding split AT-8a exists to prevent.
|
|
1547
|
+
*/
|
|
1548
|
+
readonly proposalHash?: Sha256Hash;
|
|
1549
|
+
/**
|
|
1550
|
+
* The signed bundle in force for the event (§11.1). Recorded so a Decision
|
|
1551
|
+
* can be re-derived (§11.3(e)) against the exact policy that produced it:
|
|
1552
|
+
* PB-1 expires bundles and §8.2 rules move, so a record naming only an epoch
|
|
1553
|
+
* would be replayable only while that bundle survived.
|
|
1554
|
+
*/
|
|
1555
|
+
readonly policyBundleHash?: Sha256Hash;
|
|
1556
|
+
/**
|
|
1557
|
+
* The epoch of that bundle (§11.1). §11.3(d) reconciles every signing event
|
|
1558
|
+
* against a `bundle_epoch` greater than or equal to all prior epochs, which
|
|
1559
|
+
* is the rollback detection, and L-24 makes the epoch a strictly increasing
|
|
1560
|
+
* integer that is never reused.
|
|
1561
|
+
*
|
|
1562
|
+
* DETECTION HERE, ENFORCEMENT ELSEWHERE (RES-8). Monotonicity is enforced at
|
|
1563
|
+
* the KMS against a durable high-water mark (RAD-3) with indefinite
|
|
1564
|
+
* retention in the ledger (CL-4), never from an audited value: an epoch read
|
|
1565
|
+
* back from the chain the production domain writes would let a compromised
|
|
1566
|
+
* writer lower its own high-water mark. `minimum: 0` is the type's floor,
|
|
1567
|
+
* matching AU-8's genesis record, which carries an epoch before any signing
|
|
1568
|
+
* event exists.
|
|
1569
|
+
*
|
|
1570
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1571
|
+
* type: minimum 0.
|
|
1572
|
+
*/
|
|
1573
|
+
readonly bundleEpoch?: number;
|
|
1574
|
+
/**
|
|
1575
|
+
* The Context read the Decision was computed over (§11.1; §8.8
|
|
1576
|
+
* `snapshot_hash()`). Recorded because the Context Store is adversarial in
|
|
1577
|
+
* this threat model -- §8.3.2 leaves it able to raise tier or withhold
|
|
1578
|
+
* capability facts -- so re-deriving a sampled Decision (§11.3(e)) requires
|
|
1579
|
+
* knowing which snapshot was read, and INV-1-MEDIUM's disclosed residual is
|
|
1580
|
+
* only investigable against a pinned snapshot.
|
|
1581
|
+
*/
|
|
1582
|
+
readonly contextSnapshotHash?: Sha256Hash;
|
|
1583
|
+
/**
|
|
1584
|
+
* The nonce of the Decision Receipt the event is about (§11.1, *receipt
|
|
1585
|
+
* nonce*; the receipt's own field name in §9.2 is `nonce`, and §11.1 does
|
|
1586
|
+
* not say which spelling belongs in the record).
|
|
1587
|
+
*
|
|
1588
|
+
* WHY IT IS IN THE RECORD. It is the identity a receipt is consumed under:
|
|
1589
|
+
* CL-2/CL-3 consume receipt nonces once against the linearizable ledger,
|
|
1590
|
+
* §9.3 step 6 claims it atomically, and DS-6a makes the FIRST receipt's
|
|
1591
|
+
* nonce the `origin_nonce` half of `action_id = (proposal_hash,
|
|
1592
|
+
* origin_nonce)`. §11.3(a) reconciles every execution record against exactly
|
|
1593
|
+
* one valid receipt, and orphan executions are critical -- that check needs
|
|
1594
|
+
* this value in the record.
|
|
1595
|
+
*
|
|
1596
|
+
* AND IT IS THE FIELD THAT MAKES THE CLOSED READING UNSATISFIABLE. A
|
|
1597
|
+
* pattern-constrained string has no empty encoding, so an event class with
|
|
1598
|
+
* no receipt -- an ingress rejection under R-3, a model call under B-2 --
|
|
1599
|
+
* cannot produce a schema-valid record without one, and no clause supplies
|
|
1600
|
+
* an absent form. `attestation_ids` escapes the same problem only because an
|
|
1601
|
+
* empty ARRAY is a single well-defined canonical encoding while an absent
|
|
1602
|
+
* field is two readings. Size is L-17's 128 bits; type is WE-4.
|
|
1603
|
+
*/
|
|
1604
|
+
readonly receiptNonce?: Nonce128;
|
|
1605
|
+
/**
|
|
1606
|
+
* The `attestation_id`s of the Attestation Objects the event consumed
|
|
1607
|
+
* (§11.1, *attestation IDs*). Each is SHA-256 over the canonical CBOR
|
|
1608
|
+
* encoding of the whole object (AT-1, AT-8a), RECOMPUTED by the Executor at
|
|
1609
|
+
* §9.3 step 7b(v) and never taken as transmitted -- transmitting the id for
|
|
1610
|
+
* trust is what v1.3.2 did and what Y1 broke. So the values here are a
|
|
1611
|
+
* record of a recomputation, which is the only kind of derived value an
|
|
1612
|
+
* audit record may honestly carry.
|
|
1613
|
+
*
|
|
1614
|
+
* WHY IT MATTERS THAT THEY ARE RECORDED. §11.3(c) reconciles every
|
|
1615
|
+
* `attestation_id` against exactly one receipt and at most one execution --
|
|
1616
|
+
* the check whose absence permitted T-14, attestation amplification. A
|
|
1617
|
+
* reconciler running outside the production domain (AU-4) can only perform
|
|
1618
|
+
* it against records that name the ids.
|
|
1619
|
+
*
|
|
1620
|
+
* THESE ARE NOT `attestation_digests`. Under receipt version 3 (§8.6b, AB-1)
|
|
1621
|
+
* the receipt commits to entry digests taken over the whole entry -- object,
|
|
1622
|
+
* `kind`, `attester` and `sig` together -- while an `attestation_id` is over
|
|
1623
|
+
* the object alone. Two different values over overlapping bytes; recording
|
|
1624
|
+
* the wrong one makes §11.3(c) silently compare nothing.
|
|
1625
|
+
*
|
|
1626
|
+
* AN EMPTY ARRAY IS THE CORRECT ENCODING FOR AN EVENT THAT CONSUMED NO
|
|
1627
|
+
* ATTESTATION, and it is deliberately not modelled as an absent field: `[]`
|
|
1628
|
+
* has one canonical CBOR encoding, an omitted key has two readings, and two
|
|
1629
|
+
* readings is the chain divergence this schema exists to prevent.
|
|
1630
|
+
* `uniqueItems` because one attestation appearing twice in one record is one
|
|
1631
|
+
* approval counted twice, which is T-14 in miniature.
|
|
1632
|
+
*
|
|
1633
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1634
|
+
* type: minItems 0, uniqueItems.
|
|
1635
|
+
*/
|
|
1636
|
+
readonly attestationIds?: readonly Sha256Hash[];
|
|
1637
|
+
/**
|
|
1638
|
+
* The floor-only risk the Policy Engine computed (§11.1, *floor-only and
|
|
1639
|
+
* effective risk*; the key name is §9.2's receipt field). Floor-only means
|
|
1640
|
+
* derived from the SIGNED BUNDLE's tier floors and rules, with no
|
|
1641
|
+
* Context-asserted raise folded in -- §8.3.2's separation, which is what
|
|
1642
|
+
* keeps a compromised Context Store from suppressing escalation (TR-4,
|
|
1643
|
+
* `FloorDominance`).
|
|
1644
|
+
*
|
|
1645
|
+
* DIAGNOSTIC AND AUDIT ONLY, IN THE SPECIFICATION'S OWN WORDS. §9.2's
|
|
1646
|
+
* authority note classifies this field non-authoritative in the receipt, and
|
|
1647
|
+
* TR-8 requires the Executor to RECOMPUTE it from the signed bundle and the
|
|
1648
|
+
* canonical Proposal; a transmitted value disagreeing with the recomputation
|
|
1649
|
+
* is a critical alert at §9.3 step 7a. That history is why the note exists:
|
|
1650
|
+
* v1.3.0 keyed the Executor's quorum requirement on the receipt's value, so
|
|
1651
|
+
* one compromised KMS could sign a floor-T3 action as `LOW` with no
|
|
1652
|
+
* attestations and defeat INV-1-HIGH. A value one hop further from the
|
|
1653
|
+
* signature must not be treated any better.
|
|
1654
|
+
*/
|
|
1655
|
+
readonly riskLevelFloorOnly?: Risk;
|
|
1656
|
+
/**
|
|
1657
|
+
* The effective risk -- floor composed with Context-asserted raises and
|
|
1658
|
+
* accumulator effects (§8.4, §8.5) -- recorded beside the floor-only value
|
|
1659
|
+
* because §11.1 requires BOTH. Recording only one loses the ability to
|
|
1660
|
+
* reconstruct why an action attested: the pair is the evidence of which side
|
|
1661
|
+
* raised it, and a raise that appears in the effective value and not the
|
|
1662
|
+
* floor is a Context Store claim rather than a bundle rule. Diagnostic and
|
|
1663
|
+
* audit only, per §9.2's authority note.
|
|
1664
|
+
*/
|
|
1665
|
+
readonly riskLevelEffective?: Risk;
|
|
1666
|
+
/**
|
|
1667
|
+
* The disposition the Policy Engine reached (§8.4 step 11; §5.1's flow
|
|
1668
|
+
* renders the engine's output as `ALLOW/ATTEST/DENY`; §9.2's receipt field
|
|
1669
|
+
* carries the same name).
|
|
1670
|
+
*
|
|
1671
|
+
* THIS IS THE ONLY MEMBER OF §11.1'S *Decision fields* THIS SCHEMA PINS, AND
|
|
1672
|
+
* THE REST IS AN OPEN QUESTION RATHER THAN AN OMISSION. §11.1 says *Decision
|
|
1673
|
+
* fields* and enumerates none. §9.2's receipt body carries a dozen
|
|
1674
|
+
* candidates -- `rule_id`, `receipt_version`, `issued_at`, `expires_at`,
|
|
1675
|
+
* `idempotency_key` among them -- and guessing which of them §11.1 meant
|
|
1676
|
+
* would be this schema legislating where the specification is silent, which
|
|
1677
|
+
* is how a second definition of one object gets created. Only the
|
|
1678
|
+
* disposition itself is taken, because it is the one field both §8.4 and
|
|
1679
|
+
* §9.2 name as THE Decision.
|
|
1680
|
+
*
|
|
1681
|
+
* `DENY` is included on P-4's authority as much as step 11's: no matching
|
|
1682
|
+
* rule, unknown capability, unclassified resource or unavailable Context
|
|
1683
|
+
* Store each resolve to DENY with a distinct reason code, and R-3 requires
|
|
1684
|
+
* every rejection to produce an audit record -- so a record that could not
|
|
1685
|
+
* express a refusal would leave the entire fail-closed path unauditable.
|
|
1686
|
+
*/
|
|
1687
|
+
readonly decision?: DecisionOutcome;
|
|
1688
|
+
/**
|
|
1689
|
+
* The monotonic sequence number of this record within its tenant's chain
|
|
1690
|
+
* (§11.1, last item). AU-2 makes `(tenant_id, seq)` UNIQUE and GAPLESS, with
|
|
1691
|
+
* inserts appending under SERIALIZABLE or single-writer-per-tenant
|
|
1692
|
+
* discipline.
|
|
1693
|
+
*
|
|
1694
|
+
* WHY THE MINIMUM IS 1 AND NOT 0. AU-8 defines `chain_hash_0` over the
|
|
1695
|
+
* tenant-creation record, and AU-1 pairs `chain_hash_n` with `record_n`, so
|
|
1696
|
+
* index 0 is genesis and the first pipeline event is n = 1. The
|
|
1697
|
+
* tenant-creation record is a different object (see this schema's
|
|
1698
|
+
* description) and never appears here.
|
|
1699
|
+
*
|
|
1700
|
+
* IT NAMES A POSITION AND IS NOT EVIDENCE OF ONE (RES-8). Gaplessness and
|
|
1701
|
+
* uniqueness are properties the WRITER enforces under AU-2's own discipline;
|
|
1702
|
+
* a receiver that appends at the sequence number its sender chose has let
|
|
1703
|
+
* the sender place its own link, and a chain whose positions are supplied by
|
|
1704
|
+
* the party under audit is not tamper-evident. The value is carried because
|
|
1705
|
+
* §11.1 puts it in the record and AU-1 hashes the record -- but the receiver
|
|
1706
|
+
* assigns and verifies it.
|
|
1707
|
+
*
|
|
1708
|
+
* RESOLVED IN v1.3.28 (ACP-164): WRITER PROPOSES, RECEIVER VERIFIES. The
|
|
1709
|
+
* finding this paragraph used to record was that the reference refused a
|
|
1710
|
+
* writer-supplied `seq` and stamped its own, while this schema makes the
|
|
1711
|
+
* field REQUIRED -- so the generated `AuditRecord`, which types `seq` as a
|
|
1712
|
+
* required integer, could not express a message the reference would accept,
|
|
1713
|
+
* and no F6.1 vector could satisfy both. The resolution is the
|
|
1714
|
+
* compare-and-retry shape ziffer's audit service already shipped for three
|
|
1715
|
+
* concurrent writers per chain: the writer names the position it believes is
|
|
1716
|
+
* next, and `AuditChain.append` refuses a mismatch under AU-2 with the
|
|
1717
|
+
* EXPECTED position in the refusal, which is the whole retry protocol. The
|
|
1718
|
+
* receiver still decides -- it never appends AT the writer's number, it
|
|
1719
|
+
* refuses unless that number is the one it was about to assign -- so the
|
|
1720
|
+
* RES-8 paragraph above survives unchanged: naming the right position gains
|
|
1721
|
+
* a writer nothing, and naming any other gains it a refusal. The preimage is
|
|
1722
|
+
* the record as written, `seq` included.
|
|
1723
|
+
*
|
|
1724
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1725
|
+
* type: minimum 1.
|
|
1726
|
+
*/
|
|
1727
|
+
readonly seq: number;
|
|
1728
|
+
/**
|
|
1729
|
+
* RFC 3339 instant at which the event occurred, in the rendering §9.2 uses
|
|
1730
|
+
* for `issued_at` and `expires_at`.
|
|
1731
|
+
*
|
|
1732
|
+
* THE CLAUSE THAT REQUIRES IT IS NOT §11.1, AND THAT IS THE POINT. §11.1's
|
|
1733
|
+
* record content lists NO timestamp. §11.3(g) nonetheless reconciles every
|
|
1734
|
+
* floor-HIGH execution record against *an anchor covering it, dated at or
|
|
1735
|
+
* before release* -- a comparison the reconciler makes from outside the
|
|
1736
|
+
* production trust domain (AU-4), where the record and the anchor are the
|
|
1737
|
+
* only two things it can read. Without a time in the record the AU-7
|
|
1738
|
+
* enforcement check has nothing to compare the anchor's date against, and
|
|
1739
|
+
* AU-7 is what closed W3, the pre-anchor rewrite gap.
|
|
1740
|
+
* `reference/src/acp_audit.py` carries the value under the key `at` and
|
|
1741
|
+
* `reconcile()` compares `a["at"] > r["at"]` exactly as described.
|
|
1742
|
+
*
|
|
1743
|
+
* THE KEY NAME IS `occurred_at`, PINNED BY AU-9 (v1.3.27). The reference
|
|
1744
|
+
* carried this value under `at`, and the anchor's date under `at` beside it;
|
|
1745
|
+
* both are renamed to the names the wire schemas already used (`occurred_at`
|
|
1746
|
+
* here, `anchored_at` in anchor.schema.json), because an unpinned key name
|
|
1747
|
+
* for a required value inside the AU-1 preimage was a chain divergence
|
|
1748
|
+
* waiting to happen.
|
|
1749
|
+
*
|
|
1750
|
+
* EVALUATED AGAINST NTS-DISCIPLINED WALL TIME within the L-20 skew allowance
|
|
1751
|
+
* (§12.5, leg F9.1), never a monotonic clock: an anchor and a record are
|
|
1752
|
+
* dated by different processes, and only wall time is comparable across
|
|
1753
|
+
* them.
|
|
1754
|
+
*/
|
|
1755
|
+
readonly occurredAt: Rfc3339Instant;
|
|
1756
|
+
/**
|
|
1757
|
+
* What happened, on the records AU-9 puts it on: an `execution` record
|
|
1758
|
+
* carries EX-2's `executed`, `failed`, `not_attempted` or `indeterminate`
|
|
1759
|
+
* (§9.5 -- `indeterminate` is what DS-3's reconciliation is keyed on, and it
|
|
1760
|
+
* is a value here because a chain that cannot say an outcome was unknown
|
|
1761
|
+
* cannot answer §11.3 (f)); a `release` record carries `executed` or
|
|
1762
|
+
* `refused` (DR-7, DR-9, DR-10). Absent on every other class. Through
|
|
1763
|
+
* v1.3.26 nothing in the record said what ran, so §11.3 (a) orphan
|
|
1764
|
+
* executions, (f) indeterminate outcomes and (h) released executions could
|
|
1765
|
+
* not be answered from a chain written in this vocabulary (ACP-153, item
|
|
1766
|
+
* 17).
|
|
1767
|
+
*/
|
|
1768
|
+
readonly outcome?: AuditOutcome;
|
|
1769
|
+
/**
|
|
1770
|
+
* On a `notice` record: who the signed bundle's `notice_targets.json` names
|
|
1771
|
+
* for the action class (DR-13), as committed BEFORE execution. At least one,
|
|
1772
|
+
* because DR-13 refuses to execute where the bundle names none -- a notice
|
|
1773
|
+
* with no addressee is not a detection channel -- and a record that could
|
|
1774
|
+
* carry an empty set would be that refusal written down as success.
|
|
1775
|
+
*
|
|
1776
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1777
|
+
* type: minItems 1, maxItems 100.
|
|
1778
|
+
*/
|
|
1779
|
+
readonly recipients?: readonly OperatorId[];
|
|
1780
|
+
/**
|
|
1781
|
+
* On a `release` record: the notified parties whose acknowledgements the
|
|
1782
|
+
* Executor verified (ACK-1..6) before releasing. EMPTY for a silent release,
|
|
1783
|
+
* and DR-12's per-tenant counter is the count of `release` records whose set
|
|
1784
|
+
* is empty -- recorded in audit, as the clause requires, by being derivable
|
|
1785
|
+
* from it rather than by a counter field a writer could set.
|
|
1786
|
+
*
|
|
1787
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1788
|
+
* type: maxItems 100.
|
|
1789
|
+
*/
|
|
1790
|
+
readonly acknowledgedBy?: readonly OperatorId[];
|
|
1791
|
+
/**
|
|
1792
|
+
* On an `ingress_refused` or `verification_failed` record: the clause id the
|
|
1793
|
+
* refusal fired under -- `B-6`, `V-1`, `9.3-7b(ii)` -- in the spelling
|
|
1794
|
+
* `tools/clauses.py` indexes. It is the join key between a chain and the
|
|
1795
|
+
* specification: an auditor reading `verification_failed` records grouped by
|
|
1796
|
+
* clause is reading the threat table's countermeasures firing, which is what
|
|
1797
|
+
* EX-3 puts the record there for.
|
|
1798
|
+
*
|
|
1799
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1800
|
+
* type: pattern `^[A-Za-z0-9.]{1,12}-[0-9]{1,3}[a-z]?(\([a-z-]{1,8}\))?$`,
|
|
1801
|
+
* minLength 1, maxLength 32.
|
|
1802
|
+
*/
|
|
1803
|
+
readonly clause?: string;
|
|
1804
|
+
/**
|
|
1805
|
+
* On an `execution` record: the target system's correlation identifier for
|
|
1806
|
+
* the invocation (EX-2's `target correlation IDs`), so an `indeterminate`
|
|
1807
|
+
* outcome can be resolved against the target's own record of what it did
|
|
1808
|
+
* (DS-3). Opaque here; its meaning is the target's.
|
|
1809
|
+
*
|
|
1810
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1811
|
+
* type: minLength 1, maxLength 256.
|
|
1812
|
+
*/
|
|
1813
|
+
readonly correlationId?: string;
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* The lenient projection of AuditRecord: every field optional, every enum
|
|
1817
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
1818
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
1819
|
+
*/
|
|
1820
|
+
export interface RawAuditRecord {
|
|
1821
|
+
readonly eventClass?: string;
|
|
1822
|
+
readonly tenantId?: string;
|
|
1823
|
+
readonly session?: string;
|
|
1824
|
+
readonly operator?: string;
|
|
1825
|
+
readonly fidelity?: string;
|
|
1826
|
+
readonly schemaId?: string;
|
|
1827
|
+
readonly schemaVersion?: string;
|
|
1828
|
+
readonly schemaHash?: string;
|
|
1829
|
+
readonly rawInputHash?: string;
|
|
1830
|
+
readonly proposalHash?: string;
|
|
1831
|
+
readonly policyBundleHash?: string;
|
|
1832
|
+
readonly bundleEpoch?: number;
|
|
1833
|
+
readonly contextSnapshotHash?: string;
|
|
1834
|
+
readonly receiptNonce?: string;
|
|
1835
|
+
readonly attestationIds?: readonly string[];
|
|
1836
|
+
readonly riskLevelFloorOnly?: string;
|
|
1837
|
+
readonly riskLevelEffective?: string;
|
|
1838
|
+
readonly decision?: string;
|
|
1839
|
+
readonly seq?: number;
|
|
1840
|
+
readonly occurredAt?: string;
|
|
1841
|
+
readonly outcome?: string;
|
|
1842
|
+
readonly recipients?: readonly string[];
|
|
1843
|
+
readonly acknowledgedBy?: readonly string[];
|
|
1844
|
+
readonly clause?: string;
|
|
1845
|
+
readonly correlationId?: string;
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* The bundle-epoch high-water mark consulted by RAD-3 (CL-3, T-15).
|
|
1849
|
+
* Retention is INDEFINITE (CL-4): an expiring epoch mark reopens rollback,
|
|
1850
|
+
* because a genuine but superseded and more permissive bundle becomes
|
|
1851
|
+
* acceptable again the moment the mark that refused it is forgotten.
|
|
1852
|
+
*
|
|
1853
|
+
* THIS CLASS DOES NOT BEHAVE LIKE THE OTHER THREE, AND THE SPECIFICATION
|
|
1854
|
+
* CALLS IT A CONSUMPTION ANYWAY. CL-3 lists bundle epochs under "Consumed
|
|
1855
|
+
* identifier classes", but an epoch high-water mark is not consumed: many
|
|
1856
|
+
* receipts legitimately reference the same epoch, and a ledger that treated
|
|
1857
|
+
* this as at-most-once would refuse the second receipt issued under one
|
|
1858
|
+
* bundle. CL-4 says as much by giving the mark indefinite retention while
|
|
1859
|
+
* nonces and attestation ids expire. CL-3's own text does that disclosure
|
|
1860
|
+
* for the fourth class -- "Unlike the three classes above this is a
|
|
1861
|
+
* *binding*, not a consumption" -- and does not do it for this one. The
|
|
1862
|
+
* divergence is recorded with this schema rather than resolved here, because
|
|
1863
|
+
* deciding it is a normative act.
|
|
1864
|
+
* Generated from ledger_claim.schema.json#/$defs/bundle_epoch_claim.
|
|
1865
|
+
*/
|
|
1866
|
+
export interface BundleEpochClaim {
|
|
1867
|
+
readonly tenantId: TenantId;
|
|
1868
|
+
readonly artifactClass: 'bundle_epoch';
|
|
1869
|
+
/**
|
|
1870
|
+
* The `bundle_epoch` of the receipt under verification (§9.2). An integer,
|
|
1871
|
+
* not a digest, which is why the four classes cannot share one object:
|
|
1872
|
+
* `identifier` is a string in the other three branches and there is no
|
|
1873
|
+
* encoding of this value that is simultaneously both. Monotonicity is a
|
|
1874
|
+
* LEDGER property compared against the durable mark, never a property of
|
|
1875
|
+
* this value -- a claim asserting it is the highest epoch ever seen would be
|
|
1876
|
+
* the claimant setting its own bar, which is what RAD-3 replaced the v1.2.0
|
|
1877
|
+
* history window to prevent.
|
|
1878
|
+
*
|
|
1879
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1880
|
+
* type: minimum 0.
|
|
1881
|
+
*/
|
|
1882
|
+
readonly identifier: number;
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* The lenient projection of BundleEpochClaim: every field optional, every enum
|
|
1886
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
1887
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
1888
|
+
*/
|
|
1889
|
+
export interface RawBundleEpochClaim {
|
|
1890
|
+
readonly tenantId?: string;
|
|
1891
|
+
readonly artifactClass?: string;
|
|
1892
|
+
readonly identifier?: number;
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* One asserted answer of `has_capability(operator, capability, resource)`
|
|
1896
|
+
* (section 8.8). All three arguments are carried because all three identify
|
|
1897
|
+
* the fact: an entry naming fewer of them answers a question nobody asked,
|
|
1898
|
+
* and at section 9.3 step 9 the question is specific -- capability of THIS
|
|
1899
|
+
* operator on THIS target. The reference implementation is the
|
|
1900
|
+
* counterexample and it is worth keeping in view: `acp_executor.py` keys
|
|
1901
|
+
* capabilities by the joined string `f"{task_type}:{target}"`, so a
|
|
1902
|
+
* capability name containing a colon aliases a different (capability,
|
|
1903
|
+
* resource) pair, and the three-argument interface section 8.8 declares has
|
|
1904
|
+
* no three-argument representation anywhere in the reference.
|
|
1905
|
+
* Generated from context_read.schema.json#/$defs/capability_grant.
|
|
1906
|
+
*/
|
|
1907
|
+
export interface CapabilityGrant {
|
|
1908
|
+
/**
|
|
1909
|
+
* The principal the capability is asserted for. This is the SAME value the
|
|
1910
|
+
* Attestation Object carries as `operator` and the same one section 9.3 step
|
|
1911
|
+
* 7b(iii-a) hands to step 9, so the bound is copied from
|
|
1912
|
+
* `attestation_object.schema.json` rather than chosen: if the two disagreed,
|
|
1913
|
+
* an operator an attestation can name would be an operator this read cannot
|
|
1914
|
+
* answer for, and step 9 could not run. Neither bound is stated by the
|
|
1915
|
+
* specification -- see the note on `resource`.
|
|
1916
|
+
*/
|
|
1917
|
+
readonly operator: OperatorId;
|
|
1918
|
+
/**
|
|
1919
|
+
* The capability asserted. Section 8.8 names this argument and nothing
|
|
1920
|
+
* anywhere declares its identifier space, its syntax or its relation to a
|
|
1921
|
+
* rule's required capabilities at evaluation-order step 4; the bound is the
|
|
1922
|
+
* neighbouring one rather than a fourth invention.
|
|
1923
|
+
*
|
|
1924
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1925
|
+
* type: minLength 1, maxLength 256.
|
|
1926
|
+
*/
|
|
1927
|
+
readonly capability: string;
|
|
1928
|
+
/**
|
|
1929
|
+
* The resource the capability is asserted over -- the same identifier space
|
|
1930
|
+
* `floors.json` and `tier_raises` are keyed by, so the bound is copied from
|
|
1931
|
+
* `floors.schema.json`. THE SPECIFICATION BOUNDS NO IDENTIFIER LENGTH
|
|
1932
|
+
* ANYWHERE except L-04's 2,048-character maximum string field, whose
|
|
1933
|
+
* enforcement point is 'Schema' and whose scope (a Proposal's fields, or
|
|
1934
|
+
* every string on every wire) it does not state. Three schemas in this
|
|
1935
|
+
* repository already pin three different bounds. Copying a neighbour's is
|
|
1936
|
+
* the least bad of the available moves and is not a reading of the document.
|
|
1937
|
+
*
|
|
1938
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
1939
|
+
* type: minLength 1, maxLength 256.
|
|
1940
|
+
*/
|
|
1941
|
+
readonly resource: string;
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* The lenient projection of CapabilityGrant: every field optional, every enum
|
|
1945
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
1946
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
1947
|
+
*/
|
|
1948
|
+
export interface RawCapabilityGrant {
|
|
1949
|
+
readonly operator?: string;
|
|
1950
|
+
readonly capability?: string;
|
|
1951
|
+
readonly resource?: string;
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* One snapshot-consistent read of the Context Store, carried on legs F3.1
|
|
1955
|
+
* (Context Store -> Policy Engine) and F3.5 (Context Store -> Executor) of
|
|
1956
|
+
* the flow-leg register (ZIFFER-DEPLOY-001 Annex A). Section 8.7 requires
|
|
1957
|
+
* that Context reads be snapshot-consistent, which is why ONE read is ONE
|
|
1958
|
+
* object carrying every fact read under ONE `snapshot_hash`, rather than a
|
|
1959
|
+
* separate response per function of the section 8.8 interface. Split into
|
|
1960
|
+
* per-function responses, a Policy Engine can answer capability at
|
|
1961
|
+
* evaluation-order step 4 from one snapshot and tier at step 7 from another,
|
|
1962
|
+
* and the consistency section 8.7 requires holds of neither.
|
|
1963
|
+
*
|
|
1964
|
+
* THE RULE THIS OBJECT EXISTS TO CARRY, AND THE ONE A CONSUMER MUST NOT GET
|
|
1965
|
+
* WRONG: A CONTEXT READ RAISES, AND CAN NEVER LOWER. TR-1 puts a resource's
|
|
1966
|
+
* floor tier in the signed bundle (`floors.json`), read-only to the runtime
|
|
1967
|
+
* under PB-4. TR-2 lets the Context Store serve a RAISE and nothing else.
|
|
1968
|
+
* TR-3 composes them at the consumer: `effective_tier(r) = max(floor(r),
|
|
1969
|
+
* raise(r))`, absent raise = T0, and evaluation order step 7 states the same
|
|
1970
|
+
* composition. Because risk functions only escalate (RK-3), TR-4 is then a
|
|
1971
|
+
* theorem rather than a hope: a compromised Context Store can drive
|
|
1972
|
+
* effective tier UP, producing more attestation, which is safe, and can
|
|
1973
|
+
* never drive it below the signed floor. It is mechanized as
|
|
1974
|
+
* `FloorDominance` (Annex B, theorem 2, SMT-discharged over the 4x4 lattice
|
|
1975
|
+
* and killed by mutation M1), and section 4.3 classes the Context Store on
|
|
1976
|
+
* exactly that basis -- 'Trusted for RAISES only. Cannot lower effective
|
|
1977
|
+
* tier.'
|
|
1978
|
+
*
|
|
1979
|
+
* WHAT BREAKS IF A CONSUMER TREATS THIS OBJECT AS ABLE TO LOWER. The whole
|
|
1980
|
+
* of v1.2.0's most serious defect returns, and section 8.3.2 opens by naming
|
|
1981
|
+
* it: tier data in two mutually exclusive places, so RK-5's two-person
|
|
1982
|
+
* control over floor changes protects a copy that does not feed evaluation.
|
|
1983
|
+
* That is C2, and its attack is T-16, tier suppression -- falsify a
|
|
1984
|
+
* resource's tier downward and the escalation trigger never fires, so a
|
|
1985
|
+
* floor-T3 action grades LOW, takes step 11's ALLOW branch, and executes
|
|
1986
|
+
* with no quorum. INV-1-HIGH is stated over floor-only risk (TR-5) precisely
|
|
1987
|
+
* so that no single runtime component can forge it downward; a consumer that
|
|
1988
|
+
* took an effective tier, or a floor, from this object would move the value
|
|
1989
|
+
* back under the control of the component section 10's invariant table says
|
|
1990
|
+
* must not have it. Annex B's proof would still discharge, and it would be a
|
|
1991
|
+
* proof about a composition the implementation no longer performs. Note also
|
|
1992
|
+
* which direction is cheap: TR-9 makes lowering a floor an offline-key
|
|
1993
|
+
* deploy under RK-5 while raising stays fast through this object, and that
|
|
1994
|
+
* asymmetry is the design.
|
|
1995
|
+
*
|
|
1996
|
+
* WHAT IS NOT A FIELD, EACH ABSENT ON PURPOSE. (1) A FLOOR TIER, and (2) AN
|
|
1997
|
+
* EFFECTIVE TIER. The floor is TR-1's, comes from the signed bundle the
|
|
1998
|
+
* consumer already holds, and a second copy of it here is C2 restated at the
|
|
1999
|
+
* wire; the effective tier is TR-3's `max`, which the consumer computes, and
|
|
2000
|
+
* a transmitted one is a derived security value accepted from the party
|
|
2001
|
+
* under verification (TR-8's rule, RES-8's wording). (3) A `granted: false`
|
|
2002
|
+
* capability answer -- see `capability_grants`. (4) THE PROVIDER CLASS. CP-1
|
|
2003
|
+
* declares provider selection per tenant IN THE SIGNED BUNDLE, and CP-3
|
|
2004
|
+
* requires evaluation logic to be identical across providers; a provider
|
|
2005
|
+
* class carried here is both a value read from the party being verified and
|
|
2006
|
+
* a field a consumer could branch on, and CP-3 forbids the branch. (5) A
|
|
2007
|
+
* TENANT IDENTIFIER. DP-43 puts tenant separation for the Context Store at
|
|
2008
|
+
* the CREDENTIAL boundary -- a credential scoped to one tenant must not be
|
|
2009
|
+
* able to read another tenant's rows -- so tenancy is a property of the
|
|
2010
|
+
* connection rather than a claim in the payload. A claimed `tenant_id` would
|
|
2011
|
+
* be self-asserted scope from the store, which is the same shape again. The
|
|
2012
|
+
* residual is real and is disclosed rather than closed: nothing in the
|
|
2013
|
+
* payload lets a consumer detect a store that answered with another tenant's
|
|
2014
|
+
* facts, and DP-78 already records that this repository reads no credential
|
|
2015
|
+
* domain and can audit no such boundary. (6) A SIGNATURE. WE-1 makes
|
|
2016
|
+
* COSE_Sign1 the receipt's carrier and AT-8a pins the Attestation Object's
|
|
2017
|
+
* encoding; no clause states a signature over a Context read. Section 4.3
|
|
2018
|
+
* authenticates this leg at the transport (mTLS REQUIRED between components)
|
|
2019
|
+
* and accepts the store as trusted-for-raises-only, which is coherent only
|
|
2020
|
+
* because a compromised store fails safe in both available directions.
|
|
2021
|
+
* Section 8.7.1's 'Context Store signing key SHOULD be in an offline HSM' is
|
|
2022
|
+
* INFORMATIVE and names no bytes it signs, so a `signature` property here
|
|
2023
|
+
* would invent an envelope the specification does not have.
|
|
2024
|
+
*
|
|
2025
|
+
* WHY THE FIELD SET IS CLOSED, AND WHY THAT CLAIM IS WEAKER HERE THAN IT IS
|
|
2026
|
+
* FOR THE ATTESTATION OBJECT. `additionalProperties` is false and every
|
|
2027
|
+
* property is required. NO CLAUSE STATES THIS RULE. AT-8b closes the
|
|
2028
|
+
* Attestation Object's field set and calls the closure the security property
|
|
2029
|
+
* itself; there is no AT-8b for a Context read. Closure is adopted because
|
|
2030
|
+
* it is the fail-safe direction -- an unknown key is refused rather than
|
|
2031
|
+
* ignored, a missing key is refused rather than defaulted -- and because of
|
|
2032
|
+
* Z4: any optional field gives one read two valid encodings, and the receipt
|
|
2033
|
+
* carries `context_snapshot_hash` (AT-1, section 9.2) as a name for exactly
|
|
2034
|
+
* this object, so two encodings of one snapshot are two names for it. That
|
|
2035
|
+
* argument is CONDITIONAL on a preimage the specification does not define
|
|
2036
|
+
* (see `snapshot_hash`), which is why it is recorded as a gap rather than
|
|
2037
|
+
* presented as a reading. The consequence of an optional field here is
|
|
2038
|
+
* therefore not Z4's amplification but DIVERGENCE: one consumer refuses the
|
|
2039
|
+
* unknown key, another ignores it, and at a verifier a divergence is
|
|
2040
|
+
* indistinguishable from a compromise.
|
|
2041
|
+
*
|
|
2042
|
+
* ENCODING IS NOT PINNED BY ANY CLAUSE, AND THAT IS A HOLE RATHER THAN A
|
|
2043
|
+
* CHOICE. Section 9.2 pins JCS (RFC 8785) for the Proposal and canonical
|
|
2044
|
+
* CBOR (RFC 8949 section 4.2) for the receipt and the Attestation Object
|
|
2045
|
+
* (WE-1, WE-2, AT-8a). Nothing pins a canonicalization for the Context read,
|
|
2046
|
+
* and section 8.8 gives `snapshot_hash()` a return type and never a
|
|
2047
|
+
* preimage. So the one artifact in this register whose digest is carried
|
|
2048
|
+
* inside a signed receipt is the one artifact with no stated canonical form.
|
|
2049
|
+
* The JSON below is written as diagnostic form, in the same sense WE-3 gives
|
|
2050
|
+
* that phrase for receipts, and it is NOT an assertion that JSON is the wire
|
|
2051
|
+
* form; no clause says what the wire form is.
|
|
2052
|
+
*
|
|
2053
|
+
* SUITE 12 CLASSIFICATION. Every field below is class T at the schema layer,
|
|
2054
|
+
* because this document constrains SHAPE and shape is not authority. Two of
|
|
2055
|
+
* them are T at the SYSTEM layer as well, with residuals the specification
|
|
2056
|
+
* already discloses rather than residuals this file is introducing.
|
|
2057
|
+
* Capability facts: section 8.7's note and section 10's invariant table
|
|
2058
|
+
* state that a compromised Context Store can withhold a capability (DENY,
|
|
2059
|
+
* safe), raise a tier (more attestation, safe), or FALSIFY A GRANT AT
|
|
2060
|
+
* MEDIUM, which is INV-1-MEDIUM's disclosed residual -- INV-1-HIGH survives
|
|
2061
|
+
* because floor-HIGH already required a quorum and step 9 is a revocation
|
|
2062
|
+
* recheck closing the issuance-to-execution window (T-10), not the
|
|
2063
|
+
* authorization itself. `snapshot_hash`: TR-10's rule is that a transmitted
|
|
2064
|
+
* identifier is a NAME for a binding and not EVIDENCE of one, and this
|
|
2065
|
+
* identifier is not merely unverified but unverifiable, because no clause
|
|
2066
|
+
* says what it covers.
|
|
2067
|
+
*
|
|
2068
|
+
* WHAT THIS SCHEMA DOES NOT CONSTRAIN, listed so that shape validity is
|
|
2069
|
+
* never read as conformance. (a) SNAPSHOT CONSISTENCY (section 8.7) is a
|
|
2070
|
+
* property of how the facts were read, not of the object; a store that
|
|
2071
|
+
* assembles a `ContextRead` from three different snapshots and stamps one
|
|
2072
|
+
* hash on it is shape-valid here. (b) THE FRESHNESS BOUND. CP-2 resolves
|
|
2073
|
+
* stale freshness to DENY and L-18 caps revocation staleness at 5 minutes,
|
|
2074
|
+
* but the contract that applies is the one section 8.8's provider table
|
|
2075
|
+
* gives for the bundle-declared provider class (CP-1), so the bound is
|
|
2076
|
+
* evaluated by the consumer against the signed bundle and never parsed here
|
|
2077
|
+
* -- see `freshness_seconds`. (c) THAT THE READ IS LIVE. Step 9 requires the
|
|
2078
|
+
* Executor to RE-QUERY at execution time; a cached `ContextRead` replayed
|
|
2079
|
+
* inside its freshness window is indistinguishable from a fresh one at this
|
|
2080
|
+
* layer, and that is the whole of T-10. (d) THAT THE ANSWERS MATCH THE
|
|
2081
|
+
* QUESTIONS. The specification defines no query message, so nothing binds
|
|
2082
|
+
* this response to the read that was requested. (e) ORDERING, of
|
|
2083
|
+
* `capability_grants` or of anything else.
|
|
2084
|
+
*
|
|
2085
|
+
* NOTHING IN THIS REPOSITORY PRODUCES OR CONSUMES THIS OBJECT, AND SAYING SO
|
|
2086
|
+
* IS THE POINT. The Python reference models Context as an in-process dict of
|
|
2087
|
+
* operator -> set of `"task_type:target"` strings (`acp_executor.py`,
|
|
2088
|
+
* `Executor.context`), read once at step 9. It implements none of section
|
|
2089
|
+
* 8.8's four functions: there is no `snapshot_hash()`, no `freshness()`, and
|
|
2090
|
+
* `tier_raise` appears nowhere in `reference/` or `crates/` -- both
|
|
2091
|
+
* evaluators bind `resource.effective_tier` to the FLOOR (TR-5's floor-only
|
|
2092
|
+
* computation), so TR-3's `max` has no implementation anywhere and the raise
|
|
2093
|
+
* half of the floor/raise model is proof and prose only.
|
|
2094
|
+
* `context_snapshot_hash` appears in every fixture as the literal string
|
|
2095
|
+
* `"sha256:ctx"`, which does not even match the `sha256` type this file
|
|
2096
|
+
* references. ACP-52 already records that nothing validates a fixture
|
|
2097
|
+
* against these schemas; DP-57 records that the fail-closed response to an
|
|
2098
|
+
* unreachable or stale Context Store is a normative MUST with NOTHING in
|
|
2099
|
+
* this repository that goes red when it is violated. Per DP-86 a component
|
|
2100
|
+
* gets a register row whether or not anyone built it, and by the same
|
|
2101
|
+
* argument it gets a schema.
|
|
2102
|
+
* Generated from context_read.schema.json.
|
|
2103
|
+
*/
|
|
2104
|
+
export interface ContextRead {
|
|
2105
|
+
/**
|
|
2106
|
+
* The identifier of the snapshot every fact in this object was read from
|
|
2107
|
+
* (section 8.8, `snapshot_hash() -> sha256`). It is what the Decision
|
|
2108
|
+
* carries forward as `context_snapshot_hash` in the receipt (section 9.2)
|
|
2109
|
+
* and inside every Attestation Object (AT-1), so the value a human
|
|
2110
|
+
* attester's device shows and signs over is this one.
|
|
2111
|
+
*
|
|
2112
|
+
* IT IS A NAME FOR A BINDING, NOT EVIDENCE OF ONE (TR-10; RES-8 is the
|
|
2113
|
+
* dossier's wording of the same rule). Worse than the usual case: it is not
|
|
2114
|
+
* merely unverified, it is UNVERIFIABLE. Section 8.8 declares the return
|
|
2115
|
+
* type and no clause anywhere says WHAT BYTES the digest covers or under
|
|
2116
|
+
* which canonicalization, so no consumer can recompute it, no two
|
|
2117
|
+
* implementations can be shown to agree on it, and a store is free to emit a
|
|
2118
|
+
* constant. The consequence is that the receipt's `context_snapshot_hash` --
|
|
2119
|
+
* a field carried through the signed body and through the attested object,
|
|
2120
|
+
* the field that is supposed to say WHICH Context the Decision was computed
|
|
2121
|
+
* over -- discharges nothing today. Section 9.3's checklist has no step that
|
|
2122
|
+
* reads it, which is consistent: there is nothing to check it against.
|
|
2123
|
+
*
|
|
2124
|
+
* This is a normative gap found by writing the schema rather than by reading
|
|
2125
|
+
* the text, which is the same way AT-1's `b64:` nonce type was found to be
|
|
2126
|
+
* unstated (ACP-87, closed at the root as WE-4 in v1.3.18). It is recorded
|
|
2127
|
+
* here and NOT closed here: pinning a preimage in a schema would make this
|
|
2128
|
+
* file the definition of a type the specification does not have, and a
|
|
2129
|
+
* second implementation written from the document alone would never see it.
|
|
2130
|
+
*/
|
|
2131
|
+
readonly snapshotHash: Sha256Hash;
|
|
2132
|
+
/**
|
|
2133
|
+
* The age of the last authoritative update behind this read, in whole
|
|
2134
|
+
* seconds (section 8.8, `freshness() -> age of last authoritative update`).
|
|
2135
|
+
* CP-2 makes it a control input rather than telemetry: all providers MUST
|
|
2136
|
+
* honour P-4, and stale freshness resolves to DENY with a distinct reason
|
|
2137
|
+
* code -- unknown is never LOW. What the bound IS depends on the provider
|
|
2138
|
+
* class the signed bundle declares for the tenant (CP-1) and on section
|
|
2139
|
+
* 8.8's provider table: a static signed entitlement bundle has staleness
|
|
2140
|
+
* near zero, workload attestation is per-request, and live directory sync
|
|
2141
|
+
* must be within L-18's 5 minutes for revocations or the sync is broken and
|
|
2142
|
+
* the deployment fails closed.
|
|
2143
|
+
*
|
|
2144
|
+
* THERE IS NO MAXIMUM HERE, DELIBERATELY. Encoding L-18 as a schema bound
|
|
2145
|
+
* would turn a control decision into a parse error: the consumer must DENY
|
|
2146
|
+
* with P-4's distinct reason code, and a malformed-message rejection loses
|
|
2147
|
+
* the reason code, loses the audit record of a Decision that was refused for
|
|
2148
|
+
* staleness, and applies the live-sync bound to providers section 8.8 gives
|
|
2149
|
+
* a different contract. `minimum` is 0 because an age cannot be negative and
|
|
2150
|
+
* a value outside the domain MUST refuse rather than take the permissive
|
|
2151
|
+
* branch -- EL-2 states that reading for the evaluation environment and P-4
|
|
2152
|
+
* states it generally; a negative age read as 'very fresh' is the permissive
|
|
2153
|
+
* branch arriving through a type error.
|
|
2154
|
+
*
|
|
2155
|
+
* THE NAME AND THE UNIT ARE THIS FILE'S, NOT THE SPECIFICATION'S, AND BOTH
|
|
2156
|
+
* ARE GAPS. Section 8.8 names the function `freshness()` and its return 'age
|
|
2157
|
+
* of last authoritative update'; it fixes no unit, no wire type and no name,
|
|
2158
|
+
* so `_seconds` is carried in the field name because a bare duration with
|
|
2159
|
+
* its unit in a comment is a unit nobody reads.
|
|
2160
|
+
*
|
|
2161
|
+
* AND THE RES-8 OBJECTION, STATED RATHER THAN SILENTLY FIXED. An AGE is
|
|
2162
|
+
* computed by the Context Store, on the Context Store's clock, and the
|
|
2163
|
+
* staleness decision that fires CP-2's DENY is then keyed on a derived value
|
|
2164
|
+
* supplied by the party being read -- TR-8's shape exactly. Carrying the
|
|
2165
|
+
* INSTANT of the last authoritative update instead would let the consumer
|
|
2166
|
+
* derive the age against its own NTS-disciplined clock within the L-20 skew
|
|
2167
|
+
* allowance, which is what section 12.5 and leg F9.1 exist to make
|
|
2168
|
+
* available, and would move this row from 'derived value accepted as
|
|
2169
|
+
* transmitted' to 'raw fact plus consumer-computed derivation'. This file
|
|
2170
|
+
* models the return type section 8.8 states. Changing it is a text change,
|
|
2171
|
+
* and making it silently inside a schema is how a register acquires a claim
|
|
2172
|
+
* nobody decided (the ACP-86 shape).
|
|
2173
|
+
*
|
|
2174
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2175
|
+
* type: minimum 0.
|
|
2176
|
+
*/
|
|
2177
|
+
readonly freshnessSeconds: number;
|
|
2178
|
+
/**
|
|
2179
|
+
* The capability facts this snapshot asserts: one entry per (operator,
|
|
2180
|
+
* capability, resource) triple that `has_capability` answered TRUE for
|
|
2181
|
+
* (section 8.8). Consumed at evaluation-order step 4 (operator holds every
|
|
2182
|
+
* required capability, else DENY) and again at section 9.3 step 9, where for
|
|
2183
|
+
* recomputed floor-HIGH the Executor re-queries capability of the operator
|
|
2184
|
+
* established in step 7b(iii-a) -- taken from the verified Attestation
|
|
2185
|
+
* Object, never from the receipt body -- and fails closed if it has been
|
|
2186
|
+
* revoked.
|
|
2187
|
+
*
|
|
2188
|
+
* THERE IS NO BOOLEAN, AND ABSENCE IS THE ANSWER. `has_capability` returns a
|
|
2189
|
+
* bool, but under P-4 and CP-2 the two false-ish outcomes are ONE outcome:
|
|
2190
|
+
* an unknown operator, an unknown resource and a capability the store simply
|
|
2191
|
+
* declined to assert all resolve to DENY. Representing them differently
|
|
2192
|
+
* would give one outcome two encodings while no decision anywhere depends on
|
|
2193
|
+
* the difference -- two spellings of one thing, which is the defect class
|
|
2194
|
+
* this repository is built around, and here it would also split the snapshot
|
|
2195
|
+
* digest.
|
|
2196
|
+
*
|
|
2197
|
+
* `uniqueItems` because a repeated grant is a second spelling of one fact.
|
|
2198
|
+
* Note that a grant's identity is the WHOLE TRIPLE, which is why
|
|
2199
|
+
* `x-acp-keyed-by` is deliberately absent: a lookup keyed by `operator`
|
|
2200
|
+
* alone answers 'granted' for the wrong capability on the wrong resource,
|
|
2201
|
+
* and a generator that emitted one would be building the vulnerability. That
|
|
2202
|
+
* used to cost a check that would not run: `tools/codegen.py` read an
|
|
2203
|
+
* array's `x-acp-absent` only alongside `x-acp-keyed-by`, so the fail-safe
|
|
2204
|
+
* declared below was dropped in silence -- a rule written down that reached
|
|
2205
|
+
* nothing, with every gate green. It was declared anyway, because the schema
|
|
2206
|
+
* is the normative artifact and the rule belongs in it, and the gap was
|
|
2207
|
+
* disclosed rather than worked around by mis-keying the lookup. ACP-78 M1
|
|
2208
|
+
* closed it from the generator's side: an array carrying an absent rule with
|
|
2209
|
+
* no key now HALTS in the root that emits accessors, and in this root the
|
|
2210
|
+
* rule is carried into the generated field's documentation. No multi-field
|
|
2211
|
+
* key was invented, because the key is the decision and inventing it here
|
|
2212
|
+
* would be building the vulnerability.
|
|
2213
|
+
*
|
|
2214
|
+
* No `maxItems`. L-05 bounds list length at ingress, on a request; a Context
|
|
2215
|
+
* read is not a request, and a store whose honest answer exceeds an invented
|
|
2216
|
+
* cap would have to truncate, which silently converts grants into DENYs.
|
|
2217
|
+
*
|
|
2218
|
+
* ABSENT-ENTRY RULE, carried as documentation and NOT enforced by this type,
|
|
2219
|
+
* because this is a message and not signed policy. An entry absent from this
|
|
2220
|
+
* table is a REFUSAL under P-4, CP-2, never a default value: a triple absent
|
|
2221
|
+
* from this list is a capability the snapshot does not assert, and P-4's
|
|
2222
|
+
* default is DENY: no matching rule, unknown capability, unclassified
|
|
2223
|
+
* resource and unavailable Context Store each refuse, with a distinct reason
|
|
2224
|
+
* code. CP-2 binds every provider class to that rule. An accessor that
|
|
2225
|
+
* returns a bare Option here is one unwrap away from treating 'the store
|
|
2226
|
+
* said nothing' as 'the store said yes', which at section 9.3 step 9 is a
|
|
2227
|
+
* revoked operator executing a floor-HIGH action. NO ACCESSOR IS GENERATED
|
|
2228
|
+
* FOR THIS LIST AND THE RULE IS CARRIED AS DOCUMENTATION (the wire root's
|
|
2229
|
+
* `absent_emits` is `doc`), so the sentence above describes what a CONSUMER
|
|
2230
|
+
* must do rather than what a generated lookup does for it. Two things forced
|
|
2231
|
+
* that. A grant's identity is the whole triple `(operator, capability,
|
|
2232
|
+
* resource)` -- section 8.8's `has_capability` takes all three -- and
|
|
2233
|
+
* `x-acp-keyed-by` names ONE field, so a generated lookup keyed by any
|
|
2234
|
+
* single one of them answers 'granted' for the wrong resource, which is
|
|
2235
|
+
* building the vulnerability rather than typing it. And ACP-78 M1 found that
|
|
2236
|
+
* an array's `x-acp-absent` was read only ALONGSIDE `x-acp-keyed-by`:
|
|
2237
|
+
* declared without a key it was dropped in silence, which is the failure the
|
|
2238
|
+
* generator's keyword audit refuses one level up. That is now a HALT in the
|
|
2239
|
+
* root that emits accessors.
|
|
2240
|
+
*
|
|
2241
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2242
|
+
* type: uniqueItems.
|
|
2243
|
+
*/
|
|
2244
|
+
readonly capabilityGrants: readonly CapabilityGrant[];
|
|
2245
|
+
/**
|
|
2246
|
+
* Resource identifier -> RAISE tier (section 8.8, `tier_raise(resource) ->
|
|
2247
|
+
* T0..T3`; TR-2). A RAISE and nothing else: the consumer holds the floor
|
|
2248
|
+
* from the signed bundle (TR-1) and computes `effective_tier(r) =
|
|
2249
|
+
* max(floor(r), raise(r))` itself (TR-3, evaluation order step 7). Absent
|
|
2250
|
+
* raise is T0, so an unclassified resource is governed entirely by its floor
|
|
2251
|
+
* -- which under RK-1 is T3 when `floors.json` does not name it. The two
|
|
2252
|
+
* defaults compose in the safe direction on purpose: absent floor is the
|
|
2253
|
+
* HIGHEST tier, absent raise is the LOWEST, because the floor is the value a
|
|
2254
|
+
* compromised runtime must not be able to lower and the raise is the value
|
|
2255
|
+
* it must not be able to invent downward.
|
|
2256
|
+
*
|
|
2257
|
+
* The key space is `floors.json`'s key space and the bound is copied from
|
|
2258
|
+
* `floors.schema.json` for that reason: TR-3's `max` is taken over the two
|
|
2259
|
+
* maps keyed by the same resource identifier, so a resource nameable in one
|
|
2260
|
+
* map and unnameable in the other has no defined effective tier. It is an
|
|
2261
|
+
* open map for the same reason floors is -- resources are
|
|
2262
|
+
* deployment-specific -- and the VALUES are closed.
|
|
2263
|
+
*
|
|
2264
|
+
* A T0 ENTRY AND AN ABSENT ENTRY MEAN THE SAME THING, AND THE SPECIFICATION
|
|
2265
|
+
* PERMITS BOTH. TR-2 gives the return type as T0..T3 and TR-3 makes
|
|
2266
|
+
* `max(floor, T0) = floor`, so an explicit T0 is a second encoding of 'no
|
|
2267
|
+
* raise'. The enum is not narrowed to T1..T3 here because narrowing the
|
|
2268
|
+
* stated range would be this file exceeding the document; the consequence is
|
|
2269
|
+
* recorded instead, and it is Z4-shaped -- two encodings of one snapshot are
|
|
2270
|
+
* two `snapshot_hash` values for it, in whatever preimage a future clause
|
|
2271
|
+
* defines.
|
|
2272
|
+
*
|
|
2273
|
+
* ABSENT-ENTRY RULE, carried as documentation and NOT enforced by this type,
|
|
2274
|
+
* because this is a message and not signed policy. An entry absent from this
|
|
2275
|
+
* table reads as `T0` under TR-3: TR-3 states it in as many words: absent
|
|
2276
|
+
* raise = T0. This is the one place in this file where the LOWEST value is
|
|
2277
|
+
* the fail-safe one, and the reason is that the raise is not the tier -- the
|
|
2278
|
+
* floor is, it comes from the signed bundle, and RK-1 already defaults an
|
|
2279
|
+
* unnamed resource to T3. A lookup that refused here, or that defaulted
|
|
2280
|
+
* upward, would let a silent Context Store escalate every resource it has
|
|
2281
|
+
* never heard of and hand a compromised store a denial of service against
|
|
2282
|
+
* the attestation queue that AQ-1 to AQ-3 exist to protect. THIS OBJECT
|
|
2283
|
+
* SETTLED A TENSION INSIDE THE GENERATOR AND THE ANSWER IS WORTH READING
|
|
2284
|
+
* WITH THE RULE. `tools/codegen.py` HALTED on a map with no absent rule --
|
|
2285
|
+
* every table a caller can miss needs its absent case stated in the schema
|
|
2286
|
+
* -- while its wire-root header note said no fail-safe accessor should be
|
|
2287
|
+
* emitted in that root at all, because an absent rule on a wire field would
|
|
2288
|
+
* be a default supplied by the party under verification. This is the only
|
|
2289
|
+
* MESSAGE in the register that is a LOOKUP TABLE, so it is the case neither
|
|
2290
|
+
* was written for. ACP-78 M1 resolved it per root: the wire root declares
|
|
2291
|
+
* `absent_emits="doc"`, the rule below is CARRIED into the generated field's
|
|
2292
|
+
* documentation, and NO accessor is emitted. The deciding argument is one
|
|
2293
|
+
* root over: `floors.schema.json` already generates the accessor that
|
|
2294
|
+
* answers T3 on a miss (RK-1, P-4), and a second generated accessor
|
|
2295
|
+
* answering T0 on a miss over a TRANSMITTED map would put the permissive
|
|
2296
|
+
* answer behind an API shaped like a control, one import away from the
|
|
2297
|
+
* signed one. The rule stated here is TR-3's and is right; where it has
|
|
2298
|
+
* effect is in the consumer's `max(floor, raise)`, against a floor that came
|
|
2299
|
+
* from the signed bundle.
|
|
2300
|
+
*/
|
|
2301
|
+
readonly tierRaises: Readonly<Record<string, Tier>>;
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* The lenient projection of ContextRead: every field optional, every enum
|
|
2305
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
2306
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
2307
|
+
*/
|
|
2308
|
+
export interface RawContextRead {
|
|
2309
|
+
readonly snapshotHash?: string;
|
|
2310
|
+
readonly freshnessSeconds?: number;
|
|
2311
|
+
readonly capabilityGrants?: readonly RawCapabilityGrant[];
|
|
2312
|
+
readonly tierRaises?: Readonly<Record<string, string>>;
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* The SIGNED BODY of a Decision Receipt under receipt version 3, carried on
|
|
2316
|
+
* leg F3.3 of the flow-leg register (ZIFFER-DEPLOY-001 Annex A, Policy
|
|
2317
|
+
* Engine -> Executor; the row read KMS -> Executor until ACP-110, and the
|
|
2318
|
+
* KMS cannot be that sender: B-1a puts the canonical Proposal beside the
|
|
2319
|
+
* receipt on this leg and the KMS never holds it, so the signed receipt
|
|
2320
|
+
* returns to the Policy Engine on F3.2's response). The Executor trusts a
|
|
2321
|
+
* signature, not a channel (9.1): B-4 forbids it accepting any instruction
|
|
2322
|
+
* from any other source, so this object is the only thing in the system that
|
|
2323
|
+
* can cause an external effect.
|
|
2324
|
+
*
|
|
2325
|
+
* WHERE THE EDGE OF THIS OBJECT IS. Two clauses fix it. WE-2: the COSE
|
|
2326
|
+
* payload is the canonical encoding of the receipt structure. AB-5: the
|
|
2327
|
+
* bytes covered by the receipt signature are the canonical encoding of the
|
|
2328
|
+
* receipt body WITHOUT the attestation entries. Every property below is
|
|
2329
|
+
* inside the signature and nothing else is, which is also what makes AB-6
|
|
2330
|
+
* measurable -- the 4,096-byte bound is on THIS object's canonical encoding,
|
|
2331
|
+
* and it is the whole reason version 3 exists.
|
|
2332
|
+
*
|
|
2333
|
+
* WHAT IS NOT A FIELD, EACH ABSENT ON PURPOSE.
|
|
2334
|
+
* (1) The SIGNATURE. WE-1 makes COSE_Sign1 the carrier, so the signature is
|
|
2335
|
+
* the envelope around this payload, never a member of it. The Python
|
|
2336
|
+
* reference carries a sibling `sig` key and excludes it from the signed body
|
|
2337
|
+
* by name (`SIGNED_EXCLUDE`), a stand-in for a COSE envelope it does not yet
|
|
2338
|
+
* emit and which its own CRYPTO DISCLOSURE names as a gap. Giving `sig` a
|
|
2339
|
+
* property here would make that stand-in look normative.
|
|
2340
|
+
* (2) The ATTESTATION ENTRIES. AB-1: the entries MUST travel alongside the
|
|
2341
|
+
* receipt and MUST NOT appear in the signed body; they are bound solely by
|
|
2342
|
+
* `attestation_digests` below. Note what the specification does not say --
|
|
2343
|
+
* it names no envelope structure, no field name and no ordering for that
|
|
2344
|
+
* beside-channel. `attestations` is the reference implementation's choice of
|
|
2345
|
+
* name, not the document's.
|
|
2346
|
+
* (3) `reversibility`. RV-2 is explicit: reversibility lives only in the
|
|
2347
|
+
* signed bundle and there is 'no receipt field of record'. RV-3 recomputes
|
|
2348
|
+
* it, because a receipt-asserted REVERSIBLE would suppress DR-9's
|
|
2349
|
+
* acknowledgement exactly as v1.3.0's forged floor-only risk suppressed
|
|
2350
|
+
* attestation (X1). The reference nevertheless reads a `reversibility` key
|
|
2351
|
+
* when one is present; under this closed field set such a receipt is
|
|
2352
|
+
* malformed, and that disagreement is recorded rather than reconciled.
|
|
2353
|
+
* (4) `attestation_id` and `action_id`. Both are DERIVED: the first is
|
|
2354
|
+
* SHA-256 over the canonical Attestation Object (AT-1, AT-8a) and the
|
|
2355
|
+
* Executor recomputes it (9.3 step 7b(v)); the second is the pair
|
|
2356
|
+
* (proposal_hash, origin_nonce) pinned by the ledger (DS-6a, DS-6f). A
|
|
2357
|
+
* transmitted identifier is a name for a binding, not evidence of one
|
|
2358
|
+
* (RES-9), and a schema that gave either a property would invite exactly
|
|
2359
|
+
* that read.
|
|
2360
|
+
*
|
|
2361
|
+
* WHY THE FIELD SET IS CLOSED, AND THE CAVEAT THAT GOES WITH IT.
|
|
2362
|
+
* `additionalProperties` is false and every property but `origin_nonce` is
|
|
2363
|
+
* required. THE SPECIFICATION DOES NOT STATE THIS RULE. AT-8b closes the
|
|
2364
|
+
* Attestation Object field set and calls the closure the security property
|
|
2365
|
+
* itself; there is no AT-8b for the receipt. Closure is adopted here because
|
|
2366
|
+
* it is the fail-safe direction -- an unknown key is refused rather than
|
|
2367
|
+
* ignored, a missing key is refused rather than defaulted -- and because the
|
|
2368
|
+
* alternative is a per-implementation field set on the one object whose
|
|
2369
|
+
* canonical bytes are signed and whose size is capped. It is recorded as a
|
|
2370
|
+
* gap, not presented as a reading: a clause saying for the receipt what
|
|
2371
|
+
* AT-8b says for the object is a text change this schema cannot make.
|
|
2372
|
+
*
|
|
2373
|
+
* Z4 DOES NOT REACH THIS OBJECT THE WAY IT REACHES THE ATTESTATION OBJECT,
|
|
2374
|
+
* AND THE DIFFERENCE IS WORTH STATING RATHER THAN ASSUMED. An Attestation
|
|
2375
|
+
* Object's ledger identity is a digest of itself, so two encodings are two
|
|
2376
|
+
* ids and one human approval claims two slots (T-14). A receipt's ledger
|
|
2377
|
+
* identity is `nonce`, a carried field consumed under CL-3, so two encodings
|
|
2378
|
+
* of one receipt still claim one slot. What an optional field costs here is
|
|
2379
|
+
* not amplification but DIVERGENCE: one verifier refuses the unknown key,
|
|
2380
|
+
* another ignores it, and at the verifier a divergence is indistinguishable
|
|
2381
|
+
* from a forgery. That is a weaker consequence than Z4's and it is still a
|
|
2382
|
+
* reason to close the set.
|
|
2383
|
+
*
|
|
2384
|
+
* ENCODING. Canonical CBOR, RFC 8949 section 4.2 (WE-1, WE-2) -- the same
|
|
2385
|
+
* canonicalization AT-8a pins for the Attestation Object. JCS (RFC 8785) is
|
|
2386
|
+
* the Proposal's rule and MUST NOT be used here. WE-3: a reference decoder
|
|
2387
|
+
* emits JSON for review and JSON MUST NOT be accepted as receipt input, so
|
|
2388
|
+
* the shape below is diagnostic form and never a wire form.
|
|
2389
|
+
*
|
|
2390
|
+
* WHAT THIS SCHEMA CANNOT CONSTRAIN. Listed so that shape validity is never
|
|
2391
|
+
* read as conformance; each item is a per-implementation obligation and
|
|
2392
|
+
* several carry their own conformance case and mutant (AB-5).
|
|
2393
|
+
* - AB-2's ORDERING. `attestation_digests` MUST be sorted strictly ascending
|
|
2394
|
+
* over the UTF-8 bytes of the digest strings including the `sha256:` prefix.
|
|
2395
|
+
* JSON Schema has no sortedness keyword, so an unsorted list is shape-valid
|
|
2396
|
+
* here and MUST still be refused under AB-2.
|
|
2397
|
+
* - AB-6's SIZE BOUND. 4,096 bytes of canonical encoding is a property of an
|
|
2398
|
+
* encoding, not of a JSON shape, and AB-6 requires it be checked on measured
|
|
2399
|
+
* bytes rather than on any proxy such as an entry count -- the first draft
|
|
2400
|
+
* capped the count at 50 and measured 4,107 bytes at that cap.
|
|
2401
|
+
* - AB-4's CARDINALITY and AB-1's MEMBERSHIP. Both relate this object to
|
|
2402
|
+
* entries that are not members of it, so neither is expressible here.
|
|
2403
|
+
* - L-14 / 9.3 step 5's WINDOW LENGTH: `expires_at - issued_at` <= 120 s is
|
|
2404
|
+
* arithmetic across two fields.
|
|
2405
|
+
* - CR-3's conjunctive verification and CR-4's containment floor. `alg`
|
|
2406
|
+
* names the suite; whether every declared primitive verified, and whether
|
|
2407
|
+
* the suite contains every primitive the bundle floor names, are decided
|
|
2408
|
+
* elsewhere and never from a transmitted value.
|
|
2409
|
+
* - WHETHER THIS IS A RE-DRIVE. DS-6b says a re-drive receipt MUST carry
|
|
2410
|
+
* `origin_nonce`, and no field in the receipt declares that it is one, so
|
|
2411
|
+
* the conditional obligation cannot be expressed at any layer that reads
|
|
2412
|
+
* only this object.
|
|
2413
|
+
*
|
|
2414
|
+
* SHARED TYPES ARE REFERENCED, NEVER REPEATED. `sha256` and `risk_level`
|
|
2415
|
+
* come from attestation_object.schema.json, the 128-bit `b64:` nonce from
|
|
2416
|
+
* ledger_claim.schema.json where CL-3 consumes it, and two come from the
|
|
2417
|
+
* SIGNED BUNDLE root across the root boundary: `suite_id` from
|
|
2418
|
+
* signature.schema.json, where CR-4's floor is registered, and
|
|
2419
|
+
* `fidelity_class` from adapters.schema.json, the table TR-8 verifies this
|
|
2420
|
+
* receipt's `fidelity` against (ACP-73; it lived in proposal.schema.json, on
|
|
2421
|
+
* the side being verified, until that table existed). Each is defined once
|
|
2422
|
+
* and pointed at from here. A second copy would be a second statement of one
|
|
2423
|
+
* object, which is the defect this repository publishes corrections for --
|
|
2424
|
+
* and the generator halts on a duplicate `x-acp-name` within a root and
|
|
2425
|
+
* across the two, so the copy would not even be silent. A `$ref` may cross
|
|
2426
|
+
* roots for a named SCALAR only (ACP-78 M1): the vocabulary a receipt is
|
|
2427
|
+
* compared against is owned by the signed side, and an object crossing would
|
|
2428
|
+
* drag its field graph with it. This paragraph had said the opposite -- that
|
|
2429
|
+
* a cross-root `$ref` was not a rule the generator had and `suite_id` was
|
|
2430
|
+
* therefore one type in two files -- for the whole of M1, while the `alg`
|
|
2431
|
+
* property below already crossed; prose describing a generator is exactly as
|
|
2432
|
+
* trustworthy as prose describing anything else nothing executes.
|
|
2433
|
+
*
|
|
2434
|
+
* That the homes of the wire-root types are scattered across message schemas
|
|
2435
|
+
* is an accident of which file happened to need them first, not a design.
|
|
2436
|
+
* The durable answer is one shared definitions file in this root that every
|
|
2437
|
+
* message schema references; there is none, so the placement is recorded
|
|
2438
|
+
* here rather than presented as intentional.
|
|
2439
|
+
*
|
|
2440
|
+
* SUITE 12 CLASSIFICATION. At the schema layer every field is class T,
|
|
2441
|
+
* because this document constrains SHAPE and shape is not authority. The
|
|
2442
|
+
* Executor's obligations are elsewhere and are named on the individual
|
|
2443
|
+
* properties rather than restated here: `proposal_hash` and the bundle basis
|
|
2444
|
+
* are BOUND (9.3 steps 3 and 4), floor-only risk and fidelity are RECOMPUTED
|
|
2445
|
+
* and the transmitted values only ever raise a critical alert on
|
|
2446
|
+
* disagreement (TR-8, 9.3 step 7a), the operator identity is taken from the
|
|
2447
|
+
* verified Attestation Objects and never from this body (Y4), and the origin
|
|
2448
|
+
* nonce is read from the immutable ledger binding (DS-6f).
|
|
2449
|
+
*
|
|
2450
|
+
* DISAGREEMENTS SURFACED BY WRITING THIS SCHEMA, PUBLISHED RATHER THAN
|
|
2451
|
+
* SMOOTHED OVER. Section 9.2's example is the only enumeration of receipt
|
|
2452
|
+
* fields the specification gives, and it omits two fields other clauses make
|
|
2453
|
+
* mandatory: `alg` (CR-1 names the receipt explicitly, CR-5 makes it
|
|
2454
|
+
* signature-covered) and `idempotency_key` (DS-6b: 'MUST be carried as a
|
|
2455
|
+
* distinct receipt field'). An implementer building a receipt from that
|
|
2456
|
+
* block alone emits one that is CR-1 non-conformant while conforming to the
|
|
2457
|
+
* text they read, which is the PB-8 shape the document has already published
|
|
2458
|
+
* a correction for. In the other direction, both current implementations
|
|
2459
|
+
* omit nine fields the example carries -- `schema_id`, `schema_version`,
|
|
2460
|
+
* `schema_hash`, `fidelity`, `context_snapshot_hash`, `rule_id`,
|
|
2461
|
+
* `risk_level_effective`, `risk_level_floor_only` and `audit_id` -- and
|
|
2462
|
+
* spell `issued_at` and `expires_at` as numeric epoch seconds where the
|
|
2463
|
+
* example spells RFC 3339. Modelling the specification therefore makes every
|
|
2464
|
+
* fixture in the repository invalid against this file. That is disclosed,
|
|
2465
|
+
* not designed around; ACP-52 already records that nothing validates a
|
|
2466
|
+
* fixture against these schemas and that the fixtures are schema-invalid.
|
|
2467
|
+
* Generated from decision_receipt.schema.json.
|
|
2468
|
+
*/
|
|
2469
|
+
export interface DecisionReceipt {
|
|
2470
|
+
/**
|
|
2471
|
+
* AB-0: exactly 3, verified BEFORE any other check, and every other value
|
|
2472
|
+
* rejected including its absence. Written as an inclusive bound of exactly
|
|
2473
|
+
* one value because that is what AB-0 admits.
|
|
2474
|
+
*
|
|
2475
|
+
* THERE IS NO COMPATIBILITY WINDOW AND THE REASON IS NOT COMPATIBILITY.
|
|
2476
|
+
* Version 2 is the weaker representation; a verifier that accepts both lets
|
|
2477
|
+
* an attacker present version 2 and never face the digest binding at all. A
|
|
2478
|
+
* transition window is therefore not a migration aid, it is a downgrade path
|
|
2479
|
+
* -- the CR-4 shape one layer up, with the FORMAT rather than the suite
|
|
2480
|
+
* negotiated by the party under verification.
|
|
2481
|
+
*
|
|
2482
|
+
* The number is 3 and not 2 because section 9.2's published example already
|
|
2483
|
+
* carried `"receipt_version": 2` for the containment form, and one version
|
|
2484
|
+
* number with two meanings is the two-definitions defect at the wire.
|
|
2485
|
+
*
|
|
2486
|
+
* A verifier MUST NOT let shape validation stand in for this check: a
|
|
2487
|
+
* version-2 receipt has to be refused by the name AB-0, because the
|
|
2488
|
+
* cross-language differential compares refusal names and a generic schema
|
|
2489
|
+
* error is not one.
|
|
2490
|
+
*
|
|
2491
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2492
|
+
* type: minimum 3, maximum 3.
|
|
2493
|
+
*/
|
|
2494
|
+
readonly receiptVersion: number;
|
|
2495
|
+
/**
|
|
2496
|
+
* CR-1: every signed structure MUST carry an explicit `alg` naming a
|
|
2497
|
+
* signature suite registered in the signed bundle, and the receipt is named
|
|
2498
|
+
* in that clause. CR-5 makes it signature-covered, so an issuer cannot
|
|
2499
|
+
* rewrite the suite without invalidating the receipt it travelled on.
|
|
2500
|
+
*
|
|
2501
|
+
* Carrying the suite is not the same as choosing it. The FLOOR comes from
|
|
2502
|
+
* the signed bundle and is satisfied by CONTAINMENT of primitives, never by
|
|
2503
|
+
* rank (CR-4): a suite is a set, `hybrid-ed25519-mldsa65` and `slhdsa128s`
|
|
2504
|
+
* are incomparable, and every implementation that built a rank table from
|
|
2505
|
+
* the pre-v1.3.15 wording served a lattice signature to a deployment that
|
|
2506
|
+
* had chosen hash-based precisely to avoid one. Reading the floor from
|
|
2507
|
+
* anything transmitted asks the party under verification how strictly to
|
|
2508
|
+
* judge it.
|
|
2509
|
+
*
|
|
2510
|
+
* NOTE that section 9.2's receipt example does not show this field, while
|
|
2511
|
+
* CR-1 and CR-5 require it. The clauses govern; the example is incomplete.
|
|
2512
|
+
*
|
|
2513
|
+
* THE VALUE DOMAIN IS DECLARED ONCE, IN THE BUNDLE ROOT, and this field
|
|
2514
|
+
* `$ref`s it across roots:
|
|
2515
|
+
* `spec/schemas/bundle/signature.schema.json#/$defs/suite_id`. It was
|
|
2516
|
+
* declared in both roots until ACP-78 M1 -- one vocabulary, two definitions,
|
|
2517
|
+
* with each copy carrying its own note saying the two must move together,
|
|
2518
|
+
* which is the arrangement that let the strict-Ed25519 rule sit in two
|
|
2519
|
+
* languages. CR-4 compares the receipt's suite against the bundle's
|
|
2520
|
+
* registered floor, so the two artifacts must draw from ONE set or the
|
|
2521
|
+
* comparison is between two vocabularies that merely look alike. It is
|
|
2522
|
+
* deliberately NOT `const`: listing only the hybrid suite would make a
|
|
2523
|
+
* receipt signed under `ed25519` fail SHAPE validation when the required
|
|
2524
|
+
* outcome is a CR-4 refusal naming the floor. A name outside the set is
|
|
2525
|
+
* refused under CR-1 by the verifier and never defaulted to a known suite.
|
|
2526
|
+
*/
|
|
2527
|
+
readonly alg: SuiteId;
|
|
2528
|
+
/**
|
|
2529
|
+
* The graded outcome of section 8.4 step 11. 9.3 step 2: the Executor
|
|
2530
|
+
* proceeds only on ALLOW.
|
|
2531
|
+
*
|
|
2532
|
+
* DELIBERATELY NOT `const: "ALLOW"`, for the reason `suite_id` in the bundle
|
|
2533
|
+
* signature schema is not a single-value enum: pinning the only acceptable
|
|
2534
|
+
* value here would make a DENY receipt fail SHAPE validation when the
|
|
2535
|
+
* required outcome is a refusal named 9.3-2. A check that fires under the
|
|
2536
|
+
* wrong name is a divergence waiting to happen, because the cross-language
|
|
2537
|
+
* differential compares names.
|
|
2538
|
+
*
|
|
2539
|
+
* EO-1: HIGH is never auto-allowed by any configuration, so an ALLOW over a
|
|
2540
|
+
* floor-HIGH action is a claim about a quorum, and that claim is checked
|
|
2541
|
+
* against `attestation_digests` and the entries beside the receipt rather
|
|
2542
|
+
* than believed.
|
|
2543
|
+
*/
|
|
2544
|
+
readonly decision: DecisionOutcome;
|
|
2545
|
+
/**
|
|
2546
|
+
* The canonical Proposal this receipt authorises. 9.3 step 3: the Executor
|
|
2547
|
+
* hashes the Proposal IT received over the same channel (B-1a),
|
|
2548
|
+
* canonicalized identically, and compares -- it hashes what it will execute,
|
|
2549
|
+
* never 'whatever the receipt describes'. Class B.
|
|
2550
|
+
*
|
|
2551
|
+
* This field is a name for a binding. The binding is the comparison, and it
|
|
2552
|
+
* exists because a receipt that merely asserts what it covers is a claim by
|
|
2553
|
+
* the party under verification (RES-9).
|
|
2554
|
+
*/
|
|
2555
|
+
readonly proposalHash: Sha256Hash;
|
|
2556
|
+
/**
|
|
2557
|
+
* SR-1: schemas are content-addressed by `schema_id` + `schema_version` +
|
|
2558
|
+
* `schema_hash`, and SR-5 requires every Decision to carry the triple. The
|
|
2559
|
+
* pattern is section 6.3's `AtomEnvelope`, so the receipt's identifier is
|
|
2560
|
+
* the same type the ingress validated.
|
|
2561
|
+
*
|
|
2562
|
+
* The triple is not decoration: TR-8 has the Executor verify `fidelity`
|
|
2563
|
+
* against the bundle-registered adapter binding for THIS `schema_id`, so an
|
|
2564
|
+
* unregistered id refuses rather than grading.
|
|
2565
|
+
*/
|
|
2566
|
+
readonly schemaId: SchemaId;
|
|
2567
|
+
/**
|
|
2568
|
+
* Semver, per SR-1 and section 6.3's `AtomEnvelope`. Part of the content
|
|
2569
|
+
* address, not a compatibility hint: SR-3 decides narrowing against a
|
|
2570
|
+
* bounded lattice precisely because v1.2.0 left 'narrowing' undefined and a
|
|
2571
|
+
* net-widening change could self-classify and bypass distinct-reviewer
|
|
2572
|
+
* approval.
|
|
2573
|
+
*/
|
|
2574
|
+
readonly schemaVersion: SchemaVersion;
|
|
2575
|
+
/**
|
|
2576
|
+
* The third leg of the SR-1 content address. SR-2: the validator refuses to
|
|
2577
|
+
* start on a signature or hash mismatch, so a receipt naming a schema hash
|
|
2578
|
+
* the bundle does not carry describes a grammar nobody signed.
|
|
2579
|
+
*/
|
|
2580
|
+
readonly schemaHash: Sha256Hash;
|
|
2581
|
+
/**
|
|
2582
|
+
* The adapter-stamped intent-fidelity class of the Proposal (SR-5).
|
|
2583
|
+
* DIAGNOSTIC AND AUDIT ONLY, stated normatively in section 9.2's authority
|
|
2584
|
+
* note.
|
|
2585
|
+
*
|
|
2586
|
+
* 9.3 step 7 has the Executor verify fidelity against the bundle-registered
|
|
2587
|
+
* adapter binding for `schema_id`, and 7a makes disagreement with this
|
|
2588
|
+
* transmitted value a CRITICAL ALERT rather than a preference: a
|
|
2589
|
+
* validly-signed receipt asserting `F-HIGH` for a Proposal from an
|
|
2590
|
+
* F-LOW-bound adapter means the signing substrate is emitting Decisions the
|
|
2591
|
+
* bundle does not support (T-18).
|
|
2592
|
+
*
|
|
2593
|
+
* TWO VALUES, NOT THREE, AND THE TYPE IS DEFINED ONCE. There is no
|
|
2594
|
+
* `F-MEDIUM`: this class is what actually varied between the retired Door A
|
|
2595
|
+
* / Door B binary, and it is a property of an INGRESS ADAPTER rather than a
|
|
2596
|
+
* separate architecture; it is also orthogonal to the provider class (static
|
|
2597
|
+
* entitlements against live directory sync), a conflation the document
|
|
2598
|
+
* already carries a correction for in rows 7 and 8 of its applicability
|
|
2599
|
+
* table. The type itself lives in the signed bundle's adapters.schema.json
|
|
2600
|
+
* -- the table this value is verified AGAINST (ACP-73), where it was homed
|
|
2601
|
+
* once that table existed; before that it lived in proposal.schema.json, on
|
|
2602
|
+
* the side being verified -- and is referenced here rather than repeated:
|
|
2603
|
+
* two definitions of one type is the defect this repository publishes
|
|
2604
|
+
* corrections for, and the generator halts on a duplicate `x-acp-name`
|
|
2605
|
+
* within a root and across the two.
|
|
2606
|
+
*/
|
|
2607
|
+
readonly fidelity: FidelityClass;
|
|
2608
|
+
/**
|
|
2609
|
+
* The signed bundle the Decision was computed against. 9.3 step 4: it MUST
|
|
2610
|
+
* match the bundle the Executor already trusts. Class B, and the direction
|
|
2611
|
+
* matters -- the Executor compares against its own bundle rather than
|
|
2612
|
+
* loading the one the receipt names, which would be the verifier taking its
|
|
2613
|
+
* policy from the party under verification.
|
|
2614
|
+
*/
|
|
2615
|
+
readonly policyBundleHash: Sha256Hash;
|
|
2616
|
+
/**
|
|
2617
|
+
* Epoch of that bundle. 9.3 step 4 requires it to be at least the Executor's
|
|
2618
|
+
* last-seen epoch, and the ledger records the high-water mark (CL-3), so a
|
|
2619
|
+
* receipt naming an older epoch is a rollback attempt and fails closed.
|
|
2620
|
+
*
|
|
2621
|
+
* Monotonicity is ALSO enforced one hop earlier, at the KMS, against a
|
|
2622
|
+
* durable high-water mark before it will sign at all (9.1.1 step 5, RAD-3).
|
|
2623
|
+
* Two enforcement points, neither redundant: the KMS check stops a
|
|
2624
|
+
* stale-bundle Decision being minted, the Executor check stops one being
|
|
2625
|
+
* replayed.
|
|
2626
|
+
*
|
|
2627
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2628
|
+
* type: minimum 0.
|
|
2629
|
+
*/
|
|
2630
|
+
readonly bundleEpoch: number;
|
|
2631
|
+
/**
|
|
2632
|
+
* The Context read the Decision was computed over (section 8.8
|
|
2633
|
+
* `snapshot_hash()`). Context reads are snapshot-consistent and AC-3 makes
|
|
2634
|
+
* the consumed snapshot part of the audit record, so replay is exact rather
|
|
2635
|
+
* than approximate.
|
|
2636
|
+
*
|
|
2637
|
+
* Recording the snapshot does not make Context trustworthy, and the receipt
|
|
2638
|
+
* is not where that is decided: TR-8 has the Executor recompute floor-only
|
|
2639
|
+
* risk while IGNORING every Context-Store raise, which is why a compromised
|
|
2640
|
+
* Context Store can still withhold a capability fact (DENY, safe) or raise a
|
|
2641
|
+
* tier (more attestation, safe) but cannot suppress escalation.
|
|
2642
|
+
*/
|
|
2643
|
+
readonly contextSnapshotHash: Sha256Hash;
|
|
2644
|
+
/**
|
|
2645
|
+
* The bundle rule that decided. AT-3 makes the rule identity part of what an
|
|
2646
|
+
* attester is SHOWN -- floor tiers and the bundle `rule_id`s that raised the
|
|
2647
|
+
* risk, deliberately not Context-asserted values, which is the separation
|
|
2648
|
+
* that closes A-8's display path -- and section 9.2 carries one in the
|
|
2649
|
+
* receipt for replay.
|
|
2650
|
+
*
|
|
2651
|
+
* TWO CAVEATS, BOTH GAPS RATHER THAN DESIGN. No clause pins this
|
|
2652
|
+
* identifier's syntax; the only pattern the specification attaches to a
|
|
2653
|
+
* thing called `rule_id` belongs to a DIFFERENT object -- the firewall rule
|
|
2654
|
+
* identifier inside section 6.3's `ModifyFirewallRule` payload, constrained
|
|
2655
|
+
* to `^[a-z0-9-]{1,32}$`, which the receipt example's own `RULE-002` would
|
|
2656
|
+
* fail. One name, two normative meanings, which is the CL-7 shape the
|
|
2657
|
+
* document has published a correction for. And AT-3 speaks of the rule idS
|
|
2658
|
+
* that raised the risk, plural, while the receipt carries one; nothing says
|
|
2659
|
+
* which one that is when several raised it. No pattern is asserted here
|
|
2660
|
+
* because inventing one would be a third meaning.
|
|
2661
|
+
*
|
|
2662
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2663
|
+
* type: minLength 1, maxLength 128.
|
|
2664
|
+
*/
|
|
2665
|
+
readonly ruleId: string;
|
|
2666
|
+
/**
|
|
2667
|
+
* Risk after Context raises and accumulator effects (section 8.4 step 9,
|
|
2668
|
+
* AC-4, RK-3 monotone max). DIAGNOSTIC AND AUDIT ONLY -- section 9.2's
|
|
2669
|
+
* authority note is explicit that this field is NOT authoritative for any
|
|
2670
|
+
* Executor control decision.
|
|
2671
|
+
*
|
|
2672
|
+
* It is carried because replay needs it and refused authority because X1 was
|
|
2673
|
+
* exactly this field's neighbour being believed. Effective risk can only
|
|
2674
|
+
* ever be at or above floor-only risk, and the Executor keys every
|
|
2675
|
+
* attestation requirement on the RECOMPUTED floor-only value (TR-8), so a
|
|
2676
|
+
* receipt inflating this field buys an attacker nothing and deflating it
|
|
2677
|
+
* changes nothing.
|
|
2678
|
+
*/
|
|
2679
|
+
readonly riskLevelEffective: Risk;
|
|
2680
|
+
/**
|
|
2681
|
+
* Floor-only risk: the value derived from the target resource's signed tier
|
|
2682
|
+
* floor and the bundle's risk functions, with every Context-Store raise
|
|
2683
|
+
* ignored. DIAGNOSTIC AND AUDIT ONLY.
|
|
2684
|
+
*
|
|
2685
|
+
* THIS IS THE FIELD RES-8 IS ABOUT. In v1.3.0 the Executor read it, and a
|
|
2686
|
+
* forged `risk_level_floor_only` below the bundle-derived value suppressed
|
|
2687
|
+
* the attestation requirement entirely (X1). TR-8 now has the Executor
|
|
2688
|
+
* RECOMPUTE it from the trusted bundle's floors and risk functions applied
|
|
2689
|
+
* to the independently-received canonical Proposal, and 9.3 step 7a makes
|
|
2690
|
+
* disagreement with the value transmitted here a CRITICAL ALERT, not a log
|
|
2691
|
+
* line: it means the signing substrate produced a Decision the bundle does
|
|
2692
|
+
* not support (T-18).
|
|
2693
|
+
*
|
|
2694
|
+
* The fail-safe defaults that feed the recomputation live in the bundle
|
|
2695
|
+
* schemas and are not restated here: a resource absent from `floors.json` is
|
|
2696
|
+
* T3 (RK-1), an action with no risk function is REFUSED at 8.4-3 rather than
|
|
2697
|
+
* graded HIGH, and unknown is never LOW (P-4).
|
|
2698
|
+
*/
|
|
2699
|
+
readonly riskLevelFloorOnly: Risk;
|
|
2700
|
+
/**
|
|
2701
|
+
* AB-1: the receipt's commitment to the attestations, one digest per entry,
|
|
2702
|
+
* with the entries themselves travelling alongside and never inside the
|
|
2703
|
+
* signed body. THIS ARRAY IS THE ENTIRE BINDING under version 3.
|
|
2704
|
+
*
|
|
2705
|
+
* THE PREIMAGE IS THE WHOLE ENTRY, AND THAT IS THE TRAP. Each digest is
|
|
2706
|
+
* `sha256:` over the canonical encoding of the complete entry -- `obj`,
|
|
2707
|
+
* `kind`, `attester` and `sig` together -- and MUST NOT be `attestation_id`,
|
|
2708
|
+
* nor any digest whose preimage is the Attestation Object alone.
|
|
2709
|
+
* `attestation_id` already exists, is already normative, and is the obvious
|
|
2710
|
+
* thing to reuse; it is wrong. The AT-1 field set is closed over the OBJECT
|
|
2711
|
+
* and contains no `kind`, no `attester` and no `sig`, while quorum
|
|
2712
|
+
* composition is decided from the ENTRY -- `kind` selects approval or
|
|
2713
|
+
* confirmation, `attester` supplies the identity distinctness is counted
|
|
2714
|
+
* over -- and the attester signs neither. Under version 2 containment sealed
|
|
2715
|
+
* them. Commit only to the object and an attacker on the transport path
|
|
2716
|
+
* flips one entry's `kind` from `confirmation` to `approval` while every
|
|
2717
|
+
* digest still matches. Two identifiers now exist and MUST NOT be merged:
|
|
2718
|
+
* `attestation_id` is what the ledger consumes for single use under CL-3,
|
|
2719
|
+
* this digest is what the receipt authorised byte for byte.
|
|
2720
|
+
*
|
|
2721
|
+
* WHAT `uniqueItems` DOES AND DOES NOT SAY. AB-3 forbids repeated digests
|
|
2722
|
+
* and requires the check at THIS layer rather than delegated to CL-3's
|
|
2723
|
+
* `attestation_id` claim, because CL-3 is object-scoped and needs a
|
|
2724
|
+
* Consumption Ledger -- a stateless verifier has no ledger for it to fire
|
|
2725
|
+
* in, and there AB-3 is the only defence. `uniqueItems` is the shape
|
|
2726
|
+
* statement of that rule; a duplicate MUST still be refused under the name
|
|
2727
|
+
* AB-3, not as a generic validation error. In an Executor that does hold a
|
|
2728
|
+
* ledger AB-3 is masked by CL-3 and kills no mutant, which is recorded as a
|
|
2729
|
+
* positive-path obligation rather than dressed up as a control.
|
|
2730
|
+
*
|
|
2731
|
+
* AN EMPTY ARRAY IS VALID AND `minItems` IS DELIBERATELY ABSENT. A LOW or
|
|
2732
|
+
* MEDIUM Decision carries no attestations. Requiring one entry would make
|
|
2733
|
+
* every fast-path receipt malformed, and requiring the quorum size here
|
|
2734
|
+
* would be reading the size of the quorum off the receipt -- AB-4 says
|
|
2735
|
+
* quorum MUST be counted from VERIFIED attestations and never derived from
|
|
2736
|
+
* the length of this list, which is AT-3's defect one layer down.
|
|
2737
|
+
*
|
|
2738
|
+
* ORDERING IS NOT CONSTRAINED HERE AND IS NORMATIVE: AB-2 requires strictly
|
|
2739
|
+
* ascending lexicographic order over the UTF-8 bytes of the digest strings
|
|
2740
|
+
* INCLUDING the `sha256:` prefix, because without a declared order one
|
|
2741
|
+
* quorum yields two canonical encodings of one receipt and therefore two
|
|
2742
|
+
* receipt identities -- the Z4 encoding split reached with no optional field
|
|
2743
|
+
* involved.
|
|
2744
|
+
*
|
|
2745
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2746
|
+
* type: uniqueItems.
|
|
2747
|
+
*/
|
|
2748
|
+
readonly attestationDigests: readonly Sha256Hash[];
|
|
2749
|
+
/**
|
|
2750
|
+
* The tenant the Decision was made for. 9.3 step 8: it MUST equal the tenant
|
|
2751
|
+
* of the independently-received Proposal, so a validly-signed receipt from
|
|
2752
|
+
* another tenant fails closed rather than executing across the boundary.
|
|
2753
|
+
*
|
|
2754
|
+
* The ledger is shared per tenant and its uniqueness constraints are keyed
|
|
2755
|
+
* on `(tenant_id, artifact_class, identifier)` (CL-5), so this value also
|
|
2756
|
+
* decides which single-use registry a nonce is claimed in. No pattern is
|
|
2757
|
+
* pinned because the specification pins none.
|
|
2758
|
+
*/
|
|
2759
|
+
readonly tenantId: TenantId;
|
|
2760
|
+
/**
|
|
2761
|
+
* The operator the action is attributed to. DIAGNOSTIC ONLY, and this is
|
|
2762
|
+
* stated normatively rather than as guidance (section 9.2's authority note,
|
|
2763
|
+
* Y4).
|
|
2764
|
+
*
|
|
2765
|
+
* The authoritative operator identity for approver-distinctness (AT-2) and
|
|
2766
|
+
* for the 9.3 step 9 capability recheck is `obj.operator` inside each
|
|
2767
|
+
* verified Attestation Object, which is signature-covered; 9.3 step
|
|
2768
|
+
* 7b(iii-a) requires it to be identical across every entry and requires the
|
|
2769
|
+
* identity to be taken from the verified object, NEVER resolved in favour of
|
|
2770
|
+
* this body field. A receipt whose body `operator` differs from the attested
|
|
2771
|
+
* one fails closed. Mechanized as `Y4_OperatorTamperDetected`.
|
|
2772
|
+
*
|
|
2773
|
+
* Below floor-HIGH no quorum exists and there is no attested operator to
|
|
2774
|
+
* prefer, so this field is what audit records -- which is a disclosed
|
|
2775
|
+
* asymmetry, not a second authority.
|
|
2776
|
+
*/
|
|
2777
|
+
readonly operator: OperatorId;
|
|
2778
|
+
/**
|
|
2779
|
+
* RFC 3339 instant of issuance, as section 9.2 renders it. 9.3 step 5: not
|
|
2780
|
+
* future beyond a 5 s skew allowance.
|
|
2781
|
+
*
|
|
2782
|
+
* Evaluated against NTS-disciplined wall time within the L-20 skew
|
|
2783
|
+
* allowance, never against a monotonic clock -- the two are different
|
|
2784
|
+
* quantities and DP-52 states the distinction; leg F9.1 carries the
|
|
2785
|
+
* dependency.
|
|
2786
|
+
*
|
|
2787
|
+
* TWO DISAGREEMENTS ARE RECORDED HERE RATHER THAN RESOLVED. Both current
|
|
2788
|
+
* implementations spell this field as numeric epoch seconds, because 9.3
|
|
2789
|
+
* step 5 does arithmetic on it, while the specification renders it RFC 3339
|
|
2790
|
+
* in the only place it shows a receipt; the schema follows the
|
|
2791
|
+
* specification. And RFC 3339 admits several spellings of one instant -- `Z`
|
|
2792
|
+
* against `+00:00`, fractional seconds present or absent -- so a
|
|
2793
|
+
* canonical-CBOR body containing one is not yet one encoding per instant.
|
|
2794
|
+
* For the receipt that costs a diagnostic mismatch; for the Attestation
|
|
2795
|
+
* Object, whose id is a digest of itself, it is Z4's two-ids consequence
|
|
2796
|
+
* reached through a type no clause pins, which is the WE-4 shape one layer
|
|
2797
|
+
* over.
|
|
2798
|
+
*/
|
|
2799
|
+
readonly issuedAt: Rfc3339Instant;
|
|
2800
|
+
/**
|
|
2801
|
+
* RFC 3339 instant after which the receipt is void. 9.3 step 5 refuses an
|
|
2802
|
+
* expired receipt AND enforces `expires_at - issued_at <= 120 s`.
|
|
2803
|
+
*
|
|
2804
|
+
* THE WINDOW LENGTH IS EXECUTOR-ENFORCED, WHICH IS THE WHOLE POINT OF Y2.
|
|
2805
|
+
* Both endpoints are written by the KMS, so a verifier that only checks 'not
|
|
2806
|
+
* yet expired' has delegated the validity window to the party under
|
|
2807
|
+
* verification: a compromised signer issues a receipt valid for a week and
|
|
2808
|
+
* every subsequent check still passes. Violation fails closed with a
|
|
2809
|
+
* critical alert.
|
|
2810
|
+
*
|
|
2811
|
+
* The relation between the two fields cannot be expressed in this schema, so
|
|
2812
|
+
* shape validity says nothing about it (DR-6 depends on the same bound: a
|
|
2813
|
+
* hold window at or above the L-14 ceiling would outlive receipt validity
|
|
2814
|
+
* and fails closed).
|
|
2815
|
+
*/
|
|
2816
|
+
readonly expiresAt: Rfc3339Instant;
|
|
2817
|
+
/**
|
|
2818
|
+
* The receipt nonce, claimed atomically against the Consumption Ledger at
|
|
2819
|
+
* 9.3 step 6 (CL-2 compare-and-swap; if the ledger is unavailable the
|
|
2820
|
+
* Executor fails closed; CL-4 keeps the claim for at least receipt validity
|
|
2821
|
+
* plus 24 h, because a registry that forgets a nonce before the receipt
|
|
2822
|
+
* expires is a replay window). Single use is enforced by that claim, not
|
|
2823
|
+
* asserted as a property.
|
|
2824
|
+
*
|
|
2825
|
+
* IT IS ALSO THE ORIGIN OF ACTION IDENTITY. DS-6a: the nonce of the FIRST
|
|
2826
|
+
* receipt issued for a Proposal becomes `origin_nonce`, and DS-6f has the
|
|
2827
|
+
* ledger claim the immutable `proposal_hash -> origin_nonce` binding
|
|
2828
|
+
* atomically with this claim. That binding is written once and read many
|
|
2829
|
+
* times, a rebind attempt is a critical alert, and its retention is
|
|
2830
|
+
* indefinite because an expiring origin binding reopens Z3 for any
|
|
2831
|
+
* long-lived re-drive.
|
|
2832
|
+
*
|
|
2833
|
+
* CHECKED AT 9.3 STEP 6, BEFORE THE CLAIM, IN BOTH LANGUAGES (ACP-89 -- this
|
|
2834
|
+
* paragraph recorded a gap until then, and the gap is why it is still here).
|
|
2835
|
+
* WE-4 governs this field exactly as it governs the attestation nonce -- the
|
|
2836
|
+
* `b64:` prefix is part of the value, carried and hashed as that string --
|
|
2837
|
+
* and for three releases nothing read the clause here: the value went
|
|
2838
|
+
* straight into the ledger claim in the reference and was unread in
|
|
2839
|
+
* `acp-decision`, so two spellings of one nonce claimed two ledger slots,
|
|
2840
|
+
* which is T-13 through the hole WE-4 exists to close. Step 6 now reads the
|
|
2841
|
+
* nonce BEFORE it claims: a value that is not `b64:` + RFC 4648 section 4
|
|
2842
|
+
* with padding refuses under WE-4, a well-formed value that is not 128-bit
|
|
2843
|
+
* then refuses under L-17, and only the validated string reaches the ledger
|
|
2844
|
+
* -- so a malformed nonce never consumes a slot, and it is rejected rather
|
|
2845
|
+
* than normalized for the reason the next paragraph gives.
|
|
2846
|
+
* `reference/src/acp_executor.py` and `crates/acp-decision/src/decide.rs`
|
|
2847
|
+
* carry the two checks at the same position in the same order, and the
|
|
2848
|
+
* cross-language differential compares both refusal NAMES rather than both
|
|
2849
|
+
* verdicts. WHAT THIS STILL DOES NOT REACH, because a control that
|
|
2850
|
+
* overstates itself here is the defect this paragraph was written about:
|
|
2851
|
+
* only the stateless half is checked -- the CL-2 claim and L-17's retention
|
|
2852
|
+
* half (CL-4) are the ledger's and are absent from `acp-decision` (ACP-46)
|
|
2853
|
+
* -- and nothing validates a receipt against this schema at any point
|
|
2854
|
+
* (ACP-52), so the pattern below is enforced by two implementations that
|
|
2855
|
+
* agree with it, never by a validator reading this file.
|
|
2856
|
+
*
|
|
2857
|
+
* THE TYPE IS DEFINED ONCE AND THE PATTERN IS TWO RULES THAT REFUSE UNDER
|
|
2858
|
+
* DIFFERENT NAMES. `b64:` followed by the RFC 4648 section 4 alphabet WITH
|
|
2859
|
+
* padding is WE-4, the wire type; the twenty-two characters plus `==` are
|
|
2860
|
+
* L-17's 128 bits. A verifier folding them into one check answers WE-4 for a
|
|
2861
|
+
* 64-bit nonce, which is a perfectly well-formed `b64:` value -- the wrong
|
|
2862
|
+
* clause name, and the cross-language differential compares names. The
|
|
2863
|
+
* prefix is PART OF THE VALUE: WE-4 requires it be carried, hashed and
|
|
2864
|
+
* signed as that string, and stripping it, dropping the padding or
|
|
2865
|
+
* substituting the URL-safe alphabet each produce a DIFFERENT value that
|
|
2866
|
+
* MUST be rejected rather than normalized -- normalizing makes one verifier
|
|
2867
|
+
* accept both spellings and hands the divergence to the next implementation,
|
|
2868
|
+
* where it is indistinguishable from a forgery. The type was pinned at the
|
|
2869
|
+
* root only in v1.3.18, after existing solely inside section 9.2's
|
|
2870
|
+
* diagnostic JSON with no clause saying whether the prefix counted; it was
|
|
2871
|
+
* found by writing a schema (ACP-87), which is the argument for writing
|
|
2872
|
+
* them. It is referenced from ledger_claim.schema.json rather than repeated
|
|
2873
|
+
* here, because the ledger is where a nonce is consumed and one wire type
|
|
2874
|
+
* must have one definition.
|
|
2875
|
+
*/
|
|
2876
|
+
readonly nonce: Nonce128;
|
|
2877
|
+
/**
|
|
2878
|
+
* The identifier of the audit record for this Decision, carried so a receipt
|
|
2879
|
+
* can be tied to the hash-chained audit entry (AU-1) without a search.
|
|
2880
|
+
* Section 9.2 carries it; R-1 uses the same name for the identifier returned
|
|
2881
|
+
* with a rejection, so one value class spans both outcomes.
|
|
2882
|
+
*
|
|
2883
|
+
* NO CLAUSE PINS ITS SYNTAX, and none is invented here --
|
|
2884
|
+
* `minLength`/`maxLength` bound the shape and nothing more. Note also what
|
|
2885
|
+
* this field is NOT: a receipt naming an audit id is not evidence that the
|
|
2886
|
+
* record exists or that the chain contains it. AU-3a's detection rests on
|
|
2887
|
+
* multi-party anchoring of the chain, never on an identifier the same party
|
|
2888
|
+
* transmits.
|
|
2889
|
+
*
|
|
2890
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
2891
|
+
* type: minLength 1, maxLength 128.
|
|
2892
|
+
*/
|
|
2893
|
+
readonly auditId: string;
|
|
2894
|
+
/**
|
|
2895
|
+
* The target idempotency key, `SHA-256(canonical(action_id))` where
|
|
2896
|
+
* `action_id` is the pair `(proposal_hash, origin_nonce)`. DS-6b requires it
|
|
2897
|
+
* be carried as a distinct receipt field; DS-1 requires the Executor use it
|
|
2898
|
+
* as the target's idempotency key, which is what makes exactly-once hold
|
|
2899
|
+
* ACROSS re-drives.
|
|
2900
|
+
*
|
|
2901
|
+
* ACTION IDENTITY IS NOT AUTHORIZATION IDENTITY, AND CONFLATING THEM WAS Y3.
|
|
2902
|
+
* Through v1.3.3 this key was `attestation_id`, which is fresh on every
|
|
2903
|
+
* re-drive -- so in exactly the lost-outcome case DS-3 exists to handle, the
|
|
2904
|
+
* target saw a new key for an action that may already have committed and a
|
|
2905
|
+
* floor-HIGH non-idempotent action doubled. DS-6c keeps both properties: the
|
|
2906
|
+
* key is stable, the authorization is fresh.
|
|
2907
|
+
*
|
|
2908
|
+
* IT IS TRANSMITTED AND IT IS NOT BELIEVED. The Executor derives the key
|
|
2909
|
+
* from the ledger-pinned origin binding and compares (DS-6f); a re-drive
|
|
2910
|
+
* receipt whose key differs from the ledger-derived value fails closed.
|
|
2911
|
+
* DS-6d forbids a coarser key such as `proposal_hash` alone, which would
|
|
2912
|
+
* suppress a legitimate second execution of an identical Proposal.
|
|
2913
|
+
*
|
|
2914
|
+
* Section 9.2's example does not show this field although DS-6b says it MUST
|
|
2915
|
+
* be carried, and the reference treats it as optional; both are recorded as
|
|
2916
|
+
* gaps.
|
|
2917
|
+
*/
|
|
2918
|
+
readonly idempotencyKey: Sha256Hash;
|
|
2919
|
+
/**
|
|
2920
|
+
* THE ONE OPTIONAL FIELD IN THIS SCHEMA, and the justification is owed
|
|
2921
|
+
* rather than assumed.
|
|
2922
|
+
*
|
|
2923
|
+
* WHAT IT IS. The receipt nonce of the FIRST receipt issued for this
|
|
2924
|
+
* Proposal (DS-6a). DS-6b requires a re-drive receipt to carry it. DS-6f
|
|
2925
|
+
* then makes the carried value DIAGNOSTIC ONLY: the Executor reads
|
|
2926
|
+
* `origin_nonce` from the immutable ledger binding and derives
|
|
2927
|
+
* `idempotency_key` from the value it read, and if this field is present it
|
|
2928
|
+
* MUST equal the ledger value or the Executor fails closed with a critical
|
|
2929
|
+
* alert.
|
|
2930
|
+
*
|
|
2931
|
+
* WHY IT IS OPTIONAL RATHER THAN REQUIRED. DS-6f says 'if present', in those
|
|
2932
|
+
* words, so requiring it would contradict the text; and a first receipt has
|
|
2933
|
+
* no origin other than its own nonce, so there is nothing for it to carry.
|
|
2934
|
+
* The alternative -- omitting the property entirely -- is worse under
|
|
2935
|
+
* `additionalProperties: false`, because it would make every conformant
|
|
2936
|
+
* re-drive receipt malformed.
|
|
2937
|
+
*
|
|
2938
|
+
* WHY THAT IS NOT Z4 HERE, STATED SO THE EXEMPTION IS ARGUED RATHER THAN
|
|
2939
|
+
* CLAIMED. Z4's consequence is two canonical encodings of one object
|
|
2940
|
+
* yielding two ids and therefore two ledger slots. That reaches the
|
|
2941
|
+
* Attestation Object because its ledger identity IS a digest of itself. A
|
|
2942
|
+
* receipt's ledger identity is `nonce`, carried, so present-and-absent forms
|
|
2943
|
+
* of one receipt still claim one slot. And omission cannot move the
|
|
2944
|
+
* idempotency key, because DS-6f makes the ledger the source and this field
|
|
2945
|
+
* merely a cross-check against it -- which is exactly the fix for Z3, where
|
|
2946
|
+
* DS-6b as first drafted required only that the claimed origin be A consumed
|
|
2947
|
+
* nonce. That is a MEMBERSHIP test, not a PINNING test: a compromised KMS
|
|
2948
|
+
* named any other consumed nonce, the key moved, the target could not
|
|
2949
|
+
* deduplicate, and the floor-HIGH action doubled. Fourth recurrence of the
|
|
2950
|
+
* RES-8 class, in the machinery the previous fix introduced.
|
|
2951
|
+
*
|
|
2952
|
+
* WHAT OMITTING IT DOES COST: the diagnostic cross-check disappears, so a
|
|
2953
|
+
* signing substrate that has lost track of an origin binding produces a
|
|
2954
|
+
* receipt nobody can tell apart from a correct one until the ledger is
|
|
2955
|
+
* consulted. The field set is therefore NOT closed, and the specification
|
|
2956
|
+
* has no way to say 'this is a re-drive' that would let the obligation be
|
|
2957
|
+
* expressed conditionally. Recorded as a gap.
|
|
2958
|
+
*
|
|
2959
|
+
* Same 128-bit `b64:` wire type as `nonce`, referenced from its single
|
|
2960
|
+
* definition; the WE-4 and L-17 rules written out on that field apply here
|
|
2961
|
+
* unchanged.
|
|
2962
|
+
*/
|
|
2963
|
+
readonly originNonce?: Nonce128;
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* The lenient projection of DecisionReceipt: every field optional, every enum
|
|
2967
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
2968
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
2969
|
+
*/
|
|
2970
|
+
export interface RawDecisionReceipt {
|
|
2971
|
+
readonly receiptVersion?: number;
|
|
2972
|
+
readonly alg?: string;
|
|
2973
|
+
readonly decision?: string;
|
|
2974
|
+
readonly proposalHash?: string;
|
|
2975
|
+
readonly schemaId?: string;
|
|
2976
|
+
readonly schemaVersion?: string;
|
|
2977
|
+
readonly schemaHash?: string;
|
|
2978
|
+
readonly fidelity?: string;
|
|
2979
|
+
readonly policyBundleHash?: string;
|
|
2980
|
+
readonly bundleEpoch?: number;
|
|
2981
|
+
readonly contextSnapshotHash?: string;
|
|
2982
|
+
readonly ruleId?: string;
|
|
2983
|
+
readonly riskLevelEffective?: string;
|
|
2984
|
+
readonly riskLevelFloorOnly?: string;
|
|
2985
|
+
readonly attestationDigests?: readonly string[];
|
|
2986
|
+
readonly tenantId?: string;
|
|
2987
|
+
readonly operator?: string;
|
|
2988
|
+
readonly issuedAt?: string;
|
|
2989
|
+
readonly expiresAt?: string;
|
|
2990
|
+
readonly nonce?: string;
|
|
2991
|
+
readonly auditId?: string;
|
|
2992
|
+
readonly idempotencyKey?: string;
|
|
2993
|
+
readonly originNonce?: string;
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* The out-of-band summary of a held floor-HIGH action, as it travels: a
|
|
2997
|
+
* signed Rendered Summary (`$defs/rendered_summary`, role `notification`)
|
|
2998
|
+
* beside its signature, delivered to every attester recorded in the verified
|
|
2999
|
+
* Attestation Objects and to the operator (DR-2, DR-3). Carried on legs F5.3
|
|
3000
|
+
* and F8.2 of the leg register (ZIFFER-DEPLOY-001 Annex A); F8.2 is the same
|
|
3001
|
+
* notification reaching the operator, whom DR-3 makes a required recipient.
|
|
3002
|
+
*
|
|
3003
|
+
* THIS SCHEMA IS THE ONE PERMITTED COMMON DEPENDENCY BETWEEN THE NOTIFIER
|
|
3004
|
+
* AND THE APPROVAL SERVICE, AND NOTHING ELSE IS. DR-2 requires the summary
|
|
3005
|
+
* to be rendered by a service that shares NO rendering code path with the
|
|
3006
|
+
* Attestation Presentation Service, and states the consequence in as many
|
|
3007
|
+
* words: a shared formatting library used by both is a conformance failure
|
|
3008
|
+
* EVEN WHEN it renders honestly from canonical bytes, because independence
|
|
3009
|
+
* has to hold under compromise. So a shared renderer, formatter, sanitiser,
|
|
3010
|
+
* date helper or component library between the two services is the
|
|
3011
|
+
* vulnerability, not a refactor. Two channels sourced from one renderer are
|
|
3012
|
+
* one channel wearing two hats: a single compromise of that renderer lies to
|
|
3013
|
+
* both simultaneously, and the approver's veto -- the mechanism deferred
|
|
3014
|
+
* release exists to provide -- disappears with no signature failing
|
|
3015
|
+
* anywhere. The wire format is the permitted dependency precisely because it
|
|
3016
|
+
* carries no rendering behaviour. `sim/notify/render.py` and
|
|
3017
|
+
* `sim/approve/render.py` are two files that do the same job differently on
|
|
3018
|
+
* purpose, and say so at the top.
|
|
3019
|
+
*
|
|
3020
|
+
* NONE OF THAT IS CHECKABLE HERE. DR-2's structural half is a property of
|
|
3021
|
+
* two code trees; JSON Schema constrains one message. Nothing below detects
|
|
3022
|
+
* a shared renderer, and this file must not be read as though it did.
|
|
3023
|
+
*
|
|
3024
|
+
* T-32 CLOSED HERE FOR TWO OF ITS THREE ROWS, AND THIS FILE SAYS HOW
|
|
3025
|
+
* (v1.3.25). Through v1.3.24 this object carried `source_path` and
|
|
3026
|
+
* `from_canonical` -- the notification service describing itself to the
|
|
3027
|
+
* Executor, classified T (rows 24-25 of ACP-CLASS-001) because a description
|
|
3028
|
+
* a service gives of itself is not evidence -- and an earlier revision of
|
|
3029
|
+
* this description recorded why it could be nothing more: the notification
|
|
3030
|
+
* was not among CR-1's signed structures, so there were no bytes to verify.
|
|
3031
|
+
* There are now. `obj` is a Rendered Summary signed under the `notification`
|
|
3032
|
+
* key that door_identities.json (PB-11) names in the signed bundle; the
|
|
3033
|
+
* Executor verifies the signature under that key and compares
|
|
3034
|
+
* `obj.proposal_hash` against its own canonical hash of the held Proposal
|
|
3035
|
+
* (DR-14). Both self-descriptions are WITHDRAWN, and the rows move T -> B.
|
|
3036
|
+
*
|
|
3037
|
+
* WHY THE OBJECT IS A `$defs` ENTRY HERE AND NOT ITS OWN FILE. Two producers
|
|
3038
|
+
* sign one shape -- the presentation service signs the approval summary, the
|
|
3039
|
+
* notifier signs this -- and the generator resolves a `$ref` to a `$defs`
|
|
3040
|
+
* entry only, never to a whole document (it halted on the alternative, which
|
|
3041
|
+
* is the right answer: a document is a message and a message is not a
|
|
3042
|
+
* field). The Acknowledgement carries its signed object the same way. The
|
|
3043
|
+
* approval summary has no wire schema of its own because no clause puts it
|
|
3044
|
+
* on a leg between two trust domains: it reaches the Executor from the
|
|
3045
|
+
* presentation service on the leg the Attestation Objects already travel, in
|
|
3046
|
+
* this shape.
|
|
3047
|
+
*
|
|
3048
|
+
* WHAT IS DELIBERATELY NOT A FIELD.
|
|
3049
|
+
* (a) THE RECIPIENT SET. DR-3's audience is every attester recorded in the
|
|
3050
|
+
* VERIFIED Attestation Objects plus the operator, both available to the
|
|
3051
|
+
* Executor from the receipt it has already verified -- the operator inside
|
|
3052
|
+
* the attestation signature rather than the receipt body (AT-8, Y4). A
|
|
3053
|
+
* transmitted recipient set would be the notification service certifying its
|
|
3054
|
+
* own coverage: RES-8, the shape DR-13(2) and PB-10 refused by putting
|
|
3055
|
+
* audiences in the signed bundle. Disclosed asymmetry, unchanged: DR-13(2)
|
|
3056
|
+
* puts the fast-path audience under signed policy and DR-3 does not, and
|
|
3057
|
+
* `reference/src/acp_executor.py` asks the notifier for it on this path.
|
|
3058
|
+
* (b) `delivered`. The notifier's report of whom it actually reached,
|
|
3059
|
+
* consumed at DR-8. A return value on the way back to the Executor, not a
|
|
3060
|
+
* field of this object, and the ONE T-32 row that stays T: a signed delivery
|
|
3061
|
+
* report is still the party under verification describing its own coverage,
|
|
3062
|
+
* and DR-14 states the bound rather than inventing a field that would look
|
|
3063
|
+
* like closing it.
|
|
3064
|
+
* (c) A THIRD SHAPE FOR THE DR-13 NOTICE. The register's artifact vocabulary
|
|
3065
|
+
* holds `notification` (DR-3) and `notice` (DR-13), and they are two
|
|
3066
|
+
* artifacts: the notice is committed by the Executor to its OWN durable
|
|
3067
|
+
* audit state before an IRREVERSIBLE action executes below floor-HIGH,
|
|
3068
|
+
* involves no rendering and no service, and its delivery is explicitly not a
|
|
3069
|
+
* precondition of execution. ACP-86's register names this schema for
|
|
3070
|
+
* `notice`; that is still not true of this file -- no tag discriminates the
|
|
3071
|
+
* two, and DR-13(1)'s content appears in no field here -- and the
|
|
3072
|
+
* disagreement is recorded rather than resolved, because resolving it is a
|
|
3073
|
+
* decision about the register.
|
|
3074
|
+
*
|
|
3075
|
+
* ENCODING. AT-8a pins canonical CBOR for the Attestation Object and §8 pins
|
|
3076
|
+
* JCS for the Proposal; DR-14 pins the signature as taken over the canonical
|
|
3077
|
+
* encoding of `obj` alone. The closed field sets here are Z4's reason -- an
|
|
3078
|
+
* optional field gives one object two valid encodings -- and not a rule
|
|
3079
|
+
* quoted from a clause.
|
|
3080
|
+
* Generated from notification.schema.json.
|
|
3081
|
+
*/
|
|
3082
|
+
export interface Notification {
|
|
3083
|
+
/**
|
|
3084
|
+
* The signed Rendered Summary, `role` = `notification`. Carried in its own
|
|
3085
|
+
* member rather than flattened beside the signature because the signature is
|
|
3086
|
+
* taken over THIS map alone -- the Acknowledgement's shape, for the
|
|
3087
|
+
* Acknowledgement's reason (ACK-2).
|
|
3088
|
+
*/
|
|
3089
|
+
readonly obj: RenderedSummary;
|
|
3090
|
+
/**
|
|
3091
|
+
* CR-2: a signature under a suite is a MAP from primitive name to signature
|
|
3092
|
+
* value, one entry per primitive of the suite named in `obj.alg`. A bare
|
|
3093
|
+
* scalar MUST NOT be accepted under any suite, not even a single-primitive
|
|
3094
|
+
* one -- format leniency is a downgrade in disguise. OPEN MAP, deliberately:
|
|
3095
|
+
* the suite registry lives in the signed bundle (CR-1) and CR-7 makes
|
|
3096
|
+
* migration forward-only, so a closed `{classical, pq}` object here would
|
|
3097
|
+
* hard-code one suite into the wire format. CR-3 is conjunctive and not
|
|
3098
|
+
* expressible here: every primitive of the declared suite MUST verify and
|
|
3099
|
+
* the supplied set MUST exactly match it, checked in code against `obj.alg`
|
|
3100
|
+
* resolved through the bundle. Value encoding follows
|
|
3101
|
+
* acknowledgement.schema.json's `sig`, whose description records that the
|
|
3102
|
+
* two implementations of it disagree (WE-4 base64 vs the reference's hex) --
|
|
3103
|
+
* one report, not two.
|
|
3104
|
+
*
|
|
3105
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3106
|
+
* type: values minLength 1.
|
|
3107
|
+
*/
|
|
3108
|
+
readonly sig: Readonly<Record<string, string>>;
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3111
|
+
* The lenient projection of Notification: every field optional, every enum
|
|
3112
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
3113
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
3114
|
+
*/
|
|
3115
|
+
export interface RawNotification {
|
|
3116
|
+
readonly obj?: RawRenderedSummary;
|
|
3117
|
+
readonly sig?: Readonly<Record<string, string>>;
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* The immutable `proposal_hash` to `origin_nonce` binding (CL-3, DS-6f,
|
|
3121
|
+
* T-22), claimed at the FIRST receipt for a Proposal. Unlike the three
|
|
3122
|
+
* classes above this is a BINDING and not a consumption: it is written once
|
|
3123
|
+
* and read many times, its retention is indefinite (CL-3), and a rebind
|
|
3124
|
+
* attempt is a critical alert.
|
|
3125
|
+
*
|
|
3126
|
+
* WHY IT EXISTS. DS-6b as drafted in v1.3.4 required the Executor to verify
|
|
3127
|
+
* a receipt-claimed `origin_nonce` was "recorded as consumed in the
|
|
3128
|
+
* Consumption Ledger". That is a MEMBERSHIP test: it proves the value is *a*
|
|
3129
|
+
* consumed nonce, never that it is *the* origin of this Proposal. A
|
|
3130
|
+
* compromised KMS therefore names any other consumed nonce as the origin,
|
|
3131
|
+
* the idempotency key moves, the target cannot dedup, and the floor-HIGH
|
|
3132
|
+
* action doubles -- Z3, the fourth recurrence of the RES-8 class, found
|
|
3133
|
+
* inside the fix for Y3.
|
|
3134
|
+
*
|
|
3135
|
+
* WHAT THIS CLAIM IS AND IS NOT. It PINS a value; it is not evidence that a
|
|
3136
|
+
* value was pinned. On a re-drive the Executor MUST read the origin from the
|
|
3137
|
+
* ledger and derive `idempotency_key` from what it read; a receipt-carried
|
|
3138
|
+
* `origin_nonce` is diagnostic only and must match or the Executor fails
|
|
3139
|
+
* closed with a critical alert (DS-6f). That read is a different operation
|
|
3140
|
+
* and this schema does not model it.
|
|
3141
|
+
*
|
|
3142
|
+
* CL-7 GOVERNS THE WRITE AND CANNOT BE SEEN FROM HERE. The binding MUST (i)
|
|
3143
|
+
* read from a reachable majority, (ii) fail closed if reachable replicas
|
|
3144
|
+
* disagree, never resolving a security value by majority vote, and (iii)
|
|
3145
|
+
* adopt an existing binding where one is found, writing only when none
|
|
3146
|
+
* exists. The converse ordering leaves a partitioned write permanently split
|
|
3147
|
+
* across the replica set: every later read sees two values and fails closed
|
|
3148
|
+
* forever, a durable denial of service on that Proposal produced by an
|
|
3149
|
+
* ordinary network event with no attacker. That defect was real, was found
|
|
3150
|
+
* by `partition_suite.py`, and is invisible to this document -- a claim that
|
|
3151
|
+
* validates here says nothing about the order in which the ledger read and
|
|
3152
|
+
* wrote.
|
|
3153
|
+
* Generated from ledger_claim.schema.json#/$defs/origin_binding_claim.
|
|
3154
|
+
*/
|
|
3155
|
+
export interface OriginBindingClaim {
|
|
3156
|
+
readonly tenantId: TenantId;
|
|
3157
|
+
readonly artifactClass: 'origin_binding';
|
|
3158
|
+
/**
|
|
3159
|
+
* The `proposal_hash` (DS-6f), which is the KEY half of the binding and
|
|
3160
|
+
* therefore the ledger key's `identifier` position under CL-5. It is the
|
|
3161
|
+
* canonical Proposal's hash under RFC 8785 (JCS), §9.2 -- the Proposal
|
|
3162
|
+
* canonicalizes differently from the receipt and the Attestation Object
|
|
3163
|
+
* (canonical CBOR, WE-1/WE-2/AT-8a), and mixing the two is the encoding
|
|
3164
|
+
* split at the point it does the most damage.
|
|
3165
|
+
*/
|
|
3166
|
+
readonly identifier: Sha256Hash;
|
|
3167
|
+
/**
|
|
3168
|
+
* The BOUND half: the receipt nonce of the first receipt issued for this
|
|
3169
|
+
* Proposal (DS-6f). Carried in the claim because the ledger cannot derive it
|
|
3170
|
+
* -- it is the value being pinned. It is not a second spelling of the
|
|
3171
|
+
* receipt-nonce claim's `identifier` and does not consume a slot in that
|
|
3172
|
+
* class; the two claims are distinct rows even where the value is
|
|
3173
|
+
* byte-identical, which is what `artifact_class` in the key is for.
|
|
3174
|
+
*
|
|
3175
|
+
* DS-6f requires this binding to be claimed ATOMICALLY "alongside the nonce
|
|
3176
|
+
* claim itself", and a schema of one message per class cannot express an
|
|
3177
|
+
* atomic pair. Whether the two travel as one message or two is unanswered by
|
|
3178
|
+
* the specification and is recorded as an open question against this schema;
|
|
3179
|
+
* the Python reference issues them as two separate calls four checklist
|
|
3180
|
+
* steps apart (§9.3 step 6 and step 10).
|
|
3181
|
+
*/
|
|
3182
|
+
readonly originNonce: Nonce128;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* The lenient projection of OriginBindingClaim: every field optional, every enum
|
|
3186
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
3187
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
3188
|
+
*/
|
|
3189
|
+
export interface RawOriginBindingClaim {
|
|
3190
|
+
readonly tenantId?: string;
|
|
3191
|
+
readonly artifactClass?: string;
|
|
3192
|
+
readonly identifier?: string;
|
|
3193
|
+
readonly originNonce?: string;
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* One floor-HIGH action, verified and HELD between §9.3 and execution.
|
|
3197
|
+
* Committed by the Executor as S1 of the leg register (ZIFFER-DEPLOY-001
|
|
3198
|
+
* Annex A), which carries it among the Executor-local durable commits and
|
|
3199
|
+
* not among the legs. F5.1 was its identifier while the register read it as
|
|
3200
|
+
* a leg; ACP-86 retired that id, because an Executor-internal hold crosses
|
|
3201
|
+
* no domain boundary and DP-83 makes a leg a crossing.
|
|
3202
|
+
*
|
|
3203
|
+
* S1 IS AN EXECUTOR-INTERNAL HOLD, AND THAT CHANGES WHAT THIS SCHEMA IS FOR.
|
|
3204
|
+
* Every other artifact in this directory crosses between two parties. This
|
|
3205
|
+
* one does not: the register records it as a commit whose component column
|
|
3206
|
+
* reads "Executor" and which names no crossing, so nothing here is received,
|
|
3207
|
+
* and no field is class T in the sense §14 suite 12 means it -- a value
|
|
3208
|
+
* transmitted by the party under verification. Every value below was
|
|
3209
|
+
* verified by the Executor at §9.3, recomputed by it (TR-8, RV-3), or drawn
|
|
3210
|
+
* by it (DR-11); the one exception is `notified`, and it is called out on
|
|
3211
|
+
* the field. What the schema constrains is therefore DURABLE STATE, and that
|
|
3212
|
+
* state is normative because DP-48 makes it so: an Executor MUST record
|
|
3213
|
+
* entry into `pending_release` before the hold begins, and on recovery MUST
|
|
3214
|
+
* enumerate every held action, void each with a critical alert and an audit
|
|
3215
|
+
* record, and MUST NOT resume, re-time, or release any of them. A hold that
|
|
3216
|
+
* lives only in process memory is a hold an ordinary restart erases -- along
|
|
3217
|
+
* with a repudiation (DR-4) that has already arrived.
|
|
3218
|
+
*
|
|
3219
|
+
* WHY THE HOLD EXISTS AT ALL. §9.3 establishes that a receipt is authentic,
|
|
3220
|
+
* bound, fresh and quorum-satisfying. It cannot establish that the approvers
|
|
3221
|
+
* SAW the action they approved. DR-1 therefore forbids a floor-HIGH action
|
|
3222
|
+
* from executing at verification time and holds it while a summary rendered
|
|
3223
|
+
* independently from the canonical bytes is delivered out of band (DR-2,
|
|
3224
|
+
* DR-3) and any recipient may veto (DR-4). That raises the bar from one
|
|
3225
|
+
* compromised component to two independent ones. A-8 is not closed and is
|
|
3226
|
+
* not claimed to be.
|
|
3227
|
+
*
|
|
3228
|
+
* WHAT IS BOUND TO WHAT, WHICH IS THE POINT OF THE RECORD. A release must
|
|
3229
|
+
* not be re-pointable at a different action, so the record pins three things
|
|
3230
|
+
* and not one. `proposal_hash` names the action -- and it is a NAME, not
|
|
3231
|
+
* evidence: at release the Executor re-hashes the bytes it is about to
|
|
3232
|
+
* execute and compares (9.3-3, "the Executor hashes what it will execute --
|
|
3233
|
+
* never whatever the receipt describes"). `receipt_nonce` pins the ONE
|
|
3234
|
+
* receipt whose quorum authorized this hold, because a Proposal can have
|
|
3235
|
+
* more than one live receipt (DS-3 re-drives through a NEW attestation;
|
|
3236
|
+
* DS-6a fixes the action identity across them), and a hold keyed only on the
|
|
3237
|
+
* Proposal would let a later receipt inherit a hold -- and a veto -- raised
|
|
3238
|
+
* against an earlier one. `policy_bundle_hash` and `bundle_epoch` name the
|
|
3239
|
+
* signed policy the recomputations were made against, so `reversibility`,
|
|
3240
|
+
* which decides whether silence releases, is something an auditor can
|
|
3241
|
+
* reproduce rather than an assertion they must accept.
|
|
3242
|
+
*
|
|
3243
|
+
* THE FIELD SET IS CLOSED, AND THE REASON IS NOT AT-8b's. `required` lists
|
|
3244
|
+
* every property and `additionalProperties` is false, but Z4's argument does
|
|
3245
|
+
* not carry over unchanged: nothing hashes this record, so two encodings of
|
|
3246
|
+
* it do not become two ids and two ledger slots. What does carry over is the
|
|
3247
|
+
* other half of it. An optional field is a field the release path sometimes
|
|
3248
|
+
* reads as absent, the absent branch is the one nothing exercises, and here
|
|
3249
|
+
* that branch decides whether an IRREVERSIBLE action releases with no
|
|
3250
|
+
* acknowledgement. `acknowledged_by` and `repudiated_by` are therefore
|
|
3251
|
+
* REQUIRED and MAY be empty arrays: an empty array is the honest encoding of
|
|
3252
|
+
* "nobody did", where an omitted field is the same state spelled a second
|
|
3253
|
+
* way. And DP-48's recovery path has to enumerate these records after a
|
|
3254
|
+
* restart, which it cannot do faithfully for a record carrying a field it
|
|
3255
|
+
* does not know about.
|
|
3256
|
+
*
|
|
3257
|
+
* `x-acp-absent` APPEARS NOWHERE BELOW, DELIBERATELY. An absent rule answers
|
|
3258
|
+
* "what does the SIGNED POLICY say when a lookup misses", and this record is
|
|
3259
|
+
* not policy -- `tools/codegen.py` states that as a rule for the whole wire
|
|
3260
|
+
* root, because a default supplied by the party under verification is not a
|
|
3261
|
+
* fail-safe. RV-1's absent-from-`reversibility.json` default (IRREVERSIBLE)
|
|
3262
|
+
* lives at the lookup, in `spec/schemas/bundle/reversibility.schema.json`,
|
|
3263
|
+
* which is where it has effect. Restating it here would give one fail-safe
|
|
3264
|
+
* two homes.
|
|
3265
|
+
*
|
|
3266
|
+
* WHAT IS NOT A FIELD. (a) `release_at`: it is `held_at` plus
|
|
3267
|
+
* `hold_window_seconds`, and a stored instant that can disagree with its own
|
|
3268
|
+
* inputs is a second definition of one moment; the disagreement that matters
|
|
3269
|
+
* is the early one, which is DR-1's refusal bypassed by a value rather than
|
|
3270
|
+
* by a clock. (b) `release_mode`: DR-1 says it is determined by the
|
|
3271
|
+
* recomputed reversibility class and the sampling draw, so it IS
|
|
3272
|
+
* `reversibility` and `sampled`, computed at release and never stored --
|
|
3273
|
+
* stored, one flipped field converts a DR-9 hold into a DR-7 silent release.
|
|
3274
|
+
* RV-4 does require the mode recorded, but in AUDIT, a different artifact on
|
|
3275
|
+
* a different leg (F6.1). (c) `idempotency_key`: DS-6f pins `proposal_hash`
|
|
3276
|
+
* to `origin_nonce` immutably in the Consumption Ledger and requires the
|
|
3277
|
+
* Executor to READ origin from that binding and derive the key from what it
|
|
3278
|
+
* read; a key copied in here would be read at the target call instead, which
|
|
3279
|
+
* is Z3 one layer in -- the membership-versus-pinning distinction that let a
|
|
3280
|
+
* compromised KMS move an idempotency key and double a floor-HIGH action.
|
|
3281
|
+
* (d) A Context snapshot or a stored capability decision: 9.3 step 9 is a
|
|
3282
|
+
* RE-QUERY written to close the issuance-to-execution window (T-10), a
|
|
3283
|
+
* capability read before the hold is stale by the whole window, and carrying
|
|
3284
|
+
* one would invite exactly the read that step forbids. (e) The Attestation
|
|
3285
|
+
* Objects, their signatures, and their `attestation_id`s: the quorum was
|
|
3286
|
+
* decided at 9.3 step 7b and the ids were consumed in the ledger (CL-3,
|
|
3287
|
+
* AT-5), and re-deciding it at release against a copy this record supplied
|
|
3288
|
+
* is a verification with no fresh binding. The attester IDENTITIES are kept,
|
|
3289
|
+
* because DR-3 and DR-5 need to know who must be notified and who may veto.
|
|
3290
|
+
* (f) The canonical Proposal bytes: where an Executor keeps what it is about
|
|
3291
|
+
* to execute is a storage question the specification does not answer; what
|
|
3292
|
+
* it does answer is that the hash of those bytes is recomputed and compared
|
|
3293
|
+
* (9.3-3).
|
|
3294
|
+
*
|
|
3295
|
+
* WHAT THIS SCHEMA DOES NOT CONSTRAIN. Almost everything that matters about
|
|
3296
|
+
* a hold is a relation -- between fields, between times, or between
|
|
3297
|
+
* components -- and JSON Schema expresses none of them: that the window has
|
|
3298
|
+
* elapsed (DR-1), that the receipt has not expired during it (DR-6), that
|
|
3299
|
+
* `notified` covers every attester and the operator (DR-3, DR-8), that an
|
|
3300
|
+
* acknowledgement came from a notified non-operator (DR-9, DR-5), that a
|
|
3301
|
+
* non-empty `repudiated_by` is terminal (DR-4), that `held_at` plus
|
|
3302
|
+
* `hold_window_seconds` still leaves a processing margin inside the
|
|
3303
|
+
* receipt's validity (DR-6). A record can be schema-valid and unreleasable,
|
|
3304
|
+
* and a record can be schema-valid and MUST NOT release. This document
|
|
3305
|
+
* constrains shape, and shape is not authority.
|
|
3306
|
+
*
|
|
3307
|
+
* Two limits stated rather than left to be assumed. DR-2's render-path
|
|
3308
|
+
* distinctness is a property of two CODE PATHS and cannot be a property of
|
|
3309
|
+
* any object; nothing here can carry it, and the notifier self-certifying
|
|
3310
|
+
* its own independence and coverage is the OPEN finding T-32 in
|
|
3311
|
+
* ACP-CLASS-001. DR-11's requirement that the sampling draw come from a
|
|
3312
|
+
* CSPRNG and be uninfluenceable by the requester is likewise
|
|
3313
|
+
* unrepresentable: `sampled` records the OUTCOME of a draw and can say
|
|
3314
|
+
* nothing about its source, so an implementation whose draw an attacker
|
|
3315
|
+
* grinds against produces records that validate perfectly.
|
|
3316
|
+
*
|
|
3317
|
+
* Finally, this record carries no signature and no integrity envelope,
|
|
3318
|
+
* because the specification gives it none: its integrity is the Executor's
|
|
3319
|
+
* own durable store. The closed field set above is a discipline about what
|
|
3320
|
+
* the release path may read. It is not a defence against an Executor that
|
|
3321
|
+
* writes whatever it likes.
|
|
3322
|
+
* Generated from pending_release.schema.json.
|
|
3323
|
+
*/
|
|
3324
|
+
export interface PendingRelease {
|
|
3325
|
+
/**
|
|
3326
|
+
* The canonical Proposal (JCS, WE-1) this action was authorized for. It is
|
|
3327
|
+
* the name of the held action and not evidence of it: 9.3-3 requires the
|
|
3328
|
+
* Executor to hash what it will execute and compare, and the hold is exactly
|
|
3329
|
+
* the interval during which the thing to be executed and the thing that was
|
|
3330
|
+
* approved can drift apart. Under DS-6f this is also the key of the
|
|
3331
|
+
* immutable `proposal_hash` to `origin_nonce` ledger binding, which is where
|
|
3332
|
+
* the idempotency key comes from at the target call -- not from anything
|
|
3333
|
+
* recorded here.
|
|
3334
|
+
*/
|
|
3335
|
+
readonly proposalHash: Sha256Hash;
|
|
3336
|
+
/**
|
|
3337
|
+
* The `nonce` of the ONE receipt whose quorum authorized this hold, claimed
|
|
3338
|
+
* in the Consumption Ledger at 9.3 step 6.
|
|
3339
|
+
*
|
|
3340
|
+
* WHY THE RECORD NEEDS IT AND `proposal_hash` IS NOT ENOUGH. DS-3 re-drives
|
|
3341
|
+
* an `indeterminate` outcome through a NEW attestation and therefore a new
|
|
3342
|
+
* receipt, and DS-6a fixes the action identity across re-drives, so one
|
|
3343
|
+
* Proposal can have several receipts alive inside a retention window. A hold
|
|
3344
|
+
* identified only by its Proposal is a hold any of them can be released
|
|
3345
|
+
* against -- including one whose attesters were never notified under DR-3,
|
|
3346
|
+
* which detaches the DR-4 veto from the authorization it was raised against.
|
|
3347
|
+
*
|
|
3348
|
+
* THE TYPE IS TWO RULES AND THEY REFUSE UNDER DIFFERENT NAMES. `b64:` plus
|
|
3349
|
+
* the RFC 4648 §4 alphabet WITH padding is WE-4, the wire type, and the
|
|
3350
|
+
* prefix is part of the value: it is carried, hashed and signed as that
|
|
3351
|
+
* string, and stripping it, dropping the padding, or substituting the
|
|
3352
|
+
* URL-safe alphabet each produce a different value and MUST be rejected
|
|
3353
|
+
* rather than normalized. The twenty-two characters are L-17's 128 bits. A
|
|
3354
|
+
* verifier folding the two into one check answers `WE-4` for a 64-bit nonce,
|
|
3355
|
+
* which is a perfectly well-formed `b64:` value -- the wrong clause name,
|
|
3356
|
+
* and the cross-language differential compares names.
|
|
3357
|
+
* `tools/nonce-type-vectors.json` pins that pair for the ATTESTATION nonce
|
|
3358
|
+
* across three implementations after they had already diverged for a release
|
|
3359
|
+
* with every gate green; the receipt nonce has the same two rules and no
|
|
3360
|
+
* such corpus.
|
|
3361
|
+
*/
|
|
3362
|
+
readonly receiptNonce: Nonce128;
|
|
3363
|
+
/**
|
|
3364
|
+
* The verified receipt's `expires_at`, copied at 9.3 step 5 so DR-6 can be
|
|
3365
|
+
* evaluated at release without re-reading the receipt. DR-6: release MUST
|
|
3366
|
+
* fail closed if the receipt expired during the window, because a hold that
|
|
3367
|
+
* outlives its authorization executes on an expired receipt, and L-14's 120
|
|
3368
|
+
* s ceiling exists to bound exactly that. Note what this value's authority
|
|
3369
|
+
* rests on and what it does not: step 5 enforced the window LENGTH itself
|
|
3370
|
+
* rather than reading it from the KMS-written value (Y2), and nothing
|
|
3371
|
+
* re-derives it here -- this is a copy of a checked value, kept because the
|
|
3372
|
+
* checker and the release step are separated by the hold.
|
|
3373
|
+
*/
|
|
3374
|
+
readonly receiptExpiresAt: Rfc3339Instant;
|
|
3375
|
+
/**
|
|
3376
|
+
* The signed bundle established at 9.3 step 4, under which the values below
|
|
3377
|
+
* were recomputed. It authorizes nothing at release -- no clause reads
|
|
3378
|
+
* policy through this hash -- and it is here because a recomputation with no
|
|
3379
|
+
* named input is not reproducible: RV-4 requires the recomputed
|
|
3380
|
+
* reversibility class recorded in audit, and an auditor who cannot name the
|
|
3381
|
+
* bundle cannot check the class or the release mode it selected.
|
|
3382
|
+
*/
|
|
3383
|
+
readonly policyBundleHash: Sha256Hash;
|
|
3384
|
+
/**
|
|
3385
|
+
* Epoch of that bundle (9.3 step 4, which requires it be at least the
|
|
3386
|
+
* Executor's last-seen epoch). Monotonicity is enforced at the KMS against a
|
|
3387
|
+
* durable high-water mark (RAD-3) and, across a restart, against the
|
|
3388
|
+
* Executor's own recovered mark (DP-47) -- never from this value. It is
|
|
3389
|
+
* recorded so that a hold raised under epoch N is still visibly a hold
|
|
3390
|
+
* raised under epoch N after a rotation, which is the only way to read a
|
|
3391
|
+
* release decision made under a bundle that is no longer current.
|
|
3392
|
+
*
|
|
3393
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3394
|
+
* type: minimum 0.
|
|
3395
|
+
*/
|
|
3396
|
+
readonly bundleEpoch: number;
|
|
3397
|
+
/**
|
|
3398
|
+
* The tenant scope verified at 9.3 step 8, where the receipt's tenant must
|
|
3399
|
+
* match the tenant scope of the credentials about to be used. Three
|
|
3400
|
+
* obligations need it at release rather than at verification: DR-12 counts
|
|
3401
|
+
* silent releases in a PER-TENANT counter, DR-13 counts its below-HIGH
|
|
3402
|
+
* notices as a separate class in the same discipline, and AU-2 makes
|
|
3403
|
+
* `(tenant_id, seq)` unique and gapless in the audit chain. A release that
|
|
3404
|
+
* cannot name its tenant cannot be counted and cannot be anchored.
|
|
3405
|
+
*/
|
|
3406
|
+
readonly tenantId: TenantId;
|
|
3407
|
+
/**
|
|
3408
|
+
* The operator identity taken from the VERIFIED Attestation Objects at 9.3
|
|
3409
|
+
* step 7b(iii-a) -- never from the receipt body's `operator`, which is
|
|
3410
|
+
* diagnostic only (Y4). Three rules read it during the hold. DR-3 makes the
|
|
3411
|
+
* operator a required notification recipient alongside every attester. DR-9
|
|
3412
|
+
* refuses an acknowledgement from the operator: the party who initiated the
|
|
3413
|
+
* action carries no independent information about whether the displayed
|
|
3414
|
+
* summary was honest, which is AT-2's approver-distinctness restated at the
|
|
3415
|
+
* release step. And 9.3 step 9 rechecks capability on this identity, which
|
|
3416
|
+
* is why it must be the signature-covered one.
|
|
3417
|
+
*/
|
|
3418
|
+
readonly operator: OperatorId;
|
|
3419
|
+
/**
|
|
3420
|
+
* Every attester recorded in the verified Attestation Objects. DR-3 requires
|
|
3421
|
+
* the notification to reach each of them and the operator, over a channel
|
|
3422
|
+
* distinct from the one that obtained approval; DR-5 accepts a repudiation
|
|
3423
|
+
* or an acknowledgement only from a party recorded as notified.
|
|
3424
|
+
*
|
|
3425
|
+
* IT IS KEPT AS THE RECOMPUTED HALF OF THAT PAIR. The set of parties who
|
|
3426
|
+
* MUST be notified is derived from signatures the Executor itself verified,
|
|
3427
|
+
* so DR-8's completeness test can compare `notified` against this rather
|
|
3428
|
+
* than asking the notification service who its audience should have been.
|
|
3429
|
+
* Asking the notifier is the shape of T-32, still OPEN in ACP-CLASS-001: a
|
|
3430
|
+
* service that selects and then certifies its own coverage is verifying
|
|
3431
|
+
* itself.
|
|
3432
|
+
*
|
|
3433
|
+
* `minItems` is 2 because L-16 puts the floor-HIGH quorum at two distinct
|
|
3434
|
+
* attesters, each distinct from the operator, and a floor-HIGH receipt with
|
|
3435
|
+
* no attestations fails closed at INV-1-HIGH before any hold exists.
|
|
3436
|
+
* `uniqueItems` compares identity STRINGS, which is deliberately the weaker
|
|
3437
|
+
* check: AT-2 distinctness is resolved over registry KEYS and not over names
|
|
3438
|
+
* (PB-7, where changing a `role` string let one key holder satisfy a k=2
|
|
3439
|
+
* quorum alone), and that resolution happened at 9.3 step 7b(vi). This
|
|
3440
|
+
* constraint restates none of it.
|
|
3441
|
+
*
|
|
3442
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3443
|
+
* type: minItems 2, uniqueItems; items minLength 1, maxLength 128.
|
|
3444
|
+
*/
|
|
3445
|
+
readonly attesters: readonly string[];
|
|
3446
|
+
/**
|
|
3447
|
+
* The recomputed floor-only risk that put this action on the deferred path.
|
|
3448
|
+
* The `const` is DR-1's scope made checkable: DR-1 defers an action whose
|
|
3449
|
+
* RECOMPUTED floor-only risk is HIGH, and DR-13 states in as many words that
|
|
3450
|
+
* DR-1 through DR-12 govern that path only -- an irreversible action below
|
|
3451
|
+
* HIGH takes the notice path instead, which is a different artifact on a
|
|
3452
|
+
* different path -- the Executor-local commit S2, whose leg id F5.6 ACP-86
|
|
3453
|
+
* retired for the same reason as F5.1's. A `pending_release` record graded
|
|
3454
|
+
* anything else is a record that should not exist.
|
|
3455
|
+
*
|
|
3456
|
+
* IT IS HERE TO BE REFUSED ON, NOT RELIED ON. TR-8 admits one source for a
|
|
3457
|
+
* risk grade: recomputation from the signed bundle's floors and risk
|
|
3458
|
+
* functions applied to the independently received canonical Proposal,
|
|
3459
|
+
* ignoring Context-Store raises and ignoring any transmitted value. Nothing
|
|
3460
|
+
* at release may read this field as the grade. Reading a stored grade
|
|
3461
|
+
* instead of recomputing is X1 -- a forged `risk_level_floor_only`
|
|
3462
|
+
* suppressing attestation -- with the storage moved inside the verifier.
|
|
3463
|
+
*/
|
|
3464
|
+
readonly floorOnlyRisk: Risk;
|
|
3465
|
+
/**
|
|
3466
|
+
* The class RECOMPUTED at verification from the trusted bundle and the
|
|
3467
|
+
* Proposal's `task_type` (RV-3), never read from the receipt: a
|
|
3468
|
+
* receipt-asserted `REVERSIBLE` would suppress the DR-9 acknowledgement
|
|
3469
|
+
* requirement exactly as v1.3.0's forged `risk_level_floor_only` suppressed
|
|
3470
|
+
* attestation, same defect class and a different field. RV-2 puts the class
|
|
3471
|
+
* in the signed bundle only -- no Context raise, no runtime override, no
|
|
3472
|
+
* receipt field of record -- and makes downgrading one as dangerous a policy
|
|
3473
|
+
* operation as lowering a tier floor (RK-5).
|
|
3474
|
+
*
|
|
3475
|
+
* With `sampled` it decides the release mode (DR-1), and the two outcomes
|
|
3476
|
+
* are not variations on one control. `IRREVERSIBLE` requires positive
|
|
3477
|
+
* acknowledgement from a notified non-operator and TIMES OUT CLOSED (DR-9).
|
|
3478
|
+
* `REVERSIBLE` releases on silence, and only there, because a control whose
|
|
3479
|
+
* default outcome equals its approved outcome teaches its own users to
|
|
3480
|
+
* ignore it and decays into a log entry while the dashboard still reports
|
|
3481
|
+
* that human oversight exists (DR-7).
|
|
3482
|
+
*
|
|
3483
|
+
* RV-1's absent-from-`reversibility.json` default is IRREVERSIBLE, and it
|
|
3484
|
+
* belongs to the LOOKUP, not to this field: it is carried by
|
|
3485
|
+
* `spec/schemas/bundle/reversibility.schema.json`'s `x-acp-absent` and
|
|
3486
|
+
* generated into the table's accessor. This field is required and has no
|
|
3487
|
+
* default.
|
|
3488
|
+
*
|
|
3489
|
+
* IT IS THE BUNDLE'S TYPE, NOT A SECOND ONE. This field `$ref`s
|
|
3490
|
+
* `spec/schemas/bundle/reversibility.schema.json#/$defs/reversibility`
|
|
3491
|
+
* across roots. It was declared twice until ACP-78 M1 --
|
|
3492
|
+
* `ReversibilityClass` here beside the bundle's `Reversibility` -- on the
|
|
3493
|
+
* argument that signed policy and a recorded value are never interchangeable
|
|
3494
|
+
* (RES-8) and the module a reader holds should say which they have. That
|
|
3495
|
+
* argument is right about the VALUE and wrong about the TYPE: what carries
|
|
3496
|
+
* the provenance is the object the value sits in, a signed
|
|
3497
|
+
* `reversibility.json` against a transmitted record, and a two-case
|
|
3498
|
+
* vocabulary duplicated is two definitions of one object. RV-3 also has to
|
|
3499
|
+
* COMPARE the recomputed class against the table it came from, and a
|
|
3500
|
+
* comparison between two enums that merely look alike is the CR-4 defect one
|
|
3501
|
+
* artifact down.
|
|
3502
|
+
*/
|
|
3503
|
+
readonly reversibility: Reversibility;
|
|
3504
|
+
/**
|
|
3505
|
+
* The DR-10 confirmation-sampling draw for this action. A bundle-configured
|
|
3506
|
+
* fraction of REVERSIBLE floor-HIGH actions (L-29, default 10%) inherits
|
|
3507
|
+
* DR-9's acknowledgement duty, for a reason about people rather than
|
|
3508
|
+
* cryptography: if recipients learn that reversible actions never require a
|
|
3509
|
+
* response they stop reading those notifications too, and the channel decays
|
|
3510
|
+
* for the class that is most of the traffic.
|
|
3511
|
+
*
|
|
3512
|
+
* DR-11 governs the draw and none of it is visible here. It MUST come from a
|
|
3513
|
+
* cryptographically secure source at the Executor or an equivalently
|
|
3514
|
+
* isolated component; it MUST NOT be derived from the Proposal, the operator
|
|
3515
|
+
* identity, or any requester-supplied value, because a derived draw is one
|
|
3516
|
+
* an attacker grinds inputs against until the action falls outside the
|
|
3517
|
+
* sample; and it MUST be recorded so the realised rate is verifiable after
|
|
3518
|
+
* the fact. This field is that record, and it is the OUTCOME only.
|
|
3519
|
+
*
|
|
3520
|
+
* Because it is REQUIRED, the draw is made no later than the durable commit
|
|
3521
|
+
* -- earlier than DR-10 demands, and out of reach of anything that happens
|
|
3522
|
+
* during the hold. DR-10 scopes sampling to REVERSIBLE actions and the
|
|
3523
|
+
* specification does not say what the value means for an IRREVERSIBLE one,
|
|
3524
|
+
* where DR-9 already requires acknowledgement; there both `true` and `false`
|
|
3525
|
+
* describe the same release mode.
|
|
3526
|
+
*/
|
|
3527
|
+
readonly sampled: boolean;
|
|
3528
|
+
/**
|
|
3529
|
+
* RFC 3339 instant at which the action entered `pending_release` and the
|
|
3530
|
+
* window started (DR-1). DP-48 makes it the moment the DURABLE record must
|
|
3531
|
+
* already exist by -- "before the hold begins" is the whole of that clause's
|
|
3532
|
+
* ordering, and it is AU-7's anchor-before-release rule applied to the
|
|
3533
|
+
* Executor's own state, for the same reason: a record written after the fact
|
|
3534
|
+
* can be suppressed by whatever the action enabled.
|
|
3535
|
+
*
|
|
3536
|
+
* Evaluated against NTS-disciplined wall time within the L-20 skew allowance
|
|
3537
|
+
* of five seconds, never against a monotonic clock. Leg F9.1 carries that
|
|
3538
|
+
* dependency and DP-52 states the distinction. It matters more here than on
|
|
3539
|
+
* a receipt: DR-6's margin between the end of the hold and the expiry of the
|
|
3540
|
+
* receipt is measured across two components' clocks.
|
|
3541
|
+
*/
|
|
3542
|
+
readonly heldAt: Rfc3339Instant;
|
|
3543
|
+
/**
|
|
3544
|
+
* The window this action must survive before it may release, read from the
|
|
3545
|
+
* SIGNED BUNDLE in force at 9.3 step 4 (DR-1; L-28: default 60 s, minimum 30
|
|
3546
|
+
* s). Release before it has elapsed MUST fail closed.
|
|
3547
|
+
*
|
|
3548
|
+
* THE UPPER BOUND IS A REFUSAL, NOT A STYLE CHOICE. L-14 caps receipt
|
|
3549
|
+
* validity at 120 s, so DR-6 makes a window at or above that ceiling an
|
|
3550
|
+
* UNSATISFIABLE configuration -- no legal receipt can outlive it -- which
|
|
3551
|
+
* MUST be rejected rather than silently never releasing. Testing found the
|
|
3552
|
+
* interaction directly: a 60 s hold inside a 60 s receipt window expires
|
|
3553
|
+
* mid-hold and never releases the honest path. `maximum` is inclusive and
|
|
3554
|
+
* this repository's generator carries no exclusive bound, so 119 is
|
|
3555
|
+
* "strictly below the ceiling" written in the only keyword available. It is
|
|
3556
|
+
* the loosest value the specification permits and emphatically not a safe
|
|
3557
|
+
* one: DR-6's actual requirement is `hold_window + processing margin <
|
|
3558
|
+
* receipt validity`, the margin is a property of the deployment, and no
|
|
3559
|
+
* schema can check it.
|
|
3560
|
+
*
|
|
3561
|
+
* Two choices here are this schema's and not the specification's, and both
|
|
3562
|
+
* are recorded rather than hidden. L-28 states the window in seconds but
|
|
3563
|
+
* nowhere states its granularity; integer seconds is a decision. And pinning
|
|
3564
|
+
* the window IN the record rather than re-reading it from the bundle at
|
|
3565
|
+
* release is a decision too -- it stops a rotation during the hold from
|
|
3566
|
+
* retroactively lengthening or shortening a window the attesters were
|
|
3567
|
+
* notified about -- on a question §9.6 does not answer.
|
|
3568
|
+
*
|
|
3569
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3570
|
+
* type: minimum 30, maximum 119.
|
|
3571
|
+
*/
|
|
3572
|
+
readonly holdWindowSeconds: number;
|
|
3573
|
+
/**
|
|
3574
|
+
* The parties the notification service reported delivering to. DR-3 requires
|
|
3575
|
+
* that to be every attester in the verified Attestation Objects and the
|
|
3576
|
+
* operator, over a channel distinct from the one used to obtain approval.
|
|
3577
|
+
* Two clauses then read this set: DR-8, where an unreachable recipient or
|
|
3578
|
+
* any failed delivery means the action MUST NOT release, because executing
|
|
3579
|
+
* while the detection channel is absent is executing unwatched; and DR-5,
|
|
3580
|
+
* where an acknowledgement or a repudiation is accepted ONLY from a party
|
|
3581
|
+
* recorded here, or a compromised presentation service suppresses the veto
|
|
3582
|
+
* the mechanism exists to enable.
|
|
3583
|
+
*
|
|
3584
|
+
* THIS IS THE ONE VALUE IN THE RECORD THE EXECUTOR NEITHER VERIFIED NOR
|
|
3585
|
+
* RECOMPUTED. It is the notifier's claim about its own coverage, which is
|
|
3586
|
+
* RES-8's shape and the OPEN finding T-32 in ACP-CLASS-001, where
|
|
3587
|
+
* `delivered` is classified T against a disclosed residual. `attesters` sits
|
|
3588
|
+
* beside it so DR-8's completeness test compares this claim against a set
|
|
3589
|
+
* derived from verified signatures instead of against the notifier's own
|
|
3590
|
+
* answer about who its audience should have been.
|
|
3591
|
+
*
|
|
3592
|
+
* MAY BE EMPTY, and the empty case is not slack. DP-48 requires the durable
|
|
3593
|
+
* record to exist BEFORE the hold begins while DR-3's delivery happens
|
|
3594
|
+
* DURING it, so a committed, not-yet-delivered record is a state the
|
|
3595
|
+
* specification's own ordering produces. What may be done with such a record
|
|
3596
|
+
* -- how long it may sit undelivered, whether an undelivered hold expires or
|
|
3597
|
+
* is voided -- §9.6 does not say.
|
|
3598
|
+
*
|
|
3599
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3600
|
+
* type: uniqueItems; items minLength 1, maxLength 128.
|
|
3601
|
+
*/
|
|
3602
|
+
readonly notified: readonly string[];
|
|
3603
|
+
/**
|
|
3604
|
+
* Notified parties that positively acknowledged during the window. Where the
|
|
3605
|
+
* recomputed reversibility class is IRREVERSIBLE, DR-9 requires at least one
|
|
3606
|
+
* such acknowledgement before release and TIMES OUT CLOSED -- a timed-out
|
|
3607
|
+
* action needs a fresh quorum, never a replay. DR-10 gives sampled
|
|
3608
|
+
* REVERSIBLE actions the same duty. The acknowledgement MUST NOT come from
|
|
3609
|
+
* the operator (DR-9), which is why `operator` is carried in this record
|
|
3610
|
+
* rather than looked up later.
|
|
3611
|
+
*
|
|
3612
|
+
* Empty is the ordinary state on the DR-7 silent path, and it is not free:
|
|
3613
|
+
* DR-12 requires every release with an empty set here to increment a
|
|
3614
|
+
* per-tenant counter recorded in audit, so that silence is a MEASURABLE DEBT
|
|
3615
|
+
* rather than an invisible acknowledgement, and deployments SHOULD alert on
|
|
3616
|
+
* the rate rather than merely record it.
|
|
3617
|
+
*
|
|
3618
|
+
* WHAT AN IDENTITY IN THIS ARRAY IS NOT. The reference-level ACK-1..ACK-6
|
|
3619
|
+
* rules make each acknowledgement a signed, closed-schema, single-use object
|
|
3620
|
+
* bound to `proposal_hash` and verified against a registered key (leg F5.4).
|
|
3621
|
+
* This field records only the identities that survived those checks. An
|
|
3622
|
+
* identity is not evidence of the object it came from, and nothing
|
|
3623
|
+
* downstream may treat it as one.
|
|
3624
|
+
*
|
|
3625
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3626
|
+
* type: uniqueItems; items minLength 1, maxLength 128.
|
|
3627
|
+
*/
|
|
3628
|
+
readonly acknowledgedBy: readonly string[];
|
|
3629
|
+
/**
|
|
3630
|
+
* Notified parties that repudiated during the window (DR-4, DR-5). Any
|
|
3631
|
+
* notified party MAY repudiate; repudiation MUST fail closed with a critical
|
|
3632
|
+
* alert; consumed attestations remain consumed, so a repudiated action
|
|
3633
|
+
* requires a fresh quorum and never a replay. A non-empty array is therefore
|
|
3634
|
+
* TERMINAL for this record: no later acknowledgement and no elapsed window
|
|
3635
|
+
* releases it.
|
|
3636
|
+
*
|
|
3637
|
+
* An array rather than a nullable identity, because `null` and an omitted
|
|
3638
|
+
* field are one state spelled two ways -- Z4's argument at the record layer,
|
|
3639
|
+
* where the cost is an unexercised branch in the release path rather than a
|
|
3640
|
+
* second ledger slot, and where the branch decides whether a veto is seen.
|
|
3641
|
+
* The empty array means nobody repudiated, and it means it in exactly one
|
|
3642
|
+
* way.
|
|
3643
|
+
*
|
|
3644
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3645
|
+
* type: uniqueItems; items minLength 1, maxLength 128.
|
|
3646
|
+
*/
|
|
3647
|
+
readonly repudiatedBy: readonly string[];
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* The lenient projection of PendingRelease: every field optional, every enum
|
|
3651
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
3652
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
3653
|
+
*/
|
|
3654
|
+
export interface RawPendingRelease {
|
|
3655
|
+
readonly proposalHash?: string;
|
|
3656
|
+
readonly receiptNonce?: string;
|
|
3657
|
+
readonly receiptExpiresAt?: string;
|
|
3658
|
+
readonly policyBundleHash?: string;
|
|
3659
|
+
readonly bundleEpoch?: number;
|
|
3660
|
+
readonly tenantId?: string;
|
|
3661
|
+
readonly operator?: string;
|
|
3662
|
+
readonly attesters?: readonly string[];
|
|
3663
|
+
readonly floorOnlyRisk?: string;
|
|
3664
|
+
readonly reversibility?: string;
|
|
3665
|
+
readonly sampled?: boolean;
|
|
3666
|
+
readonly heldAt?: string;
|
|
3667
|
+
readonly holdWindowSeconds?: number;
|
|
3668
|
+
readonly notified?: readonly string[];
|
|
3669
|
+
readonly acknowledgedBy?: readonly string[];
|
|
3670
|
+
readonly repudiatedBy?: readonly string[];
|
|
3671
|
+
}
|
|
3672
|
+
/**
|
|
3673
|
+
* The typed candidate operation the Policy Engine decides over (§3), and the
|
|
3674
|
+
* hash preimage every other artifact in this system binds to. Carried on
|
|
3675
|
+
* legs F1.3, F4.1, F4.2 and F5.2 of the leg register (ZIFFER-DEPLOY-001
|
|
3676
|
+
* Annex A).
|
|
3677
|
+
*
|
|
3678
|
+
* ONE SCHEMA, NOT TWO, AND THE CANONICAL FLAG IS AN ENCODING RULE RATHER
|
|
3679
|
+
* THAN A PROPERTY. The closed artifact vocabulary carries two names for this
|
|
3680
|
+
* object -- `Proposal` (§3) and `canonical Proposal` (B-1a) -- and they are
|
|
3681
|
+
* one field set. `canonical` names an ENCODING of these bytes, not a second
|
|
3682
|
+
* artifact with a second shape: B-1a requires the Proposal reaching the
|
|
3683
|
+
* Executor to be `canonicalized identically before hashing`, and identical
|
|
3684
|
+
* canonicalization of two different field sets is not a thing that can be
|
|
3685
|
+
* asked for. A `canonical_proposal.schema.json` beside this file would put
|
|
3686
|
+
* two definitions on one object, which is the defect this repository has
|
|
3687
|
+
* published corrections for three times. The flag is therefore stated once,
|
|
3688
|
+
* here, in the ENCODING paragraph below -- and NOT as a property. A field
|
|
3689
|
+
* asserting `I am canonical` would be the party under verification
|
|
3690
|
+
* certifying its own encoding; B-1a's obligation is discharged by the
|
|
3691
|
+
* receiver hashing what it actually received (9.3-3), never by reading a
|
|
3692
|
+
* claim about it.
|
|
3693
|
+
*
|
|
3694
|
+
* ENCODING: JCS, AND IT MUST NOT BE MIXED WITH THE CBOR RULE. §9.2 states
|
|
3695
|
+
* the split in one sentence -- RFC 8785 (JCS) for the Proposal, canonical
|
|
3696
|
+
* CBOR (RFC 8949 §4.2) for the receipt and the Attestation Object
|
|
3697
|
+
* (WE-1/WE-2, AT-8a). AT-8a writes the second half as a prohibition (`JCS
|
|
3698
|
+
* MUST NOT be used for the Attestation Object`) and this schema writes the
|
|
3699
|
+
* first half the same way: canonical CBOR MUST NOT be used for the Proposal.
|
|
3700
|
+
* Mixing them is the encoding split at the point it does the most damage,
|
|
3701
|
+
* because `proposal_hash` is not one artifact's identifier -- it is the
|
|
3702
|
+
* binding EVERY other artifact carries. AT-1 puts it in the object the
|
|
3703
|
+
* humans sign; 9.3-3 makes the Executor recompute it over what it will
|
|
3704
|
+
* actually execute; DS-6a builds `action_id` from it and DS-6f pins an
|
|
3705
|
+
* IMMUTABLE `proposal_hash -> origin_nonce` binding in the Consumption
|
|
3706
|
+
* Ledger, retained INDEFINITELY because an expiring one would reopen Z3
|
|
3707
|
+
* (CL-3, CL-4); DR-13 writes it into the notice. Two encoders therefore do
|
|
3708
|
+
* not merely disagree about a digest. They give ONE action two identities.
|
|
3709
|
+
* The quorum the humans raised binds to a hash the Executor never computes,
|
|
3710
|
+
* so 9.3-3 fails closed and at the verifier that is indistinguishable from a
|
|
3711
|
+
* forgery -- the sentence WE-4's rationale already had to write once, for a
|
|
3712
|
+
* type nobody had written down. And a re-drive re-encoded by the second
|
|
3713
|
+
* encoder presents a `proposal_hash` DS-6f holds no binding for, so the
|
|
3714
|
+
* recovery path DS-3 mandates fails closed on an authorized action; an
|
|
3715
|
+
* implementation that instead BINDS the new hash has silently given one
|
|
3716
|
+
* action two action identities and two DS-6b idempotency keys, which is the
|
|
3717
|
+
* doubled trade DS-4 names as the worst failure available. Y5.2/AT-8a is
|
|
3718
|
+
* that hazard one artifact over, against a binding kept for 24 hours; this
|
|
3719
|
+
* one is kept forever.
|
|
3720
|
+
*
|
|
3721
|
+
* THE FIELD SET IS PR-1's (v1.3.27), AND THIS SCHEMA IS ITS RENDERING.
|
|
3722
|
+
* Through v1.3.26 ZIFFER-SPEC-001 STATED NO FIELD SET FOR THE PROPOSAL: §3
|
|
3723
|
+
* defined it in one sentence, SR-5 required the `(schema_id, schema_version,
|
|
3724
|
+
* schema_hash)` triple and the fidelity class, B-6 forbade the client to set
|
|
3725
|
+
* the stamp, V-11 spoke of an `envelope` and its `payload` and put
|
|
3726
|
+
* `task_type` inside the payload -- and `reference/src/acp_executor.py` read
|
|
3727
|
+
* a THIRD shape, flat, with `tenant_id`, `targets`, `params` and `cidrs` at
|
|
3728
|
+
* top level and no `schema_version`, `schema_hash` or `fidelity` at all.
|
|
3729
|
+
* Three shapes, one artifact, and `proposal_hash` was a hash over whichever
|
|
3730
|
+
* one an implementer chose. This file reported that choosing between them
|
|
3731
|
+
* was a normative act and refused to make it. v1.3.27 made it, after the
|
|
3732
|
+
* first end-to-end build (M7) put the two shapes on one wire: the door
|
|
3733
|
+
* emitted this envelope, the verifier graded the flat object, §9.3 step 8
|
|
3734
|
+
* looked for a `tenant_id` the envelope could not carry, and neither side
|
|
3735
|
+
* could be called wrong. PR-1 fixes the envelope -- the triple, the fidelity
|
|
3736
|
+
* class, the tenant the admitting adapter's VERIFIED bundle names, and a
|
|
3737
|
+
* CLOSED payload of `task_type`, `operator`, `targets`, `params` and `cidrs`
|
|
3738
|
+
* -- as the one Proposal, the one hash preimage, and the one object §8.3.1's
|
|
3739
|
+
* typing environment is derived from. The flat shape is gone: the reference
|
|
3740
|
+
* reads the envelope through one accessor, `acp_executor.payload()`, and the
|
|
3741
|
+
* Rust verifier through `acp_decision::Proposal::from_wire`, so a second
|
|
3742
|
+
* reading has nowhere to live.
|
|
3743
|
+
*
|
|
3744
|
+
* WHY BOTH LEVELS ARE CLOSED, AND WHAT THAT REFUSES. `additionalProperties`
|
|
3745
|
+
* is false at the root AND at the payload, and every property at both levels
|
|
3746
|
+
* is REQUIRED -- `params` and `cidrs` may be EMPTY objects, never absent. An
|
|
3747
|
+
* optional field means one object has two valid encodings and therefore two
|
|
3748
|
+
* `proposal_hash` values -- Z4, which AT-8b closes for the Attestation
|
|
3749
|
+
* Object by making its field set closed and every field required. Until
|
|
3750
|
+
* v1.3.27 no clause did the same for the Proposal, although it is hashed,
|
|
3751
|
+
* bound and ledger-keyed by the same machinery and its DS-6f binding is the
|
|
3752
|
+
* one retained indefinitely; PR-1 now states AT-8b's rule for it in as many
|
|
3753
|
+
* words, and this schema is where the rule is machine-readable. One
|
|
3754
|
+
* consequence is disclosed rather than resolved: §7.4 permits a deployment
|
|
3755
|
+
* to insert a domain-screening stage whose verdict `enters the Proposal as a
|
|
3756
|
+
* field policy can rule on`. V-1 forbids unknown fields on the registered
|
|
3757
|
+
* Input Schema that governs `params`, and PR-1 closes the payload around it,
|
|
3758
|
+
* so that verdict has no key to live under. The specification names the
|
|
3759
|
+
* field, does not name its key, does not type it, and does not say whether
|
|
3760
|
+
* it is inside the hash preimage. A deployment that inserts a screen is
|
|
3761
|
+
* outside this schema and needs a version increment, on AT-8b's reasoning
|
|
3762
|
+
* applied here.
|
|
3763
|
+
*
|
|
3764
|
+
* WHAT IS NOT A FIELD. `proposal_hash` is DERIVED, not carried, and has no
|
|
3765
|
+
* property below. It is SHA-256 over the canonical encoding of these bytes
|
|
3766
|
+
* (§9.2), and §9.3 step 3 makes the Executor hash `the Proposal the Executor
|
|
3767
|
+
* independently received and canonicalized` -- `never whatever the receipt
|
|
3768
|
+
* describes`. A Proposal carrying its own hash invites a reader to compare a
|
|
3769
|
+
* transmitted value against a transmitted value, which is TR-10 exactly: a
|
|
3770
|
+
* transmitted identifier is a NAME for a binding, not evidence of one. Also
|
|
3771
|
+
* absent, each deliberately: `risk_level` and `reversibility`, recomputed by
|
|
3772
|
+
* the Executor from the signed bundle and these bytes (TR-8, RV-3) because a
|
|
3773
|
+
* receipt-asserted risk is X1 and a receipt-asserted reversibility is the
|
|
3774
|
+
* same defect with a new field; and `origin_nonce` and `idempotency_key`,
|
|
3775
|
+
* pinned by the ledger with any receipt-carried claim diagnostic only
|
|
3776
|
+
* (DS-6f, Z3). `operator` IS a field since PR-1 -- it is what §8.4 step 4
|
|
3777
|
+
* checks capability for -- and it is still not authority at floor-HIGH,
|
|
3778
|
+
* where the value the Executor acts on is `attestations[].obj.operator`
|
|
3779
|
+
* inside the attester signature (Y4, §9.3 step 7b(iii-a)).
|
|
3780
|
+
*
|
|
3781
|
+
* SUITE 12 CLASSIFICATION. Every property below is class T at the schema
|
|
3782
|
+
* layer: this document constrains SHAPE, and shape is not authority. What
|
|
3783
|
+
* discharges each one lives elsewhere and is cited on the property rather
|
|
3784
|
+
* than restated (DP-84). The triple is a NAME for a binding until §8.4 step
|
|
3785
|
+
* 1 verifies it against the SIGNED registry (TR-10, SR-2). `fidelity` is
|
|
3786
|
+
* RECOMPUTED from the bundle-registered adapter binding for `schema_id`
|
|
3787
|
+
* (TR-8, §9.3 step 7) and is never read as authority from here,
|
|
3788
|
+
* notwithstanding that B-6 requires it to be carried. `tenant_id` is stamped
|
|
3789
|
+
* by the adapter from the bundle it VERIFIED (PR-2) -- B at the door -- and
|
|
3790
|
+
* is T on the wire until §9.3 step 8 compares it against the receipt's and
|
|
3791
|
+
* against the bundle the Executor trusts, at which point a receipt, a
|
|
3792
|
+
* Proposal and a manifest that all name one tenant is what the step
|
|
3793
|
+
* establishes. `operator` below floor-HIGH is the disclosed residual the
|
|
3794
|
+
* receipt's `operator` already was (ACP-CLASS-001); at floor-HIGH it is B
|
|
3795
|
+
* through Y4.
|
|
3796
|
+
* Generated from proposal.schema.json.
|
|
3797
|
+
*/
|
|
3798
|
+
export interface Proposal {
|
|
3799
|
+
/**
|
|
3800
|
+
* Names the registered schema the payload was validated against. Schemas are
|
|
3801
|
+
* content-addressed by `schema_id` + `schema_version` + `schema_hash` (SR-1)
|
|
3802
|
+
* and SR-5 requires that triple on every Proposal. It is also the key the
|
|
3803
|
+
* Executor uses to look up the bundle-registered ADAPTER binding when it
|
|
3804
|
+
* recomputes `fidelity` (§9.3 step 7), so a Proposal without it leaves TR-8
|
|
3805
|
+
* with nothing to recompute against and the FC-2 confirmation requirement
|
|
3806
|
+
* resting on a transmitted stamp.
|
|
3807
|
+
*
|
|
3808
|
+
* THE PATTERN IS §6.3's, CHARACTER FOR CHARACTER, AND THE REFERENCE DOES NOT
|
|
3809
|
+
* SATISFY IT. `reference/suites/conformance.py` builds `schema_id: "fw.v1"`,
|
|
3810
|
+
* and `.` is outside that class. The pattern is kept as written: widening a
|
|
3811
|
+
* schema's own pattern to admit the fixture that violates it proves only
|
|
3812
|
+
* that the instrument is adjustable. Whether a pattern stated inside a
|
|
3813
|
+
* NORMATIVE EXAMPLE binds an implementation is itself unresolved and is
|
|
3814
|
+
* reported, not assumed.
|
|
3815
|
+
*/
|
|
3816
|
+
readonly schemaId: SchemaId;
|
|
3817
|
+
/**
|
|
3818
|
+
* The semantic version of that registered schema (SR-1), carried on the
|
|
3819
|
+
* Proposal by SR-5 so the artifact says which admissible SET it was checked
|
|
3820
|
+
* against and not merely which schema name. This is the axis SR-3 decides
|
|
3821
|
+
* change direction over: a change is NARROWING only where the new admissible
|
|
3822
|
+
* set is a provable subset of the old in every dimension of the bounded
|
|
3823
|
+
* lattice -- character class, length bound, enum set, numeric range -- and
|
|
3824
|
+
* anything not provably narrowing in every dimension is widening and takes
|
|
3825
|
+
* the distinct-reviewer path. v1.2.0 left `narrowing` undefined, which let a
|
|
3826
|
+
* net-widening change self-classify as narrowing and bypass review.
|
|
3827
|
+
*/
|
|
3828
|
+
readonly schemaVersion: SchemaVersion;
|
|
3829
|
+
/**
|
|
3830
|
+
* The content address of that schema version (SR-1), and the reason the
|
|
3831
|
+
* triple is a triple rather than a pair: `schema_id` and `schema_version`
|
|
3832
|
+
* are names a registry could remap, a hash is not. Class T here and a NAME
|
|
3833
|
+
* for a binding, not evidence of one (TR-10). The binding is discharged at
|
|
3834
|
+
* §8.4 step 1 -- schema attestation, valid `(schema_id, version, hash)` in
|
|
3835
|
+
* bundle, else DENY -- against the SIGNED registry, and the validator MUST
|
|
3836
|
+
* refuse to start on a signature or hash mismatch (SR-2). SR-6 supplies the
|
|
3837
|
+
* other direction: a Proposal naming a removed schema receives DENY with
|
|
3838
|
+
* `schema_retired`.
|
|
3839
|
+
*/
|
|
3840
|
+
readonly schemaHash: Sha256Hash;
|
|
3841
|
+
/**
|
|
3842
|
+
* The fidelity class of the adapter that produced this Proposal. Stamped by
|
|
3843
|
+
* the ADAPTER, bound into the Decision and the receipt, and MUST NOT be
|
|
3844
|
+
* settable by the client (B-6); carried here because B-6 and SR-5 both say
|
|
3845
|
+
* the Proposal carries it.
|
|
3846
|
+
*
|
|
3847
|
+
* CARRIED IS NOT TRUSTED. The Executor MUST verify fidelity against the
|
|
3848
|
+
* bundle-registered adapter binding for this `schema_id` and MUST NOT accept
|
|
3849
|
+
* it as transmitted (TR-8, §9.3 step 7), because a forged `F-HIGH` stamp
|
|
3850
|
+
* suppresses the FC-2 confirmation requirement exactly as v1.3.0's forged
|
|
3851
|
+
* `risk_level_floor_only` suppressed attestation (X1). The value is inside
|
|
3852
|
+
* the hash preimage, so it is bound to everything `proposal_hash` binds --
|
|
3853
|
+
* including the object the attesters signed and the
|
|
3854
|
+
* `resource.effective_tier` comparisons that read `fidelity` in §8.3's own
|
|
3855
|
+
* reference bundle -- and it is still not authority for any control
|
|
3856
|
+
* decision.
|
|
3857
|
+
*
|
|
3858
|
+
* AND NO CLAUSE SAYS WHAT A DISAGREEMENT HERE MEANS. §9.3 step 7a makes a
|
|
3859
|
+
* disagreement between the recomputed value and the RECEIPT's value a
|
|
3860
|
+
* critical alert. Nothing states the outcome when the recomputed value
|
|
3861
|
+
* disagrees with the value carried in the PROPOSAL, which is the value AT-3
|
|
3862
|
+
* showed the attesters and the one the quorum is bound to. The Python
|
|
3863
|
+
* reference never reaches the question: its Proposal carries no `fidelity`
|
|
3864
|
+
* field at all, which is B-6 and SR-5 unimplemented rather than a reading of
|
|
3865
|
+
* them.
|
|
3866
|
+
*/
|
|
3867
|
+
readonly fidelity: FidelityClass;
|
|
3868
|
+
/**
|
|
3869
|
+
* The tenant the admitting Ingress Adapter's VERIFIED bundle governs
|
|
3870
|
+
* (RAD-5's value, the manifest's `tenant_id`), stamped by the adapter under
|
|
3871
|
+
* PR-2 and never settable by the client -- B-6's rule for `fidelity`,
|
|
3872
|
+
* applied to the second value the door supplies. It exists so that §9.3 step
|
|
3873
|
+
* 8 has a byte to compare: the step requires the receipt's tenant to equal
|
|
3874
|
+
* the Proposal's AND the trusted bundle's, both held independently of the
|
|
3875
|
+
* receipt, and until v1.3.27 the schema-shaped Proposal carried no tenant at
|
|
3876
|
+
* all, so a conformant door's output could not pass a conformant Executor's
|
|
3877
|
+
* step 8. Class T on the wire, discharged at step 8 -- and NOT a lookup key:
|
|
3878
|
+
* the Executor selects its bundle from its own configuration and compares
|
|
3879
|
+
* this value against it, never the other way round, because a transmitted
|
|
3880
|
+
* tenant that selected the policy would be the KMS's pre-RAD-5 defect one
|
|
3881
|
+
* component over.
|
|
3882
|
+
*/
|
|
3883
|
+
readonly tenantId: TenantId;
|
|
3884
|
+
/**
|
|
3885
|
+
* The validated object itself: PR-1's closed payload of `task_type`,
|
|
3886
|
+
* `operator`, `targets`, `params` and `cidrs`, all five REQUIRED. V-11 is
|
|
3887
|
+
* the clause that names both halves -- it requires the validator to reject
|
|
3888
|
+
* any envelope whose payload `task_type` disagrees with its `schema_id`.
|
|
3889
|
+
* §8.3.1 says the typing environment risk expressions resolve against is
|
|
3890
|
+
* `derived from the Proposal schema`: since PR-1 that environment is
|
|
3891
|
+
* `params` (each entry an EL-2 value, bound under its own name) together
|
|
3892
|
+
* with each target's tier from `floors.json` (`<target>.effective_tier`,
|
|
3893
|
+
* RK-2), and nothing else in the envelope is visible to a rule.
|
|
3894
|
+
*/
|
|
3895
|
+
readonly payload: ProposalPayload;
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* The lenient projection of Proposal: every field optional, every enum
|
|
3899
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
3900
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
3901
|
+
*/
|
|
3902
|
+
export interface RawProposal {
|
|
3903
|
+
readonly schemaId?: string;
|
|
3904
|
+
readonly schemaVersion?: string;
|
|
3905
|
+
readonly schemaHash?: string;
|
|
3906
|
+
readonly fidelity?: string;
|
|
3907
|
+
readonly tenantId?: string;
|
|
3908
|
+
readonly payload?: RawProposalPayload;
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* `task_type` and the four fields PR-1 closes the payload around. CLOSED at
|
|
3912
|
+
* this level since v1.3.27, and the registered Input Schema's fields live
|
|
3913
|
+
* INSIDE `params`, where V-1 closes them.
|
|
3914
|
+
*
|
|
3915
|
+
* WHAT WAS OPEN HERE, AND WHY IT IS NOT ANY MORE. Through v1.3.26 this
|
|
3916
|
+
* object declared `task_type` and left everything else undeclared, on the
|
|
3917
|
+
* argument that the payload's field set is closed SOMEWHERE ELSE -- the
|
|
3918
|
+
* per-tenant, content-addressed registry SR-1 and L-06 describe, a
|
|
3919
|
+
* DEPLOYMENT fact this file must not restate. The argument stands, and PR-1
|
|
3920
|
+
* relocates it one level down: the registered schema governs `params`, an
|
|
3921
|
+
* object whose keys are the deployment's, while the four members every
|
|
3922
|
+
* Proposal has in common -- the action class, the principal, the resources,
|
|
3923
|
+
* the CIDR widths -- are named once, here, and REQUIRED. An open payload had
|
|
3924
|
+
* left the verifier no way to tell a registered field from a smuggled one,
|
|
3925
|
+
* and the generated types carried `task_type` and nothing else, which a
|
|
3926
|
+
* reader could take for the whole payload (ACP-153, item 14).
|
|
3927
|
+
*
|
|
3928
|
+
* Z4, SAID PLAINLY. Every member REQUIRED and both objects closed is AT-8b's
|
|
3929
|
+
* rule -- no optional fields, no defaultable fields, no extension points --
|
|
3930
|
+
* applied to the Proposal, which through v1.3.26 had no counterpart of it.
|
|
3931
|
+
* `params` and `cidrs` are EMPTY objects when an action has none, never
|
|
3932
|
+
* absent, so one action has one encoding. What this cannot close: a
|
|
3933
|
+
* registered Input Schema that declares an optional or defaulted field
|
|
3934
|
+
* inside `params` reopens it there, and no clause forbids one from doing so;
|
|
3935
|
+
* B-1a is what stops that becoming two hashes -- the CANONICAL Proposal
|
|
3936
|
+
* travels to the Executor and §9.3 step 3 hashes what arrived -- and the
|
|
3937
|
+
* absence of an AT-8b-style rule for registered schemas is reported, not
|
|
3938
|
+
* assumed harmless.
|
|
3939
|
+
*
|
|
3940
|
+
* WHAT THIS TYPE ENFORCES, AND WHAT IT DOES NOT. EL-2: every `params` value
|
|
3941
|
+
* is an integer or a string, and a value of any other JSON type -- a
|
|
3942
|
+
* non-integer number, a boolean, null, an array, an object, or an integer
|
|
3943
|
+
* outside the width the deployment declares under AC-1a -- MUST cause the
|
|
3944
|
+
* Proposal to be REFUSED, never re-typed and never coerced. Since v1.3.27
|
|
3945
|
+
* the generated types carry that domain (`param_value`), so an off-domain
|
|
3946
|
+
* value fails to parse rather than being read as a string; the clause exists
|
|
3947
|
+
* because a JSON `22.0` bound as a STRING in the Python reference graded
|
|
3948
|
+
* MEDIUM where `port: 22` graded HIGH, against this document's own reference
|
|
3949
|
+
* bundle. Not enforced here and enforced at the validator instead: V-7 (NFC
|
|
3950
|
+
* before pattern matching, bidi and zero-width code points refused), V-6
|
|
3951
|
+
* (duplicate keys refused at every depth), L-01 to L-05 (32 KiB before
|
|
3952
|
+
* parsing, depth 8, 24 fields per schema, 2,048 characters per string, 100
|
|
3953
|
+
* items per list), and V-1's closure of `params` against the registered
|
|
3954
|
+
* schema -- for which no signed registry exists yet in the bundle (ACP-153,
|
|
3955
|
+
* item 12).
|
|
3956
|
+
* Generated from proposal.schema.json#/$defs/proposal_payload.
|
|
3957
|
+
*/
|
|
3958
|
+
export interface ProposalPayload {
|
|
3959
|
+
/**
|
|
3960
|
+
* The action class: a `Literal` over the registered set, closed world, no
|
|
3961
|
+
* dynamic dispatch and no plugins (V-8). §8.4 step 3 looks up a risk
|
|
3962
|
+
* function by it and DENIES where none exists -- an action with no risk
|
|
3963
|
+
* function is refused at `8.4-3` rather than graded HIGH, because grading an
|
|
3964
|
+
* unknown action is P-4's rule broken. RV-3 recomputes the reversibility
|
|
3965
|
+
* class from the trusted bundle and THIS value, never from the receipt, and
|
|
3966
|
+
* RV-1's absent-implies-IRREVERSIBLE default keys on it; DR-13 then reads
|
|
3967
|
+
* that default on the fast path, which is what turned it from documentation
|
|
3968
|
+
* into a control.
|
|
3969
|
+
*
|
|
3970
|
+
* V-11 REQUIRES IT TO AGREE WITH `schema_id` AND DOES NOT SAY WHAT AGREEMENT
|
|
3971
|
+
* IS. The clause admits two readings -- string identity, or a lookup
|
|
3972
|
+
* resolving `schema_id` to the task type it registers -- and the two
|
|
3973
|
+
* disagree on this repository's own fixtures, which pair `schema_id:
|
|
3974
|
+
* "fw.v1"` with `task_type: "modify_firewall_rule"`. §6.3's normative
|
|
3975
|
+
* example implements identity (`v.task_type != info.data["schema_id"]`),
|
|
3976
|
+
* under which these are two spellings of one value and this property is
|
|
3977
|
+
* redundant with `schema_id` -- and V-11 exists precisely because two
|
|
3978
|
+
* spellings can disagree. The pattern below is `schema_id`'s, for that
|
|
3979
|
+
* reason and no stronger one: no clause states a pattern for `task_type`
|
|
3980
|
+
* directly, and this is the strongest thing the text supports.
|
|
3981
|
+
*
|
|
3982
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
3983
|
+
* type: pattern `^[a-z0-9_-]{1,32}$`.
|
|
3984
|
+
*/
|
|
3985
|
+
readonly taskType: string;
|
|
3986
|
+
/**
|
|
3987
|
+
* The operating principal the action is proposed for: the identity §8.4 step
|
|
3988
|
+
* 4 checks every required capability against, step 6 tests against the
|
|
3989
|
+
* excluded roles, and §8.5 groups accumulators by. The Attestation Object's
|
|
3990
|
+
* `operator_id`, referenced rather than re-declared: one definition of a
|
|
3991
|
+
* principal identifier. CARRIED IS NOT TRUSTED. Below floor-HIGH this is the
|
|
3992
|
+
* disclosed residual the receipt's `operator` already was -- a
|
|
3993
|
+
* client-asserted principal, bounded by the capability check that binds it
|
|
3994
|
+
* to signed entitlements (§8.8). At floor-HIGH the authoritative value is
|
|
3995
|
+
* `attestations[].obj.operator` inside every attester signature (Y4, §9.3
|
|
3996
|
+
* step 7b(iii-a)), and the Policy Engine copies THIS value into the object
|
|
3997
|
+
* the attesters sign, so a Proposal whose `operator` differs from what the
|
|
3998
|
+
* quorum attested cannot execute.
|
|
3999
|
+
*/
|
|
4000
|
+
readonly operator: OperatorId;
|
|
4001
|
+
/**
|
|
4002
|
+
* The resources the action names, by the identifiers `floors.json` is keyed
|
|
4003
|
+
* by (RK-1; the item bounds are that table's `propertyNames`). At least one:
|
|
4004
|
+
* an action that names no resource has no floor to recompute a risk from,
|
|
4005
|
+
* and TR-8 has nothing to recompute against -- refused at the door under
|
|
4006
|
+
* this schema rather than graded against nothing. At most 100 (L-05's
|
|
4007
|
+
* per-list bound). Each target's tier enters the typing environment as
|
|
4008
|
+
* `<target>.effective_tier` (RK-2), and a target absent from `floors.json`
|
|
4009
|
+
* resolves to `T3` (RK-1) -- unknown is never LOW.
|
|
4010
|
+
*
|
|
4011
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
4012
|
+
* type: minItems 1, maxItems 100; items minLength 1, maxLength 256.
|
|
4013
|
+
*/
|
|
4014
|
+
readonly targets: readonly string[];
|
|
4015
|
+
/**
|
|
4016
|
+
* The registered Input Schema's object -- the fields `(schema_id,
|
|
4017
|
+
* schema_version, schema_hash)` resolves to and V-1 closes -- carried under
|
|
4018
|
+
* one key so the four common members beside it can be named without
|
|
4019
|
+
* restating a per-tenant registry here. Each value is an EL-2 value
|
|
4020
|
+
* (`param_value`): an integer or a string, bound into the typing environment
|
|
4021
|
+
* under its own name. EMPTY, never absent, for an action with no parameters
|
|
4022
|
+
* (Z4). Nothing in this repository validates a payload against a registered
|
|
4023
|
+
* Input Schema, because no signed registry exists in the bundle yet
|
|
4024
|
+
* (ACP-153, item 12): until it does, `params` is closed by name at the
|
|
4025
|
+
* registry a deployment holds and open to this file, which is disclosed
|
|
4026
|
+
* rather than papered over.
|
|
4027
|
+
*
|
|
4028
|
+
* Map keys: A parameter name as the registered Input Schema declares it and
|
|
4029
|
+
* as a rule names it in an expression (§8.3.1): lower-case, underscores, at
|
|
4030
|
+
* most 64 characters. The set of names is the REGISTERED schema's (SR-1,
|
|
4031
|
+
* V-1), closed there and not here.
|
|
4032
|
+
*/
|
|
4033
|
+
readonly params: Readonly<Record<string, ParamValue>>;
|
|
4034
|
+
/**
|
|
4035
|
+
* Prefix widths of the CIDR-typed parameters, by parameter name:
|
|
4036
|
+
* `{"source_cidr": 24}` for a `/24`. §8.3.1's `prefix_len(...)` reads them,
|
|
4037
|
+
* and the environment builder binds each as `<name>.prefix_len` (AC-1a's
|
|
4038
|
+
* integer width). A width is 0..128; an IPv4 width above 32 is the
|
|
4039
|
+
* registered schema's to refuse. EMPTY, never absent, for an action with no
|
|
4040
|
+
* CIDR parameter. Carried apart from `params` rather than parsed out of a
|
|
4041
|
+
* string there, because the width is what a rule compares and a rule that
|
|
4042
|
+
* had to parse a CIDR would be a rule that could be made to parse it
|
|
4043
|
+
* wrongly.
|
|
4044
|
+
*
|
|
4045
|
+
* Map keys: The name of the CIDR-typed parameter the width belongs to, in
|
|
4046
|
+
* the same class as a `params` name.
|
|
4047
|
+
*
|
|
4048
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
4049
|
+
* type: values minimum 0, maximum 128.
|
|
4050
|
+
*/
|
|
4051
|
+
readonly cidrs: Readonly<Record<string, number>>;
|
|
4052
|
+
}
|
|
4053
|
+
/**
|
|
4054
|
+
* The lenient projection of ProposalPayload: every field optional, every enum
|
|
4055
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
4056
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
4057
|
+
*/
|
|
4058
|
+
export interface RawProposalPayload {
|
|
4059
|
+
readonly taskType?: string;
|
|
4060
|
+
readonly operator?: string;
|
|
4061
|
+
readonly targets?: readonly string[];
|
|
4062
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
4063
|
+
readonly cidrs?: Readonly<Record<string, number>>;
|
|
4064
|
+
}
|
|
4065
|
+
/**
|
|
4066
|
+
* One execution per receipt (CL-3, T-09). The Executor claims the receipt
|
|
4067
|
+
* nonce at §9.3 step 6, before any effect leaves the control plane, and a
|
|
4068
|
+
* second receipt bearing the same nonce is refused because the ledger
|
|
4069
|
+
* already holds it. AT-5's shape applies here too: single use is enforced by
|
|
4070
|
+
* consumption, never by assertion -- v1.2.0 stated the property and shipped
|
|
4071
|
+
* no mechanism.
|
|
4072
|
+
* Generated from ledger_claim.schema.json#/$defs/receipt_nonce_claim.
|
|
4073
|
+
*/
|
|
4074
|
+
export interface ReceiptNonceClaim {
|
|
4075
|
+
readonly tenantId: TenantId;
|
|
4076
|
+
/**
|
|
4077
|
+
* CL-3's first identifier class. See the note on `artifact_class` naming in
|
|
4078
|
+
* `$defs/tenant_id` and in the schema description: the four tokens are this
|
|
4079
|
+
* schema's rendering of class names the specification states only in prose.
|
|
4080
|
+
*/
|
|
4081
|
+
readonly artifactClass: 'receipt_nonce';
|
|
4082
|
+
/**
|
|
4083
|
+
* The receipt's `nonce` (§9.2), sized 128 bits by L-17 and typed by WE-4.
|
|
4084
|
+
* Named `identifier` rather than `nonce` because CL-5 writes the ledger key
|
|
4085
|
+
* as `(tenant_id, artifact_class, identifier)` and a second spelling of one
|
|
4086
|
+
* column is the defect this repository is built around.
|
|
4087
|
+
*/
|
|
4088
|
+
readonly identifier: Nonce128;
|
|
4089
|
+
}
|
|
4090
|
+
/**
|
|
4091
|
+
* The lenient projection of ReceiptNonceClaim: every field optional, every enum
|
|
4092
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
4093
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
4094
|
+
*/
|
|
4095
|
+
export interface RawReceiptNonceClaim {
|
|
4096
|
+
readonly tenantId?: string;
|
|
4097
|
+
readonly artifactClass?: string;
|
|
4098
|
+
readonly identifier?: string;
|
|
4099
|
+
}
|
|
4100
|
+
/**
|
|
4101
|
+
* The SIGNED OBJECT a door produces during a deferred-release hold (DR-14):
|
|
4102
|
+
* the human-readable summary of one action, bound to the canonical Proposal
|
|
4103
|
+
* it was rendered from and to the role that rendered it. One shape, two
|
|
4104
|
+
* producers -- the Attestation Presentation Service signs the approval
|
|
4105
|
+
* summary under the `presentation` key and the notification service signs
|
|
4106
|
+
* the Notification under the `notification` key, both named in the bundle's
|
|
4107
|
+
* door_identities.json (PB-11). The Executor verifies each under the key for
|
|
4108
|
+
* its role and compares each `proposal_hash` against its own canonical hash
|
|
4109
|
+
* of the held Proposal.
|
|
4110
|
+
*
|
|
4111
|
+
* WHAT THE SIGNATURE DOES NOT PROVE. That `summary` is faithful to the bytes
|
|
4112
|
+
* `proposal_hash` names. A compromised door signs whatever it renders; what
|
|
4113
|
+
* it can no longer do is sign for the OTHER door, because it does not hold
|
|
4114
|
+
* that key. Rendering honesty remains DR-2's structural requirement -- no
|
|
4115
|
+
* shared rendering path, demonstrated at build time under conformance suite
|
|
4116
|
+
* 3 -- and this object must not be read as though a signature supplied it.
|
|
4117
|
+
*
|
|
4118
|
+
* CLOSED FIELD SET, FOR Z4'S REASON: an optional field gives one summary two
|
|
4119
|
+
* valid encodings. No nonce, on purpose: the object binds `proposal_hash`, a
|
|
4120
|
+
* replay of it is the same summary for the same action, and DR-4/DR-5 key
|
|
4121
|
+
* repudiation on the proposal, not on the summary.
|
|
4122
|
+
* Generated from notification.schema.json#/$defs/rendered_summary.
|
|
4123
|
+
*/
|
|
4124
|
+
export interface RenderedSummary {
|
|
4125
|
+
/**
|
|
4126
|
+
* CR-1: the signature suite this object is signed under, inside the signed
|
|
4127
|
+
* bytes. An unregistered suite fails closed.
|
|
4128
|
+
*/
|
|
4129
|
+
readonly alg: SuiteId;
|
|
4130
|
+
/**
|
|
4131
|
+
* Which door rendered this. Inside the signed bytes so the object says what
|
|
4132
|
+
* it is: the Executor still resolves the key by the slot it expects
|
|
4133
|
+
* (notification under `notification`, approval summary under
|
|
4134
|
+
* `presentation`), and PB-11's distinctness means a summary signed for one
|
|
4135
|
+
* slot cannot verify in the other -- but a signed object that does not name
|
|
4136
|
+
* its own role is one whose meaning depends on where it was found, which is
|
|
4137
|
+
* the two-encodings defect one step removed.
|
|
4138
|
+
*/
|
|
4139
|
+
readonly role: DoorRole;
|
|
4140
|
+
/**
|
|
4141
|
+
* The canonical Proposal this summary was rendered from. The binding the
|
|
4142
|
+
* object exists to carry: the Executor compares it against its OWN canonical
|
|
4143
|
+
* hash of the Proposal being held, never against a transmitted value
|
|
4144
|
+
* (TR-10's shape). This is also what DR-4's repudiation cites, which the
|
|
4145
|
+
* unsigned notification of v1.3.24 could not name -- an earlier revision of
|
|
4146
|
+
* this file reported that gap and did not patch it; DR-14 closes it.
|
|
4147
|
+
*/
|
|
4148
|
+
readonly proposalHash: Sha256Hash;
|
|
4149
|
+
/**
|
|
4150
|
+
* The human-readable summary DR-2 requires, rendered from the canonical
|
|
4151
|
+
* Proposal bytes by the door named in `role`. §7.3 governs its content and
|
|
4152
|
+
* this is the field most exposed to the model: the canonical Proposal
|
|
4153
|
+
* carries parameters that passed validation and are still
|
|
4154
|
+
* attacker-influenced, so whatever displays this string MUST treat it as
|
|
4155
|
+
* DATA and never as markup -- an approver reading attacker-influenced prose
|
|
4156
|
+
* is an injection target, and T-02 includes humans. Empty is refused
|
|
4157
|
+
* (`minLength: 1`, this schema's reading): a summary with no content is a
|
|
4158
|
+
* detection channel that is present and blank. No maximum, deliberately: an
|
|
4159
|
+
* invented bound silently truncates a legitimate summary, which is a display
|
|
4160
|
+
* lie manufactured by the schema. Named for the specification's word;
|
|
4161
|
+
* `reference/src/acp_executor.py` called it `text` through v1.3.24.
|
|
4162
|
+
*
|
|
4163
|
+
* Schema constraints, carried as documentation and NOT enforced by this
|
|
4164
|
+
* type: minLength 1.
|
|
4165
|
+
*/
|
|
4166
|
+
readonly summary: string;
|
|
4167
|
+
}
|
|
4168
|
+
/**
|
|
4169
|
+
* The lenient projection of RenderedSummary: every field optional, every enum
|
|
4170
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
4171
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
4172
|
+
*/
|
|
4173
|
+
export interface RawRenderedSummary {
|
|
4174
|
+
readonly alg?: string;
|
|
4175
|
+
readonly role?: string;
|
|
4176
|
+
readonly proposalHash?: string;
|
|
4177
|
+
readonly summary?: string;
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* What the KMS receives from the Policy Engine. It is the only artifact that
|
|
4181
|
+
* crosses leg F3.2 of the leg register (ZIFFER-DEPLOY-001 Annex A), and it
|
|
4182
|
+
* carries one thing: the canonical bytes the KMS is being asked to sign with
|
|
4183
|
+
* the `receipt_signing_key` (§9.1.1 step 6).
|
|
4184
|
+
*
|
|
4185
|
+
* WHY THE FIELD SET IS CLOSED, AND WHY IT HAS EXACTLY ONE MEMBER. §9.1.1
|
|
4186
|
+
* step 2 has the KMS extract 'the claimed `policy_bundle_hash` and
|
|
4187
|
+
* `bundle_epoch`' from every signing request. Both are already inside the
|
|
4188
|
+
* bytes below, because both are receipt fields (§9.2). Carrying them AGAIN
|
|
4189
|
+
* beside those bytes would give one value two spellings, and the two
|
|
4190
|
+
* spellings are separately chosen by the party under verification. A
|
|
4191
|
+
* compromised Policy Engine puts the current epoch and the current bundle
|
|
4192
|
+
* hash in the request fields -- which is what the KMS retrieves, verifies
|
|
4193
|
+
* and checks against its high-water mark (steps 3-5) -- and a superseded,
|
|
4194
|
+
* genuinely signed, more permissive bundle's hash and epoch in the body it
|
|
4195
|
+
* hands over. Every step of §9.1.1 passes, and the KMS then signs, under the
|
|
4196
|
+
* one key the Executor trusts, a receipt naming a bundle it never verified.
|
|
4197
|
+
* That is not a subtle failure of the protocol: it is RAD-4 violated in the
|
|
4198
|
+
* exact words RAD-4 uses, reached without the KMS breaking any rule it was
|
|
4199
|
+
* given, because the rule was given about a different field. Downstream it
|
|
4200
|
+
* is T-15 bundle rollback, and §9.3 step 4 is the only thing left in its way
|
|
4201
|
+
* -- a comparison against the Executor's last-seen epoch, which
|
|
4202
|
+
* ZIFFER-SPEC-001 never makes durable or shared (ZIFFER-DEPLOY-001 DP-40
|
|
4203
|
+
* supplies that obligation and records the gap as a live one).
|
|
4204
|
+
* `additionalProperties: false` with one required member is what forbids the
|
|
4205
|
+
* second spelling, and it is the reason this artifact needs a schema at all
|
|
4206
|
+
* rather than being left to the transport.
|
|
4207
|
+
*
|
|
4208
|
+
* WHAT THE KMS DOES WITH THIS, SO THE SCHEMA IS NOT MISREAD AS AUTHORITY.
|
|
4209
|
+
* Every value reachable through this field is a CLAIM by the Policy Engine,
|
|
4210
|
+
* and RAD-4 is explicit that the KMS MUST NOT accept bundle metadata, epochs
|
|
4211
|
+
* or policy hashes from the Policy Engine runtime without cryptographic
|
|
4212
|
+
* verification. The `policy_bundle_hash` in the body is a NAME for a bundle,
|
|
4213
|
+
* never evidence of one: the KMS retrieves that bundle from the read-only
|
|
4214
|
+
* repository the Policy Engine runtime cannot write to (§9.1.1 step 3,
|
|
4215
|
+
* isolation stronger than filesystem permissions) and verifies its Ed25519
|
|
4216
|
+
* signature against its own copy of the `policy_signing_key` public key
|
|
4217
|
+
* (step 4). On failure it rejects all signing requests until a valid bundle
|
|
4218
|
+
* is presented (RAD-4). The `bundle_epoch` in the body is not authority
|
|
4219
|
+
* either: RAD-3 compares it against the highest epoch the KMS has ever
|
|
4220
|
+
* validated for that tenant in durable storage, rejects anything lower, and
|
|
4221
|
+
* updates the mark (step 5). The mark is state at the KMS and appears in no
|
|
4222
|
+
* message here. TR-10 states the general form -- a transmitted identifier is
|
|
4223
|
+
* a name for a binding, not evidence of one.
|
|
4224
|
+
*
|
|
4225
|
+
* WHAT IS NOT A FIELD, each absence with its reason. (a) No
|
|
4226
|
+
* `policy_bundle_hash`, `bundle_epoch` or `tenant_id` beside the body: one
|
|
4227
|
+
* value, one spelling, per the paragraph above; the tenant RAD-1, RAD-2 and
|
|
4228
|
+
* RAD-3 are all scoped to is the body's `tenant_id` (§9.2). (b) No key
|
|
4229
|
+
* identifier and no algorithm or suite selector: the Executor verifies under
|
|
4230
|
+
* the `receipt_signing_key` the signed bundle registers (§9.3 step 1), so
|
|
4231
|
+
* the key is settled by the bundle the KMS has just verified, not by the
|
|
4232
|
+
* requester. Offering the party under verification a negotiable parameter is
|
|
4233
|
+
* the downgrade shape AB-0 refuses one layer up, where accepting receipt
|
|
4234
|
+
* versions 2 and 3 concurrently would let an attacker present the weaker
|
|
4235
|
+
* one. (c) No canonical Proposal: §9.1.1 gives the KMS steps 2-5 and no
|
|
4236
|
+
* policy evaluation. The KMS is independent about BUNDLE AUTHENTICITY, not
|
|
4237
|
+
* about whether the Decision is correct, and §10's compromise table already
|
|
4238
|
+
* grants that a compromised Policy Engine emits arbitrary Decision content,
|
|
4239
|
+
* answering it with RAD-1/RAD-2 rate limits, the Executor's recomputation
|
|
4240
|
+
* (TR-8) and audit replay. A Proposal on this leg would read as a second
|
|
4241
|
+
* evaluation that no clause defines. (d) No attestation entries: under
|
|
4242
|
+
* receipt version 3 they are excluded from the signed body by construction
|
|
4243
|
+
* (AB-5), they reach the KMS from the approver devices on leg F4.4, and they
|
|
4244
|
+
* travel beside the receipt on F3.3. (e) No `x-acp-absent` anywhere: no
|
|
4245
|
+
* clause declares a fail-safe default for any part of this artifact, and
|
|
4246
|
+
* inventing one would give an absent field a meaning the specification does
|
|
4247
|
+
* not give it.
|
|
4248
|
+
*
|
|
4249
|
+
* WHAT THIS SCHEMA DOES NOT CONSTRAIN, stated so the claim is not read wider
|
|
4250
|
+
* than it is. It constrains SHAPE, and shape is not authority: under the §14
|
|
4251
|
+
* suite-12 classification every value reachable through this field is class
|
|
4252
|
+
* T at the schema layer, and the receiving obligations are RAD-3 and RAD-4
|
|
4253
|
+
* exactly as the leg register cites them, not restated here
|
|
4254
|
+
* (ZIFFER-DEPLOY-001 DP-84). It says nothing about ORDER, and §9.1.1 is an
|
|
4255
|
+
* ordered checklist: a KMS that signs first and verifies afterwards
|
|
4256
|
+
* satisfies every line below. It says nothing about the durable high-water
|
|
4257
|
+
* mark, which is per-tenant state and crosses no leg. It says nothing about
|
|
4258
|
+
* freshness of the request itself -- no clause gives this leg an anti-replay
|
|
4259
|
+
* value, and a replayed request re-signs the same body, whose duplicate is
|
|
4260
|
+
* caught downstream only by CL-3's consumption of the receipt nonce. And it
|
|
4261
|
+
* says nothing about the response: §9.1.1 step 7's `SIGNING_DENIED` is a
|
|
4262
|
+
* normative reply with no schema here and no row in the leg register.
|
|
4263
|
+
*
|
|
4264
|
+
* WHAT THE SPECIFICATION DOES NOT ANSWER. Recorded rather than filled in,
|
|
4265
|
+
* because a schema that invents an answer publishes it as though the
|
|
4266
|
+
* specification had one. (1) Who assembles `attestation_digests` (AB-1) is
|
|
4267
|
+
* unstated, and the register gives the Policy Engine no leg on which
|
|
4268
|
+
* attestation entries or their digests reach it -- F4.4 routes them to the
|
|
4269
|
+
* KMS. If the KMS assembles them, this leg carries a PARTIAL body and this
|
|
4270
|
+
* schema is wrong in shape rather than in detail. (2) §9.1.1 step 2 does not
|
|
4271
|
+
* say whether the claimed values are separate request fields or are read out
|
|
4272
|
+
* of the body; this schema takes the second reading and gives its reason
|
|
4273
|
+
* above, but the text settles neither. (3) No clause names the party that
|
|
4274
|
+
* canonicalizes the receipt body, nor forbids the KMS re-encoding a
|
|
4275
|
+
* structure it decoded before signing; AT-8a states that rule for the
|
|
4276
|
+
* Attestation Object only. (4) AB-6's 4,096-byte bound is written about the
|
|
4277
|
+
* Executor, and no clause states it at this leg even though the bytes are
|
|
4278
|
+
* the same bytes. (5) Nothing in this repository builds or consumes a
|
|
4279
|
+
* signing request: `sim/receipts.py` plays the KMS and signs directly, there
|
|
4280
|
+
* is no KMS-side epoch mark anywhere, and `tools/check-flow-legs.py`
|
|
4281
|
+
* resolves this leg's RAD-3 citation against a raise site in the Executor
|
|
4282
|
+
* and exempts RAD-4 outright. So the two obligations this leg cites have no
|
|
4283
|
+
* executable consumer AT THIS BOUNDARY, which is the
|
|
4284
|
+
* normative-text-with-no-executable-consumer shape ACP-50, ACP-56 and ACP-57
|
|
4285
|
+
* each landed a check for.
|
|
4286
|
+
* Generated from signing_request.schema.json.
|
|
4287
|
+
*/
|
|
4288
|
+
export interface SigningRequest {
|
|
4289
|
+
/**
|
|
4290
|
+
* The bytes to be signed: the canonical CBOR encoding (RFC 8949 §4.2) of the
|
|
4291
|
+
* receipt body WITHOUT the attestation entries (WE-1, WE-2, AB-5), carried
|
|
4292
|
+
* as a WE-4 value.
|
|
4293
|
+
*
|
|
4294
|
+
* THE NAME IS THE SPECIFICATION'S. AB-5, AB-6 and §9.3 step 7b all call
|
|
4295
|
+
* these bytes 'the signed body', so calling them anything else here would be
|
|
4296
|
+
* a second vocabulary for one object -- the defect ZIFFER-DEPLOY-001 DP-16
|
|
4297
|
+
* forbids one layer down. At this leg the signature does not exist yet; the
|
|
4298
|
+
* name is for the bytes the signature will cover, and no field here carries
|
|
4299
|
+
* a signature.
|
|
4300
|
+
*
|
|
4301
|
+
* THE KMS SIGNS THE BYTES IT RECEIVED. It parses them to extract the claimed
|
|
4302
|
+
* `policy_bundle_hash` and `bundle_epoch` of §9.1.1 step 2, and signs the
|
|
4303
|
+
* received encoding rather than a re-encoding of the structure it decoded.
|
|
4304
|
+
* Two encoders on one signing path is the defect AT-8a names for the
|
|
4305
|
+
* Attestation Object -- issuer and verifier disagree and one object acquires
|
|
4306
|
+
* two identities -- and nothing about the receipt makes it immune, since the
|
|
4307
|
+
* Executor recomputes nothing here and verifies the signature over the bytes
|
|
4308
|
+
* it is handed (§9.3 step 1). WE-3 refuses JSON as receipt input for the
|
|
4309
|
+
* same reason; it is written about a receipt consumer rather than about this
|
|
4310
|
+
* leg, so it supports the rule here without stating it. Carrying bytes
|
|
4311
|
+
* rather than a decoded object is also what makes 'the bytes signed are the
|
|
4312
|
+
* bytes the Policy Engine composed' a checkable claim instead of an
|
|
4313
|
+
* assumption.
|
|
4314
|
+
*
|
|
4315
|
+
* THE LENGTH BOUND IS DERIVED, NOT CITED, AND IT IS NOT EXACT. AB-6 caps the
|
|
4316
|
+
* signed body at 4,096 bytes and requires the check on MEASURED BYTES rather
|
|
4317
|
+
* than on any proxy, having been written after a draft capped the digest
|
|
4318
|
+
* count at 50 and measured 4,107 bytes at that cap. These are the bytes AB-6
|
|
4319
|
+
* bounds (AB-5), so a request over the cap asks for a receipt no conformant
|
|
4320
|
+
* Executor may accept and no custody tier T2 key can produce -- the
|
|
4321
|
+
* 4,096-byte limit is AWS KMS's `MessageType: RAW` cap, executed rather than
|
|
4322
|
+
* read in `tools/kms-compat-2026-08-22.json` (5/5 PASS, 2026-08-22).
|
|
4323
|
+
* `maxLength` here is on the base64 TEXT, and base64 quantizes at three
|
|
4324
|
+
* bytes: 5,468 characters admits 4,098 bytes, two more than AB-6 allows. It
|
|
4325
|
+
* is therefore a floor on refusal and never the check. An implementation
|
|
4326
|
+
* MUST measure the decoded bytes, which is AB-6's own warning about proxies
|
|
4327
|
+
* applied to AB-6's own transport encoding. No clause places the bound at
|
|
4328
|
+
* this leg at all; that gap is recorded on the schema above rather than
|
|
4329
|
+
* closed here.
|
|
4330
|
+
*
|
|
4331
|
+
* EVERYTHING IN THESE BYTES IS A CLAIM. The KMS verifies the bundle they
|
|
4332
|
+
* name (RAD-4) and the epoch they reference (RAD-3) against artifacts and
|
|
4333
|
+
* state it holds itself; it does not verify the Decision they assert, and no
|
|
4334
|
+
* clause asks it to.
|
|
4335
|
+
*/
|
|
4336
|
+
readonly signedBody: Base64Bytes;
|
|
4337
|
+
}
|
|
4338
|
+
/**
|
|
4339
|
+
* The lenient projection of SigningRequest: every field optional, every enum
|
|
4340
|
+
* degraded to `string`. What a verifier parses. Never make a decision on
|
|
4341
|
+
* one — the fail-safe lookups live on the strict type only.
|
|
4342
|
+
*/
|
|
4343
|
+
export interface RawSigningRequest {
|
|
4344
|
+
readonly signedBody?: string;
|
|
4345
|
+
}
|
|
4346
|
+
/**
|
|
4347
|
+
* One claim made against the Consumption Ledger. Carried on leg F3.4 of the
|
|
4348
|
+
* leg register (ZIFFER-DEPLOY-001 Annex A), Executor -> Consumption Ledger,
|
|
4349
|
+
* whose cited receiving obligations are CL-2, CL-3, CL-6 and CL-7. The
|
|
4350
|
+
* Consumption Ledger is TCB (§4.3): a ledger that falsely reports an
|
|
4351
|
+
* identifier absent reopens T-09 (receipt replay) and T-14 (attestation
|
|
4352
|
+
* amplification). This document is the smallest thing that can be wrong
|
|
4353
|
+
* before any of those guarantees is reachable at all.
|
|
4354
|
+
*
|
|
4355
|
+
* WHY THIS IS FOUR CLOSED OBJECTS AND NOT ONE OBJECT WITH OPTIONAL FIELDS.
|
|
4356
|
+
* CL-3 enumerates four identifier classes and they do not carry the same
|
|
4357
|
+
* value: a receipt nonce is 128 bits rendered `b64:` (WE-4, L-17), an
|
|
4358
|
+
* attestation id is a `sha256:` digest over the canonical CBOR of the whole
|
|
4359
|
+
* Attestation Object (AT-1, AT-8a), a bundle epoch is an integer high-water
|
|
4360
|
+
* mark (CL-3, RAD-3), and an origin binding is a PAIR, `proposal_hash` to
|
|
4361
|
+
* `origin_nonce` (DS-6f). Folding those into one object requires optional
|
|
4362
|
+
* fields, and an optional field is Z4 exactly as AT-8b describes it one
|
|
4363
|
+
* layer up: the same claim present-as-null and the same claim with the field
|
|
4364
|
+
* omitted are each well formed, encode differently, and any ledger that keys
|
|
4365
|
+
* or audits on the encoded claim then holds two rows for one claim. One
|
|
4366
|
+
* nonce, two slots, which is the hole CL-3 exists to close. So each class is
|
|
4367
|
+
* its own object, every field is REQUIRED, `additionalProperties` is false,
|
|
4368
|
+
* and the composition is `oneOf` rather than `anyOf` so that a document
|
|
4369
|
+
* matching two branches is REFUSED rather than resolved to the first match.
|
|
4370
|
+
*
|
|
4371
|
+
* `x-acp-absent` deliberately appears nowhere below. §9.3.1 declares no
|
|
4372
|
+
* absent-value default for a claim, and inventing one here would let a
|
|
4373
|
+
* malformed claim be normalized into a well-formed one, which is the precise
|
|
4374
|
+
* opposite of what CL-2 requires when the ledger cannot proceed. A default
|
|
4375
|
+
* that turns a refusal into an acceptance is not a fail-safe default.
|
|
4376
|
+
*
|
|
4377
|
+
* WHY `artifact_class` IS CARRIED AND NOT INFERRED FROM THE VALUE. CL-5's
|
|
4378
|
+
* key is the triple `(tenant_id, artifact_class, identifier)`, and the class
|
|
4379
|
+
* is what keeps the four namespaces disjoint. Without it, one execution per
|
|
4380
|
+
* receipt (T-09) and one receipt per attestation (T-14) stop being
|
|
4381
|
+
* independent guarantees: an identifier claimed in one class is observed as
|
|
4382
|
+
* claimed in the other, so consuming an attestation can silently consume a
|
|
4383
|
+
* nonce that was never issued, or fail to consume one that was. Inferring
|
|
4384
|
+
* the class from the value's syntax is strictly worse than carrying it,
|
|
4385
|
+
* because two of the four classes already share the `sha256:` form -- an
|
|
4386
|
+
* attestation id and the key half of an origin binding are both digests, and
|
|
4387
|
+
* nothing in the string distinguishes them.
|
|
4388
|
+
*
|
|
4389
|
+
* WHAT IS NOT A FIELD, AND WHY. (a) No timestamp. CL-8 requires identifier,
|
|
4390
|
+
* class, timestamp and Executor identity in the audit row of every claim
|
|
4391
|
+
* operation, hit and miss; it does not say the claim carries them, and F3.4
|
|
4392
|
+
* does not cite CL-8 at all. A claim-carried timestamp lets the party the
|
|
4393
|
+
* audit row is ABOUT choose the time that row is dated, which is the T-32
|
|
4394
|
+
* shape -- a component certifying a fact about itself -- while the ledger
|
|
4395
|
+
* has its own clock and is the party doing the recording. (b) No Executor
|
|
4396
|
+
* identity, for the same reason: an identity taken from the authenticated
|
|
4397
|
+
* connection (mTLS is REQUIRED between components, §4.3) is evidence, an
|
|
4398
|
+
* identity typed into the message by the sender is a name for one. (c) No
|
|
4399
|
+
* compare-and-swap comparand. CL-2 requires CAS or an equivalent atomic
|
|
4400
|
+
* primitive, but the expected value is fixed by the semantics rather than
|
|
4401
|
+
* chosen per call -- absence, for the three consumption classes, and
|
|
4402
|
+
* absence-or-the-already-adopted-value for the binding (CL-7) -- so a
|
|
4403
|
+
* transmitted expectation would let the claimant choose the bar it is
|
|
4404
|
+
* measured against. For the epoch class that is not a subtlety: a
|
|
4405
|
+
* transmitted expected high-water mark is T-15 bundle rollback with extra
|
|
4406
|
+
* steps. (d) No response. The ledger's answer travels the other way and the
|
|
4407
|
+
* register has no return row for it; that omission is recorded against this
|
|
4408
|
+
* schema rather than silently repaired by modelling a message the register
|
|
4409
|
+
* does not carry.
|
|
4410
|
+
*
|
|
4411
|
+
* WHAT THIS SCHEMA CANNOT CONSTRAIN, STATED SO THE CLAIM IS NOT READ WIDER
|
|
4412
|
+
* THAN IT IS. Every clause F3.4 cites is about BEHAVIOUR or ORDERING, and
|
|
4413
|
+
* none of them is expressible in a message shape. CL-1 linearizability; CL-2
|
|
4414
|
+
* atomicity and fail-closed when the ledger is unavailable; CL-6 fail-closed
|
|
4415
|
+
* when absence cannot be confirmed; CL-7 completing the read phase across
|
|
4416
|
+
* the quorum before mutating any replica, and never resolving a disagreement
|
|
4417
|
+
* by vote; CL-4 retention; DS-6f immutability of the binding and the
|
|
4418
|
+
* critical alert on a rebind attempt. A schema-valid claim says nothing
|
|
4419
|
+
* about any of them. They are per-implementation obligations
|
|
4420
|
+
* (`spec/vectors/OBLIGATIONS.md`), exercised by
|
|
4421
|
+
* `reference/suites/partition_suite.py` and `partition_integration.py`. An
|
|
4422
|
+
* implementation reporting "CL-7 satisfied" because a claim validated
|
|
4423
|
+
* against this file has answered a different question from the one CL-7
|
|
4424
|
+
* asks.
|
|
4425
|
+
*
|
|
4426
|
+
* THIS SCHEMA HALTED `tools/codegen.sh`, AND THE HALT IS WHAT GOT THE RULE
|
|
4427
|
+
* WRITTEN. `oneOf` was on the generator's refused list, because it emitted
|
|
4428
|
+
* no rule for a tagged union and a generated type that looked complete while
|
|
4429
|
+
* expressing none of the union would be worse than no type at all. Neither
|
|
4430
|
+
* way of making the halt go away was taken, because both damage something
|
|
4431
|
+
* real: collapsing the four classes into one object needs optional fields,
|
|
4432
|
+
* which is the Z4 defect this schema is shaped to refuse, and dropping a
|
|
4433
|
+
* class contradicts CL-3, which enumerates four. What the halt actually said
|
|
4434
|
+
* is that the ACP-78 wire surface contains a tagged union -- so in M1 the
|
|
4435
|
+
* generator was taught one. It accepts a `oneOf` whose branches are closed
|
|
4436
|
+
* objects made disjoint by a single required `const` field, PROVES that
|
|
4437
|
+
* discriminator from the schema rather than being told it, and still halts
|
|
4438
|
+
* on any `oneOf` that is not tagged. The tag is `artifact_class`, which CL-5
|
|
4439
|
+
* already makes a column of the ledger key, so nothing here exists for the
|
|
4440
|
+
* generator's benefit. What the generated union deliberately does NOT carry
|
|
4441
|
+
* is a decoder that tries branches until one fits: `first shape that fits`
|
|
4442
|
+
* is how a document meant as one artifact class is accepted as another,
|
|
4443
|
+
* which is the namespace collapse `artifact_class` exists to prevent.
|
|
4444
|
+
*
|
|
4445
|
+
* SUITE 12 CLASSIFICATION. Every field below is class T at the schema layer,
|
|
4446
|
+
* because shape is not authority and this document constrains shape. Two of
|
|
4447
|
+
* them need their residual said out loud. `tenant_id` NAMES a namespace and
|
|
4448
|
+
* does not authorise one: where a deployment runs one ledger per tenant
|
|
4449
|
+
* (CL-5) the tenant is fixed by the instance and the authenticated
|
|
4450
|
+
* connection, and a claim MUST NOT reach another tenant's namespace by
|
|
4451
|
+
* naming it. `origin_nonce` is a value being pinned for the first time,
|
|
4452
|
+
* never evidence that a binding exists -- on a re-drive the Executor MUST
|
|
4453
|
+
* READ the origin from the ledger and derive `idempotency_key` from what it
|
|
4454
|
+
* read (DS-6f, closing Z3), and that read is a different operation from this
|
|
4455
|
+
* message.
|
|
4456
|
+
* Generated from ledger_claim.schema.json.
|
|
4457
|
+
*
|
|
4458
|
+
* A DISCRIMINATED union over `artifactClass`, which every branch pins to a
|
|
4459
|
+
* literal. Narrow it by switching on that field; a `default` arm that
|
|
4460
|
+
* accepts an unrecognised value is a branch this union does not have, and
|
|
4461
|
+
* the fail-safe answer there is to refuse. There is no `Raw` union: with
|
|
4462
|
+
* every field optional each branch would accept every other document and the
|
|
4463
|
+
* tag would decide nothing.
|
|
4464
|
+
*/
|
|
4465
|
+
export type LedgerClaim = ReceiptNonceClaim | AttestationIdClaim | BundleEpochClaim | OriginBindingClaim;
|
|
4466
|
+
//# sourceMappingURL=generated_wire.d.ts.map
|