betterwithage Claude Opus 4.7 commited on
Commit
2cb0138
·
verified ·
1 Parent(s): 784993d

deploy(hf): sync szl-holdings/a11oy@a0276036e42005911c7f6257141bc49698a82639 derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy a0276036e42005911c7f6257141bc49698a82639.
Files: 1360 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Dockerfile CHANGED
@@ -500,7 +500,8 @@ COPY szl_connectors/ ./szl_connectors/
500
 
501
  # --- ESTATE ECOSYSTEM FOUNDATION (Dev5, 2026-06): byte-identical shared modules ---
502
  # … (full rationale: docs/DOCKERFILE_NOTES.md §66)
503
- COPY static/shared/szl_label_engine.js static/shared/szl_receipt_cosign.js static/shared/szl_codename_sanitizer.js static/shared/szl_holo3d.js static/shared/szl_command_bar.js static/shared/szl_command_bar.css ./static/shared/
 
504
 
505
  # --- GOVERNANCE / EVAL / CALIBRATION layer (Dev B, 2026-06): ADDITIVE ---
506
  # … (full rationale: docs/DOCKERFILE_NOTES.md §67)
 
500
 
501
  # --- ESTATE ECOSYSTEM FOUNDATION (Dev5, 2026-06): byte-identical shared modules ---
502
  # … (full rationale: docs/DOCKERFILE_NOTES.md §66)
503
+ COPY static/shared/szl_label_engine.js static/shared/szl_receipt_cosign.js static/shared/szl_codename_sanitizer.js static/shared/szl_holo3d.js static/shared/szl_command_bar.js static/shared/szl_command_bar.css static/shared/puriq_receipt_v1.js ./static/shared/
504
+ COPY schemas/puriq-receipt-v1.json ./schemas/puriq-receipt-v1.json
505
 
506
  # --- GOVERNANCE / EVAL / CALIBRATION layer (Dev B, 2026-06): ADDITIVE ---
507
  # … (full rationale: docs/DOCKERFILE_NOTES.md §67)
schemas/puriq-receipt-v1.json ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://a-11-oy.com/schemas/puriq-receipt-v1.json",
4
+ "title": "PurIQ receipt payload v1",
5
+ "description": "Exact receipt payload contract for one governed scoring event. payload_hash binds the canonical JSON object containing receipt_version through gate; payload_hash and signature are not part of that hash material.",
6
+ "$comment": "SPDX-License-Identifier: Apache-2.0; (c) 2026 Lutar, Stephen P. - SZL Holdings. Canonical JSON uses ECMAScript number/string spelling and UTF-16 code-unit key ordering, rejects lone surrogates, and does not normalize Unicode. Runtime validation additionally requires confidence.low <= confidence.high and valid session linkage. UNSIGNED is a valid receipt state, not a verified signature.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [
10
+ "receipt_version",
11
+ "receipt_id",
12
+ "issued_at",
13
+ "session_id",
14
+ "sequence",
15
+ "prev_receipt_hash",
16
+ "subject",
17
+ "ranking_inputs",
18
+ "gate",
19
+ "payload_hash",
20
+ "signature"
21
+ ],
22
+ "properties": {
23
+ "receipt_version": {
24
+ "const": 1
25
+ },
26
+ "receipt_id": {
27
+ "$ref": "#/$defs/uuid_v4"
28
+ },
29
+ "issued_at": {
30
+ "type": "string",
31
+ "format": "date-time",
32
+ "pattern": "^(?:[0-9]{4}-(?:06-30|12-31)T23:59:60(?:\\.[0-9]+)?Z|[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-5][0-9](?:\\.[0-9]+)?Z)$"
33
+ },
34
+ "session_id": {
35
+ "$ref": "#/$defs/uuid_v4"
36
+ },
37
+ "sequence": {
38
+ "type": "integer",
39
+ "minimum": 0,
40
+ "maximum": 9007199254740991
41
+ },
42
+ "prev_receipt_hash": {
43
+ "oneOf": [
44
+ {
45
+ "const": "GENESIS"
46
+ },
47
+ {
48
+ "$ref": "#/$defs/sha256_hex"
49
+ }
50
+ ]
51
+ },
52
+ "subject": {
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "required": [
56
+ "normalized_record_hash",
57
+ "source_record_id",
58
+ "parser_version"
59
+ ],
60
+ "properties": {
61
+ "normalized_record_hash": {
62
+ "$ref": "#/$defs/sha256_hex"
63
+ },
64
+ "source_record_id": {
65
+ "type": "string"
66
+ },
67
+ "parser_version": {
68
+ "type": "string",
69
+ "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$"
70
+ }
71
+ }
72
+ },
73
+ "ranking_inputs": {
74
+ "type": "object",
75
+ "additionalProperties": false,
76
+ "required": [
77
+ "source_path",
78
+ "reasons",
79
+ "confidence",
80
+ "caveats"
81
+ ],
82
+ "properties": {
83
+ "source_path": {
84
+ "type": "array",
85
+ "items": {
86
+ "type": "string"
87
+ }
88
+ },
89
+ "reasons": {
90
+ "type": "array",
91
+ "items": {
92
+ "type": "object",
93
+ "additionalProperties": false,
94
+ "required": [
95
+ "code",
96
+ "direction",
97
+ "weight",
98
+ "detail"
99
+ ],
100
+ "properties": {
101
+ "code": {
102
+ "type": "string"
103
+ },
104
+ "direction": {
105
+ "enum": [
106
+ "up",
107
+ "down"
108
+ ]
109
+ },
110
+ "weight": {
111
+ "type": "number"
112
+ },
113
+ "detail": {
114
+ "type": "string"
115
+ }
116
+ }
117
+ }
118
+ },
119
+ "confidence": {
120
+ "type": "object",
121
+ "additionalProperties": false,
122
+ "required": [
123
+ "low",
124
+ "high"
125
+ ],
126
+ "properties": {
127
+ "low": {
128
+ "type": "number",
129
+ "minimum": 0,
130
+ "maximum": 1
131
+ },
132
+ "high": {
133
+ "type": "number",
134
+ "minimum": 0,
135
+ "maximum": 1
136
+ }
137
+ }
138
+ },
139
+ "caveats": {
140
+ "type": "array",
141
+ "items": {
142
+ "type": "string"
143
+ }
144
+ }
145
+ }
146
+ },
147
+ "gate": {
148
+ "type": "object",
149
+ "additionalProperties": false,
150
+ "required": [
151
+ "name",
152
+ "result",
153
+ "failures"
154
+ ],
155
+ "properties": {
156
+ "name": {
157
+ "const": "yuyay-13"
158
+ },
159
+ "result": {
160
+ "enum": [
161
+ "pass",
162
+ "fail"
163
+ ]
164
+ },
165
+ "failures": {
166
+ "type": "array",
167
+ "items": {
168
+ "type": "string"
169
+ }
170
+ }
171
+ },
172
+ "allOf": [
173
+ {
174
+ "if": {
175
+ "properties": {
176
+ "result": {
177
+ "const": "pass"
178
+ }
179
+ },
180
+ "required": [
181
+ "result"
182
+ ]
183
+ },
184
+ "then": {
185
+ "properties": {
186
+ "failures": {
187
+ "type": "array",
188
+ "maxItems": 0
189
+ }
190
+ }
191
+ },
192
+ "else": {
193
+ "properties": {
194
+ "failures": {
195
+ "type": "array",
196
+ "minItems": 1
197
+ }
198
+ }
199
+ }
200
+ }
201
+ ]
202
+ },
203
+ "payload_hash": {
204
+ "$ref": "#/$defs/sha256_hex"
205
+ },
206
+ "signature": {
207
+ "type": "object",
208
+ "additionalProperties": false,
209
+ "required": [
210
+ "algorithm",
211
+ "key_id",
212
+ "value"
213
+ ],
214
+ "properties": {
215
+ "algorithm": {
216
+ "const": "HMAC-SHA256"
217
+ },
218
+ "key_id": {
219
+ "type": [
220
+ "string",
221
+ "null"
222
+ ]
223
+ },
224
+ "value": {
225
+ "oneOf": [
226
+ {
227
+ "const": "UNSIGNED"
228
+ },
229
+ {
230
+ "type": "string",
231
+ "pattern": "^[0-9a-f]{64}$"
232
+ }
233
+ ]
234
+ }
235
+ },
236
+ "oneOf": [
237
+ {
238
+ "properties": {
239
+ "key_id": {
240
+ "const": null
241
+ },
242
+ "value": {
243
+ "const": "UNSIGNED"
244
+ }
245
+ }
246
+ },
247
+ {
248
+ "properties": {
249
+ "key_id": {
250
+ "type": "string",
251
+ "minLength": 1
252
+ },
253
+ "value": {
254
+ "type": "string",
255
+ "pattern": "^[0-9a-f]{64}$"
256
+ }
257
+ }
258
+ }
259
+ ]
260
+ }
261
+ },
262
+ "$defs": {
263
+ "sha256_hex": {
264
+ "type": "string",
265
+ "pattern": "^[0-9a-f]{64}$"
266
+ },
267
+ "uuid_v4": {
268
+ "type": "string",
269
+ "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
270
+ }
271
+ }
272
+ }
serve.py CHANGED
@@ -13050,6 +13050,7 @@ try:
13050
  "szl_holo3d.js": _VENDOR_JS_CT,
13051
  "szl_command_bar.js": _VENDOR_JS_CT,
13052
  "szl_command_bar.css": _VENDOR_CSS_CT,
 
13053
  }
13054
 
13055
  @app.get("/static/shared/{fname}")
@@ -13063,9 +13064,40 @@ try:
13063
  return _VendResponse(content=f.read_bytes(), media_type=ct,
13064
  headers={"Cache-Control": "public, max-age=3600"})
13065
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13066
  import sys as _vend_sys
13067
  print("[a11oy] AIR-GAP vendor routes registered: /vendor/{7 libs+KaTeX}, "
13068
- "/vendor/earth-night.jpg, /vendor/fonts/*, /static/shared/{3 SZL modules} (NO CDN — Warhacker #2 Tychee)", file=_vend_sys.stderr)
 
13069
  except Exception as _vend_e: # never crash the app — additive only
13070
  import sys as _vend_sys, traceback as _vend_tb
13071
  print(f"[a11oy] AIR-GAP vendor routes NOT registered: {_vend_e!r}", file=_vend_sys.stderr)
 
13050
  "szl_holo3d.js": _VENDOR_JS_CT,
13051
  "szl_command_bar.js": _VENDOR_JS_CT,
13052
  "szl_command_bar.css": _VENDOR_CSS_CT,
13053
+ "puriq_receipt_v1.js": _VENDOR_JS_CT,
13054
  }
13055
 
13056
  @app.get("/static/shared/{fname}")
 
13064
  return _VendResponse(content=f.read_bytes(), media_type=ct,
13065
  headers={"Cache-Control": "public, max-age=3600"})
13066
 
13067
+ # Public schema identifiers are served from an explicit allowlist. Keeping
13068
+ # this route ahead of the SPA catch-all prevents a missing schema from being
13069
+ # disguised as an HTML 200 and keeps unrelated repository schemas private.
13070
+ _SCHEMA_DIR = Path("/app/schemas")
13071
+ if not _SCHEMA_DIR.is_dir():
13072
+ _SCHEMA_DIR = Path(__file__).resolve().parent / "schemas"
13073
+ _SCHEMA_ALLOW = {
13074
+ "puriq-receipt-v1.json": "application/schema+json",
13075
+ }
13076
+
13077
+ @app.get("/schemas/{fname}")
13078
+ async def _public_schema(fname: str):
13079
+ ct = _SCHEMA_ALLOW.get(fname)
13080
+ if ct is None:
13081
+ return JSONResponse(
13082
+ {"error": "schema not allowlisted", "file": fname},
13083
+ status_code=404,
13084
+ )
13085
+ f = _SCHEMA_DIR / fname
13086
+ if not f.is_file():
13087
+ return JSONResponse(
13088
+ {"error": "schema missing on disk", "file": fname},
13089
+ status_code=404,
13090
+ )
13091
+ return _VendResponse(
13092
+ content=f.read_bytes(),
13093
+ media_type=ct,
13094
+ headers={"Cache-Control": "public, max-age=31536000, immutable"},
13095
+ )
13096
+
13097
  import sys as _vend_sys
13098
  print("[a11oy] AIR-GAP vendor routes registered: /vendor/{7 libs+KaTeX}, "
13099
+ "/vendor/earth-night.jpg, /vendor/fonts/*, /static/shared/{allowlisted modules}, "
13100
+ "/schemas/{allowlisted schemas} (NO CDN — Warhacker #2 Tychee)", file=_vend_sys.stderr)
13101
  except Exception as _vend_e: # never crash the app — additive only
13102
  import sys as _vend_sys, traceback as _vend_tb
13103
  print(f"[a11oy] AIR-GAP vendor routes NOT registered: {_vend_e!r}", file=_vend_sys.stderr)
static/shared/puriq_receipt_v1.js ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* SPDX-License-Identifier: Apache-2.0
2
+ * (c) 2026 Lutar, Stephen P. - SZL Holdings - ORCID 0009-0001-0110-4173
3
+ *
4
+ * PurIQ receipt v1 canonical serializer and client-side session verifier.
5
+ * Browser-native Web Crypto; the same module is consumable from Node tests.
6
+ *
7
+ * Hash material is the exact object containing receipt_version through gate.
8
+ * payload_hash and signature are deliberately outside that material. Signed
9
+ * receipts require a server verification callback because signing key material
10
+ * never enters the browser. Honest UNSIGNED receipts require key_id === null.
11
+ * Canonical encoding uses ECMAScript JSON number/string spelling and UTF-16
12
+ * code-unit key ordering. Lone surrogates are rejected; Unicode is not normalized.
13
+ */
14
+ (function (root, factory) {
15
+ var api = factory();
16
+ if (typeof module === "object" && module.exports) {
17
+ module.exports = api;
18
+ }
19
+ if (root) {
20
+ root.PurIQReceiptV1 = api;
21
+ }
22
+ })(
23
+ typeof self !== "undefined"
24
+ ? self
25
+ : typeof window !== "undefined"
26
+ ? window
27
+ : null,
28
+ function () {
29
+ "use strict";
30
+
31
+ var TOP_LEVEL_KEYS = [
32
+ "gate",
33
+ "issued_at",
34
+ "payload_hash",
35
+ "prev_receipt_hash",
36
+ "ranking_inputs",
37
+ "receipt_id",
38
+ "receipt_version",
39
+ "sequence",
40
+ "session_id",
41
+ "signature",
42
+ "subject"
43
+ ];
44
+ var MATERIAL_KEYS = [
45
+ "gate",
46
+ "issued_at",
47
+ "prev_receipt_hash",
48
+ "ranking_inputs",
49
+ "receipt_id",
50
+ "receipt_version",
51
+ "sequence",
52
+ "session_id",
53
+ "subject"
54
+ ];
55
+ var SHA256_HEX = /^[0-9a-f]{64}$/;
56
+ var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
57
+ var RFC3339_UTC = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?Z$/;
58
+ var SEMVER = /^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
59
+
60
+ function _validUnicode(value) {
61
+ for (var i = 0; i < value.length; i += 1) {
62
+ var code = value.charCodeAt(i);
63
+ if (code >= 0xd800 && code <= 0xdbff) {
64
+ var next = value.charCodeAt(i + 1);
65
+ if (!(next >= 0xdc00 && next <= 0xdfff)) return false;
66
+ i += 1;
67
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+
74
+ function _validTimestamp(value) {
75
+ if (typeof value !== "string" || !RFC3339_UTC.test(value)) return false;
76
+ var year = Number(value.slice(0, 4));
77
+ var month = Number(value.slice(5, 7));
78
+ var day = Number(value.slice(8, 10));
79
+ var hour = Number(value.slice(11, 13));
80
+ var minute = Number(value.slice(14, 16));
81
+ var second = Number(value.slice(17, 19));
82
+ var leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
83
+ var days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
84
+ return month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1] &&
85
+ hour <= 23 && minute <= 59 &&
86
+ (second <= 59 ||
87
+ (second === 60 && hour === 23 && minute === 59 &&
88
+ ((month === 6 && day === 30) || (month === 12 && day === 31))));
89
+ }
90
+
91
+ function _isPlainObject(value) {
92
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
93
+ return false;
94
+ }
95
+ var proto = Object.getPrototypeOf(value);
96
+ return proto === Object.prototype || proto === null;
97
+ }
98
+
99
+ function _canonical(value, seen) {
100
+ if (typeof value === "string" && !_validUnicode(value)) {
101
+ throw new TypeError("canonical JSON rejects lone Unicode surrogates");
102
+ }
103
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
104
+ return JSON.stringify(value);
105
+ }
106
+ if (typeof value === "number") {
107
+ if (!Number.isFinite(value)) {
108
+ throw new TypeError("canonical JSON rejects non-finite numbers");
109
+ }
110
+ return JSON.stringify(value);
111
+ }
112
+ if (typeof value !== "object") {
113
+ throw new TypeError("canonical JSON accepts JSON values only");
114
+ }
115
+ if (seen.indexOf(value) !== -1) {
116
+ throw new TypeError("canonical JSON rejects circular structures");
117
+ }
118
+ seen.push(value);
119
+ var result;
120
+ if (Array.isArray(value)) {
121
+ var items = [];
122
+ for (var i = 0; i < value.length; i += 1) {
123
+ items.push(_canonical(value[i], seen));
124
+ }
125
+ result = "[" + items.join(",") + "]";
126
+ } else {
127
+ if (!_isPlainObject(value)) {
128
+ throw new TypeError("canonical JSON accepts plain objects only");
129
+ }
130
+ var keys = Object.keys(value).sort();
131
+ var parts = [];
132
+ for (var j = 0; j < keys.length; j += 1) {
133
+ var key = keys[j];
134
+ if (!_validUnicode(key)) {
135
+ throw new TypeError("canonical JSON rejects lone Unicode surrogates");
136
+ }
137
+ parts.push(JSON.stringify(key) + ":" + _canonical(value[key], seen));
138
+ }
139
+ result = "{" + parts.join(",") + "}";
140
+ }
141
+ seen.pop();
142
+ return result;
143
+ }
144
+
145
+ function canonicalJSON(value) {
146
+ return _canonical(value, []);
147
+ }
148
+
149
+ function _subtle() {
150
+ var cryptoObject =
151
+ typeof globalThis !== "undefined" && globalThis.crypto
152
+ ? globalThis.crypto
153
+ : null;
154
+ return cryptoObject && cryptoObject.subtle ? cryptoObject.subtle : null;
155
+ }
156
+
157
+ function _utf8(value) {
158
+ if (typeof TextEncoder === "undefined") {
159
+ throw new Error("TextEncoder unavailable");
160
+ }
161
+ return new TextEncoder().encode(value);
162
+ }
163
+
164
+ function _hex(buffer) {
165
+ var bytes = new Uint8Array(buffer);
166
+ var output = "";
167
+ for (var i = 0; i < bytes.length; i += 1) {
168
+ output += bytes[i].toString(16).padStart(2, "0");
169
+ }
170
+ return output;
171
+ }
172
+
173
+ function sha256Hex(value) {
174
+ var subtle = _subtle();
175
+ if (!subtle) {
176
+ return Promise.reject(
177
+ new Error("Web Crypto SubtleCrypto unavailable; use a secure browser context")
178
+ );
179
+ }
180
+ return subtle.digest("SHA-256", _utf8(String(value))).then(_hex);
181
+ }
182
+
183
+ function _sameKeys(value, expected) {
184
+ if (!_isPlainObject(value)) {
185
+ return false;
186
+ }
187
+ var actual = Object.keys(value).sort();
188
+ if (actual.length !== expected.length) {
189
+ return false;
190
+ }
191
+ for (var i = 0; i < expected.length; i += 1) {
192
+ if (actual[i] !== expected[i]) {
193
+ return false;
194
+ }
195
+ }
196
+ return true;
197
+ }
198
+
199
+ function _allStrings(value) {
200
+ if (!Array.isArray(value)) {
201
+ return false;
202
+ }
203
+ for (var i = 0; i < value.length; i += 1) {
204
+ if (typeof value[i] !== "string") {
205
+ return false;
206
+ }
207
+ }
208
+ return true;
209
+ }
210
+
211
+ function _allUnicodeValid(value, seen) {
212
+ if (typeof value === "string") {
213
+ return _validUnicode(value);
214
+ }
215
+ if (value === null || typeof value !== "object") {
216
+ return true;
217
+ }
218
+ if (seen.indexOf(value) !== -1) {
219
+ return false;
220
+ }
221
+ seen.push(value);
222
+ var valid = true;
223
+ if (Array.isArray(value)) {
224
+ for (var index = 0; index < value.length && valid; index += 1) {
225
+ valid = _allUnicodeValid(value[index], seen);
226
+ }
227
+ } else if (_isPlainObject(value)) {
228
+ var keys = Object.keys(value);
229
+ for (var keyIndex = 0; keyIndex < keys.length && valid; keyIndex += 1) {
230
+ valid = _validUnicode(keys[keyIndex]) &&
231
+ _allUnicodeValid(value[keys[keyIndex]], seen);
232
+ }
233
+ }
234
+ seen.pop();
235
+ return valid;
236
+ }
237
+
238
+ function _receiptErrors(receipt) {
239
+ var errors = [];
240
+ if (!_sameKeys(receipt, TOP_LEVEL_KEYS)) {
241
+ return ["receipt_shape"];
242
+ }
243
+ if (!_allUnicodeValid(receipt, [])) errors.push("unicode");
244
+ if (receipt.receipt_version !== 1) errors.push("receipt_version");
245
+ if (typeof receipt.receipt_id !== "string" || !UUID_V4.test(receipt.receipt_id)) {
246
+ errors.push("receipt_id");
247
+ }
248
+ if (!_validTimestamp(receipt.issued_at)) {
249
+ errors.push("issued_at");
250
+ }
251
+ if (typeof receipt.session_id !== "string" || !UUID_V4.test(receipt.session_id)) {
252
+ errors.push("session_id");
253
+ }
254
+ if (!Number.isSafeInteger(receipt.sequence) || receipt.sequence < 0) {
255
+ errors.push("sequence");
256
+ }
257
+ if (
258
+ receipt.prev_receipt_hash !== "GENESIS" &&
259
+ (typeof receipt.prev_receipt_hash !== "string" ||
260
+ !SHA256_HEX.test(receipt.prev_receipt_hash))
261
+ ) {
262
+ errors.push("prev_receipt_hash");
263
+ }
264
+
265
+ if (!_sameKeys(receipt.subject, [
266
+ "normalized_record_hash",
267
+ "parser_version",
268
+ "source_record_id"
269
+ ])) {
270
+ errors.push("subject_shape");
271
+ } else {
272
+ if (typeof receipt.subject.normalized_record_hash !== "string" ||
273
+ !SHA256_HEX.test(receipt.subject.normalized_record_hash)) {
274
+ errors.push("normalized_record_hash");
275
+ }
276
+ if (typeof receipt.subject.source_record_id !== "string") {
277
+ errors.push("source_record_id");
278
+ }
279
+ if (
280
+ typeof receipt.subject.parser_version !== "string" ||
281
+ !SEMVER.test(receipt.subject.parser_version)
282
+ ) {
283
+ errors.push("parser_version");
284
+ }
285
+ }
286
+
287
+ if (!_sameKeys(receipt.ranking_inputs, [
288
+ "caveats",
289
+ "confidence",
290
+ "reasons",
291
+ "source_path"
292
+ ])) {
293
+ errors.push("ranking_inputs_shape");
294
+ } else {
295
+ if (!_allStrings(receipt.ranking_inputs.source_path)) errors.push("source_path");
296
+ if (!_allStrings(receipt.ranking_inputs.caveats)) errors.push("caveats");
297
+ if (!Array.isArray(receipt.ranking_inputs.reasons)) {
298
+ errors.push("reasons");
299
+ } else {
300
+ for (var reasonIndex = 0; reasonIndex < receipt.ranking_inputs.reasons.length; reasonIndex += 1) {
301
+ var reason = receipt.ranking_inputs.reasons[reasonIndex];
302
+ if (!_sameKeys(reason, ["code", "detail", "direction", "weight"])) {
303
+ errors.push("reason_shape");
304
+ break;
305
+ }
306
+ if (
307
+ typeof reason.code !== "string" ||
308
+ (reason.direction !== "up" && reason.direction !== "down") ||
309
+ typeof reason.weight !== "number" ||
310
+ !Number.isFinite(reason.weight) ||
311
+ typeof reason.detail !== "string"
312
+ ) {
313
+ errors.push("reason_value");
314
+ break;
315
+ }
316
+ }
317
+ }
318
+ var confidence = receipt.ranking_inputs.confidence;
319
+ if (!_sameKeys(confidence, ["high", "low"])) {
320
+ errors.push("confidence_shape");
321
+ } else if (
322
+ typeof confidence.low !== "number" ||
323
+ !Number.isFinite(confidence.low) ||
324
+ confidence.low < 0 ||
325
+ confidence.low > 1 ||
326
+ typeof confidence.high !== "number" ||
327
+ !Number.isFinite(confidence.high) ||
328
+ confidence.high < 0 ||
329
+ confidence.high > 1 ||
330
+ confidence.low > confidence.high
331
+ ) {
332
+ errors.push("confidence");
333
+ }
334
+ }
335
+
336
+ if (!_sameKeys(receipt.gate, ["failures", "name", "result"])) {
337
+ errors.push("gate_shape");
338
+ } else {
339
+ if (receipt.gate.name !== "yuyay-13") errors.push("gate_name");
340
+ if (receipt.gate.result !== "pass" && receipt.gate.result !== "fail") {
341
+ errors.push("gate_result");
342
+ }
343
+ if (!_allStrings(receipt.gate.failures)) errors.push("gate_failures");
344
+ if (Array.isArray(receipt.gate.failures) && receipt.gate.result === "pass" && receipt.gate.failures.length !== 0) {
345
+ errors.push("gate_failures_on_pass");
346
+ }
347
+ if (Array.isArray(receipt.gate.failures) && receipt.gate.result === "fail" && receipt.gate.failures.length === 0) {
348
+ errors.push("gate_failures_on_fail");
349
+ }
350
+ }
351
+
352
+ if (typeof receipt.payload_hash !== "string" || !SHA256_HEX.test(receipt.payload_hash)) {
353
+ errors.push("payload_hash");
354
+ }
355
+ if (!_sameKeys(receipt.signature, ["algorithm", "key_id", "value"])) {
356
+ errors.push("signature_shape");
357
+ } else {
358
+ if (receipt.signature.algorithm !== "HMAC-SHA256") errors.push("signature_algorithm");
359
+ if (receipt.signature.value === "UNSIGNED") {
360
+ if (receipt.signature.key_id !== null) errors.push("unsigned_key_id");
361
+ } else {
362
+ if (typeof receipt.signature.value !== "string" || !SHA256_HEX.test(receipt.signature.value)) errors.push("signature_value");
363
+ if (typeof receipt.signature.key_id !== "string" || receipt.signature.key_id.length === 0) {
364
+ errors.push("signature_key_id");
365
+ }
366
+ }
367
+ }
368
+ return errors;
369
+ }
370
+
371
+ function payloadMaterial(receipt) {
372
+ if (!_sameKeys(receipt, TOP_LEVEL_KEYS)) {
373
+ throw new TypeError("receipt must contain exactly the PurIQ v1 fields");
374
+ }
375
+ var material = {};
376
+ for (var i = 0; i < MATERIAL_KEYS.length; i += 1) {
377
+ var key = MATERIAL_KEYS[i];
378
+ material[key] = receipt[key];
379
+ }
380
+ return material;
381
+ }
382
+
383
+ function computePayloadHash(receipt) {
384
+ var errors = _receiptErrors(receipt);
385
+ if (errors.length !== 0) {
386
+ return Promise.reject(new TypeError("invalid PurIQ v1 receipt: " + errors.join(",")));
387
+ }
388
+ return sha256Hex(canonicalJSON(payloadMaterial(receipt)));
389
+ }
390
+
391
+ async function verifySession(receipts, options) {
392
+ options = options || {};
393
+ if (!Array.isArray(receipts)) {
394
+ throw new TypeError("receipts must be an array");
395
+ }
396
+ var signatureVerifier = options.verifySignature;
397
+ var expectedSessionId = receipts.length > 0 && _isPlainObject(receipts[0])
398
+ ? receipts[0].session_id : null;
399
+ var results = [];
400
+ var firstFailureReceiptId = null;
401
+ var firstFailureIndex = null;
402
+ var upstreamInvalid = false;
403
+
404
+ for (var index = 0; index < receipts.length; index += 1) {
405
+ var receipt = receipts[index];
406
+ var errors = _receiptErrors(receipt);
407
+ var expectedHash = null;
408
+ var payloadHashValid = false;
409
+ var sequenceValid = false;
410
+ var linkValid = false;
411
+ var signatureValid = false;
412
+ var signatureState = "INVALID";
413
+
414
+ if (upstreamInvalid) errors.push("upstream_invalid");
415
+ if (_isPlainObject(receipt)) {
416
+ sequenceValid = receipt.sequence === index;
417
+ if (!sequenceValid) errors.push("sequence_not_contiguous");
418
+ if (receipt.session_id !== expectedSessionId) errors.push("session_id_mismatch");
419
+ var priorReceipt = index === 0 ? null : receipts[index - 1];
420
+ var expectedPrevious =
421
+ index === 0
422
+ ? "GENESIS"
423
+ : _isPlainObject(priorReceipt)
424
+ ? priorReceipt.payload_hash
425
+ : null;
426
+ linkValid = receipt.prev_receipt_hash === expectedPrevious;
427
+ if (!linkValid) errors.push("prev_receipt_hash_mismatch");
428
+ if (receipt.gate && receipt.gate.result !== "pass") errors.push("gate_failed_receipt");
429
+ }
430
+
431
+ if (errors.indexOf("receipt_shape") === -1 && errors.indexOf("unicode") === -1) {
432
+ try {
433
+ expectedHash = await sha256Hex(canonicalJSON(payloadMaterial(receipt)));
434
+ payloadHashValid = receipt.payload_hash === expectedHash;
435
+ if (!payloadHashValid) errors.push("payload_hash_mismatch");
436
+ } catch (error) {
437
+ errors.push("payload_hash_uncomputable");
438
+ }
439
+ }
440
+
441
+ if (_isPlainObject(receipt) && _isPlainObject(receipt.signature)) {
442
+ if (receipt.signature.value === "UNSIGNED" && receipt.signature.key_id === null) {
443
+ signatureValid = null;
444
+ signatureState = receipt.signature.algorithm === "HMAC-SHA256" ? "UNSIGNED" : "INVALID";
445
+ } else if (
446
+ receipt.signature.algorithm === "HMAC-SHA256" &&
447
+ SHA256_HEX.test(receipt.signature.value || "") &&
448
+ typeof receipt.signature.key_id === "string" &&
449
+ receipt.signature.key_id.length > 0
450
+ ) {
451
+ if (typeof signatureVerifier !== "function") {
452
+ signatureState = "UNVERIFIED";
453
+ errors.push("signed_receipt_unverified");
454
+ } else {
455
+ try {
456
+ signatureValid =
457
+ (await signatureVerifier({
458
+ receipt_id: receipt.receipt_id,
459
+ payload_hash: receipt.payload_hash,
460
+ key_id: receipt.signature.key_id,
461
+ signature: receipt.signature.value
462
+ })) === true;
463
+ signatureState = signatureValid ? "VERIFIED" : "INVALID";
464
+ if (!signatureValid) errors.push("signature_invalid");
465
+ } catch (error) {
466
+ errors.push("signature_verification_error");
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ errors = Array.from(new Set(errors));
473
+ var valid = errors.length === 0;
474
+ if (!valid && firstFailureIndex === null) {
475
+ firstFailureIndex = index;
476
+ firstFailureReceiptId =
477
+ _isPlainObject(receipt) && typeof receipt.receipt_id === "string"
478
+ ? receipt.receipt_id
479
+ : null;
480
+ }
481
+ if (!valid) upstreamInvalid = true;
482
+ results.push({
483
+ receipt_id:
484
+ _isPlainObject(receipt) && typeof receipt.receipt_id === "string"
485
+ ? receipt.receipt_id
486
+ : null,
487
+ sequence: _isPlainObject(receipt) ? receipt.sequence : null,
488
+ valid: valid,
489
+ payload_hash_valid: payloadHashValid,
490
+ sequence_valid: sequenceValid,
491
+ link_valid: linkValid,
492
+ signature_valid: signatureValid,
493
+ signature_state: signatureState,
494
+ expected_payload_hash: expectedHash,
495
+ errors: errors
496
+ });
497
+ }
498
+
499
+ return {
500
+ valid: results.every(function (result) { return result.valid; }),
501
+ receipt_count: receipts.length,
502
+ first_failure_receipt_id: firstFailureReceiptId,
503
+ first_failure_index: firstFailureIndex,
504
+ results: results
505
+ };
506
+ }
507
+
508
+ return Object.freeze({
509
+ VERSION: 1,
510
+ canonicalJSON: canonicalJSON,
511
+ sha256Hex: sha256Hex,
512
+ payloadMaterial: payloadMaterial,
513
+ computePayloadHash: computePayloadHash,
514
+ validateReceipt: function (receipt) {
515
+ return _receiptErrors(receipt).slice();
516
+ },
517
+ verifySession: verifySession
518
+ });
519
+ }
520
+ );