@openwop/openwop-conformance 2.37.1 → 2.38.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.
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
3
3
  "package": "@openwop/spec-artifacts",
4
- "version": "2.37.1",
5
- "corpusTag": "v2.37.1",
4
+ "version": "2.38.0",
5
+ "corpusTag": "v2.38.0",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "6366b5514be71098e9ea41fc81ff52bc39db1a547b353c0b344cc2a2bf5370aa",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "02711723089859b95144d788c0caabf215305570c7d48be1da5d2a5e21eff4e1",
13
- "api/v2/asyncapi.yaml": "d492342046d1ec3a2dc43a28c4cf6d821d6319c30a8552cbc8fcd13806f7b2df",
14
- "api/v2/openapi.yaml": "ba67b61f0e716444308ef7bc00416b8eee5829c28b67c5070358d35c3ca64d36",
12
+ "api/seams-v2.yaml": "4d7a6a93b84ad4d8602021fb1417c1436b168002d9e9811079cdd8f42af1f68e",
13
+ "api/v2/asyncapi.yaml": "c8bc549b600c858a97ec7f9638ab89bdae08d4e5adf9dd78f8ce412921b2a7e0",
14
+ "api/v2/openapi.yaml": "e36c19ccb3d107b3afb8b45c5107a5d9ac56e6e6c23202d83d3bed14518825b8",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -98,7 +98,7 @@
98
98
  "schemas/trigger-subscription-registration.schema.json": "929d628bad08e1def696e47e9a148aba00fa801e28d49bbd0dc2360b08673435",
99
99
  "schemas/trigger-subscription.schema.json": "4f7b067f20032073b140c4bb15dc9a9914c4f6421a252ff1ff261cbdd8edf270",
100
100
  "schemas/ui-plugin-message.schema.json": "346638babf1f8ea7b161cde75c0038b518cd2cc5bf0631772e20a9b74c96b5dd",
101
- "schemas/v2/a2a-task-state.schema.json": "38e622a7edffda4e9361af0a9f11192f6e214efbb00f508fc5382e3c068139f5",
101
+ "schemas/v2/a2a-task-state.schema.json": "1810363e2d9bddb5997923bdac4044cc17c6f46ec5a40515d2ff21d9c82c1ad9",
102
102
  "schemas/v2/a2ui-surface-delta-frame.schema.json": "6889c5da3fe7e76cb614038c0eea6774776fa640448558b258eb87fb50cb9b64",
103
103
  "schemas/v2/agent-deployment-transition.schema.json": "93a334ff042d51378c89fdd7134664271bf9592419bb5e3e00821ed06bc1904e",
104
104
  "schemas/v2/agent-deployment.schema.json": "d59b43fb62b522005ee22bc3bc53775a6777493e5e42d71812abc347625fa7fe",
@@ -112,20 +112,20 @@
112
112
  "schemas/v2/ai-envelope.schema.json": "0dc7dfbf032a37239ffd81fc2d67f999a40782ff5074866a56e414164cdee809",
113
113
  "schemas/v2/annotation-create.schema.json": "ec32c22a243b343eeb6956a23be2d76ba5b50c8018ec3ec9d6efa5a1d9b0c792",
114
114
  "schemas/v2/annotation.schema.json": "eb4813cdaa8e325412cfcaeeee5c8a28f3751c946dec64e546c7ba0ad3aad8d4",
115
- "schemas/v2/artifact-type-pack-manifest.schema.json": "862c03bcfcae87db0c8eb9114dd5058f5bced3ec93e19c6affcfcce6e1a9ba2b",
115
+ "schemas/v2/artifact-type-pack-manifest.schema.json": "c8820351a2ab93855bd654ad3d8a9788f97975d09b3a7a0418e374ed6114595d",
116
116
  "schemas/v2/artifact.schema.json": "85b9dc284d39b99d2a66fa8cbf47f2dd3d8526d3bd6cc411ad315b372de38975",
117
117
  "schemas/v2/audit-verify-result.schema.json": "a748dfadb5155adfde8a91045e76b0db6695005fe559035d71670fdd55edb3ba",
118
118
  "schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
119
- "schemas/v2/capabilities.schema.json": "79ddbbda4686fe66ec5b0aaf9bafb2385e2f8ccbb1ee39087d75cef9fd1f1628",
120
- "schemas/v2/certification-bundle.schema.json": "cb8ddf29a008b1c2e731fd61f855bf072790b700e458c702c40434b6fd47ba73",
119
+ "schemas/v2/capabilities.schema.json": "740944d7f0a00c91308449f3a3f52fa7b91b16b8fd36a0171b917afe9f4b0f09",
120
+ "schemas/v2/certification-bundle.schema.json": "67ac56bbf5f8deeec58b32e8d7bc9ce854a6a8c3cd81693e65192e5c3b726154",
121
121
  "schemas/v2/channel-presence-payload.schema.json": "4eeb224910e6f7eee3ab5e7c3899559c25c83a26d0022e0ad195c4672611977a",
122
122
  "schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
123
- "schemas/v2/chat-card-pack-manifest.schema.json": "f7cf09b30d3e1d2251620d84ea9f65541438d015ab44aec10d1dcfe68d8bd496",
123
+ "schemas/v2/chat-card-pack-manifest.schema.json": "92a57db2fd4b2463f14ef155fef8e3f18afc78960b646459dcf32efc79ff6bf5",
124
124
  "schemas/v2/compact-tool-descriptor.schema.json": "b5c418d72082d5b713bf318212a83397e5be98e56d25ca056302b41b8dd8273b",
125
125
  "schemas/v2/compensation-policy.schema.json": "b3c1142637f5bf980f2b0bb67adb47e8cb7abb99a0405a4b6816ccf5d9c089d6",
126
126
  "schemas/v2/compensation-projection.schema.json": "f46977c7076347b5473faa8c4bbe73d8321c33b33c98fff2f26ee7cbada4ed98",
127
127
  "schemas/v2/configurable.schema.json": "33a6884047e2490f723733bf8b74d3b9164e00374cfea6dc3eac5a70dc368861",
128
- "schemas/v2/connection-pack-manifest.schema.json": "bb61e88d4dd2ad572eb84dba618f949e5276852c8be3f0259f25473ce146e59f",
128
+ "schemas/v2/connection-pack-manifest.schema.json": "8d019de6bd0c5b14ea3b227580734ec792521493b396a850f7f9217aa7a6a547",
129
129
  "schemas/v2/conversation-event.schema.json": "9ad4c57285818edbf38b17816de0c4162f777c835f59e68702d0367a81cbccf6",
130
130
  "schemas/v2/conversation-turn.schema.json": "75bb6a4a03072a958feb5b34e2a1f40471e9e3996a81fa430b65717f0773c9a4",
131
131
  "schemas/v2/credential-provenance.schema.json": "f86b09e94b1c524dec7d1c6353965665ae8891e9517faef6e742d639c5578c99",
@@ -145,7 +145,7 @@
145
145
  "schemas/v2/error-envelope.schema.json": "217fa41cfe5a664e49d0b4dd97d3185b9eaaec9585bc4392aaf93175812ffbee",
146
146
  "schemas/v2/eval-summary.schema.json": "2f384daf05b8f3b4f604688cae504d6d9971ba41b8c771e55fe9a17e09e86b7a",
147
147
  "schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
148
- "schemas/v2/form-content-pack-manifest.schema.json": "8ec157cf51241d4df800398e811d2c35addd076b929c58be3ce3dcd671c67641",
148
+ "schemas/v2/form-content-pack-manifest.schema.json": "a1942a124e250a6e9fbdb6b811c5a125e78fbcbcaa46dfc71d0636f24710a339",
149
149
  "schemas/v2/frontend-plugin-manifest.schema.json": "fed6b8ddb3056c53b1bfc91525410a4445a3c37a8bf9eb9f6f9964ffea26d993",
150
150
  "schemas/v2/goal.schema.json": "ad104ed5a3866736b8fed9d7c0b23dc82af526b5bef4afe848f01fc3af2161e9",
151
151
  "schemas/v2/heartbeat-evaluated.schema.json": "4086b1ba1d0547c08b038dfb263ac89416a3e406430db1250156fb2f9b74f8d6",
@@ -157,21 +157,21 @@
157
157
  "schemas/v2/localized-content-section.schema.json": "b52cfe36c857c2800f71c480dcd7323e1a29783ecdc9f2440bc8e75a5a034346",
158
158
  "schemas/v2/memory-entry.schema.json": "290576ae84f4fef4cdf975932b87963283b11c50ea3d2654b32cd96aa6537c3d",
159
159
  "schemas/v2/memory-list-options.schema.json": "f66703bccb68efb28391c273abb66aa117ee5b5ce475c477846ed700c336afcd",
160
- "schemas/v2/node-pack-manifest.schema.json": "848f3db5867e4d13e29aabb31ea94a4d0d27d7a1db35bf79f2620f9dd98b74ec",
160
+ "schemas/v2/node-pack-manifest.schema.json": "d8daf0d01bb0d7a5aec16d15fc55e39daa99356e62522773a6f73d35f78ac1ef",
161
161
  "schemas/v2/orchestrator-decision.schema.json": "2da17538e84cfbe70151eb118f1ccf670cff58b8697148e394074d32ac4af6b4",
162
162
  "schemas/v2/org-chart-responsibility-view.schema.json": "8a2565e4cdc6737a3946a70799f93642808b92f9c8d56dcdff3a0782053b89a1",
163
163
  "schemas/v2/pack-lockfile.schema.json": "2685417702c105146b780cd99154296a46578fbb10d81c8c713348ee26fea898",
164
164
  "schemas/v2/part.schema.json": "08b75eaec9a0b4a8cedca7bab732dd906b52b0749eaa444ec1ac015aa43519a5",
165
165
  "schemas/v2/prompt-kind.schema.json": "67251c937a5a91fb97c0422050e808dc0c162a9b8561366814cf87b0a7f62e00",
166
- "schemas/v2/prompt-pack-manifest.schema.json": "4b1e510341cc6ba167b007ecae38ce0d71d5b89c751671777c5457da926e9744",
166
+ "schemas/v2/prompt-pack-manifest.schema.json": "752407469475396ad250085437a6cc6bd56a017ac6d3e52196aecc65dda4e51a",
167
167
  "schemas/v2/prompt-ref.schema.json": "11538fa36a7a652c93100cac4f1857eb4130137b546e3d94890618636d44b729",
168
168
  "schemas/v2/prompt-template.schema.json": "494fc83028cb3af5dbb8a943d0e83b2348f7775e9fa02369162bcb58a6f81559",
169
169
  "schemas/v2/proposal.schema.json": "a5c582ce1e854e5fe10412ebed00e537961ee827bbaca506ca374b200a685992",
170
- "schemas/v2/registry-version-manifest.schema.json": "270704ebc6bae63be949efbf9503578938b103733db9da3cb8fcbee1cd7684dd",
170
+ "schemas/v2/registry-version-manifest.schema.json": "f05c620db2ee8f9c60fbe33f2e385cc1bc98bc92c588de8a7170c442b1bbe068",
171
171
  "schemas/v2/residency.schema.json": "3fd23c9732beb140504e81560fc426eb6764d547e56da5fb92c5a5c7bf04db3a",
172
172
  "schemas/v2/run-ancestry-response.schema.json": "334c17f2062dea62726ca1d07b8be3f169cdc422af843c1def5fb317039814c9",
173
173
  "schemas/v2/run-diff-response.schema.json": "8bdb2045c274580038bd74781f7d062f5049839797c164a89de4ed89edc53a74",
174
- "schemas/v2/run-event-payloads.schema.json": "65296730d837389ad1a4ee6d97418c2ba01c2544e2689960b50b28fc200b27af",
174
+ "schemas/v2/run-event-payloads.schema.json": "b7b497c0a0fa41f279750138227d3378fc83ca894fb33e4b0a4fe7c685b84bc4",
175
175
  "schemas/v2/run-event.schema.json": "2f0d7416bd376c56affe97b6b2b49981532e8e9ff45c0a162cf806c78b3e7fb7",
176
176
  "schemas/v2/run-list-response.schema.json": "c2dbc34768dcf64528914a213aa3cb55952e8e3e90a64b04a9423b2a19749d38",
177
177
  "schemas/v2/run-options.schema.json": "f0e9f494d80086c2ed2cf9fe87df54e5b4e66601699c4a1089f4edbc7ba58867",
@@ -192,7 +192,7 @@
192
192
  "schemas/v2/webhook-dead-letter-page.schema.json": "543f5ccc26d8f777684a037172ab7b9fdf70735e5394eeb42e5fa6569503f528",
193
193
  "schemas/v2/webhook-delivery.schema.json": "4e8ab3b722ad1731adf0548c1217f0de62da40de14e8abf77533aee4ace4c079",
194
194
  "schemas/v2/webhook-verification.schema.json": "76e4e0d81143ffd2649bc7adba2aa34f2525e2c4e9f0300a425d908b3163196b",
195
- "schemas/v2/workflow-chain-pack-manifest.schema.json": "58fa5b67c6e3a320f77aa1eb5d5fa7c6c9b3356afe8b75ad74a7cf082496e04b",
195
+ "schemas/v2/workflow-chain-pack-manifest.schema.json": "0816c6c59a91452d6fa1931394b766780eaf90949c9f7cd0ced522dadd32ff81",
196
196
  "schemas/v2/workflow-definition.schema.json": "b9043e3413fc01d939e7b2892cc0fe28bdfd77d5bacc561b6ca4ae20197b985b",
197
197
  "schemas/v2/workload-identity.schema.json": "1f589debfac6d70e0908da511550ec69a27b4bcc8314551cd843b6d923605efc",
198
198
  "schemas/v2/workspace-file-create.schema.json": "b6b5260857318c3b67de0231ed15e3a746f525880a763e7b36a7854178dd10d0",
@@ -204,7 +204,7 @@
204
204
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
205
205
  "spec/v1/alias-detectors.json": "2401fcb1c18cdd688c018b3d716ae6bca85c5e356220ee2b793bd9d81872412d",
206
206
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
207
- "spec/v1/core-standard-manifest.json": "c92119aa65b12b7f670cc59e7768177d20f8f47173e7fbd15c3b0a6ef340810a",
207
+ "spec/v1/core-standard-manifest.json": "30feee3e0480c4102801c55e49830974e021be2284f01171e1327c8ac3e56e87",
208
208
  "spec/v1/deprecations.json": "2d03f4729810280147ea08c630ea65434f2dc5370567d0aac41c3595f8337d40",
209
209
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
210
210
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -218,7 +218,7 @@
218
218
  "spec/v1/spec-gaps.json": "ea9612af55bea9dae3e82223d4f714243f8f590b0193f1188257b13491a38afc",
219
219
  "spec/v2/README.md": "ccfed7d978ce7293a6d8f3c0f6045835dfb7e488f2b67a68f6b04d67c4b0cfc9",
220
220
  "spec/v2/core/capabilities.md": "e22381fed3a93376ad478fa7f07571fdad9653aefd7c05f189a2ef45a49f6c63",
221
- "spec/v2/core/conformance.md": "975bf95049247530d723e81051b6e64bd2a1f9d150183dec391cae48e35af7cc",
221
+ "spec/v2/core/conformance.md": "38ebc9e56a830d0ee0ade429b81b0cd302c23e49a13647b1b902275262d6610d",
222
222
  "spec/v2/core/connection-packs.md": "7257339a4fc125011bf89961588c949cb058a0c0f7d9808ad08a5288deb3f8fc",
223
223
  "spec/v2/core/conversation.md": "c9a937e410fdb4e092cec2f05a7cab8b5232745090cfb20041c03c70ccbb5cdc",
224
224
  "spec/v2/core/errors.md": "619cd74381c41acf33d012eb1afd834e148ac292663a1a6fa6744dbaff9661d9",
@@ -228,19 +228,19 @@
228
228
  "spec/v2/core/host-services.md": "c3ff93201d642edf835e9ae84f06a17edd207f9d351f8bfd70572be05bbfd894",
229
229
  "spec/v2/core/idempotency.md": "b61d4a200526485937897dd7ebca092e68424a80624e0be7a0b7db8cdbd54403",
230
230
  "spec/v2/core/identity.md": "d15667552019f64316be9b65c6e5696c10766581405f05da3907fb47f36e6459",
231
- "spec/v2/core/interop.md": "ec8239d6d430e0d6df4db51d23bcbbbf9bc989bdb7e487e8312a104485ab6d16",
231
+ "spec/v2/core/interop.md": "9a6b37e07c0235856199ace6b3a84f592fa5903fcf6c442f8a18f4d6b6aa5525",
232
232
  "spec/v2/core/interrupt.md": "1dc94f7c791dd5a547b1abafde6a8a76acd3d2993d288057f302f57911dc83bf",
233
233
  "spec/v2/core/node-pack-runtimes.md": "c435ad506f618831b6c0a9fd9150d83a5dc6caa1e3d755b949af2ff8949f1140",
234
234
  "spec/v2/core/oauth.md": "08e558a742259e75cfb2123f4c364d238dc4c57eea1a3c25d8f420ab4dff8244",
235
235
  "spec/v2/core/overview.md": "8f9d4a68b6185664e40d226cb82d9a9270a6c6630e3db59bddff300be781947d",
236
- "spec/v2/core/packs.md": "3107db14f68f36f5c0d8a13a6f0b3ca9ebef7ff890b8133894bfd28a63462d9b",
236
+ "spec/v2/core/packs.md": "91cd577b4a636c1587ef78742e583a203244b8508b289b01a18a8ef2ef0a9b3e",
237
237
  "spec/v2/core/persistence.md": "d32736bd74e51df7c552565b4105a8ec801ece6d2292c8676421368ee9bffec1",
238
- "spec/v2/core/replay.md": "c0412290c805231f5b975acc0c094465c627cd600abec34efa034cbf0a4520cf",
238
+ "spec/v2/core/replay.md": "9050256a417171b184122877aa395d264545bb67c20e045315f42baa539f13cd",
239
239
  "spec/v2/core/runs.md": "c7dcc5116648f42cadecdcd4c949b194272908cbc4b1fbc7321956d5b3a713af",
240
- "spec/v2/core/security-defaults.md": "b54c055bea0a3c5417751a7423e390ef59b5f9acd6911f1b4b754e1a4578e7e2",
240
+ "spec/v2/core/security-defaults.md": "500471a8db7af9b776ac40b4a9a278c9f3d45e1c1dead880114e7fadc7bee37d",
241
241
  "spec/v2/core/tool-catalog.md": "35f1a3fd509db0fc0490ddf400686fe94dc4b20c98064d5ebe776da516153e60",
242
242
  "spec/v2/core/versioning.md": "0aded71d090c8358c3ce17763c120cfc1cc42531a5eaa1b8a2a7016ed9594069",
243
- "spec/v2/core/webhooks.md": "af63fdf728d2f6fd174a61d6c711d720c64154a702c9c28fbe1ca190020a4995",
243
+ "spec/v2/core/webhooks.md": "9c9169d775875f72982ed3506c1bfe27546ad05806d114af103dd7c28d4d5122",
244
244
  "spec/v2/core/workflow-chain-packs.md": "ee45d3fede3bb6f0cc6abcf0d6c929c7cd213a0fbe862b738a998f929c4d1102",
245
245
  "spec/v2/corrections.json": "cc74ffde74384b0f261a4f625871d922e8661d654d647a8534e21036595cb280",
246
246
  "spec/v2/corrections.schema.json": "4ac595b9a6d7f66d53de03fe0edbe0dc58de46426932387bfdc0ceb3cf949a3e",
@@ -270,7 +270,7 @@
270
270
  "spec/v2/ext/webResearch/README.md": "e74fbe55a8c00040f1f87b5448259ee4eb3a26769d607f7d2dd9a671a2e2832b",
271
271
  "spec/v2/facets/a2a.schema.json": "b68bf5e29e455e46c2ad7cfcccf5963730ec2310ff9db81884550d8df30fd6ef",
272
272
  "spec/v2/facets/aiProviders.schema.json": "81fb39b6698564d9d07a0d10525ecb6360c152fe3f3a5ea0439112509ca130e2",
273
- "spec/v2/facets/auth.schema.json": "668f183fc15a6fb058c53635d3457db5a821f551a90141d08676a7a28917b0a9",
273
+ "spec/v2/facets/auth.schema.json": "7d98bc81a0f7d307460d039eebfebd21388091bfe1d538762583940f94c0bd2a",
274
274
  "spec/v2/facets/envelopeStrictness.schema.json": "28be0a27f880bd45ec1222a16155f5e9167bbca57bf0322970fe1eb2654562aa",
275
275
  "spec/v2/facets/heartbeat.schema.json": "81d7fa1895a8b0241bd07cf00a7e7905bef0fd14eb3427325261c62eee5746a6",
276
276
  "spec/v2/facets/interrupt.schema.json": "2110ef32c3f880bf706475ef4cf8ddf97ff06871b8e6003a5edbcf4439f1fb70",
@@ -285,16 +285,16 @@
285
285
  "spec/v2/facets/webhooks.schema.json": "2fac635ee5fbc10a14a1a46cf512a11bac660bfbabe0aea79f6c7b657ecb9e4e",
286
286
  "spec/v2/facets/workflowChainPacks.schema.json": "ea625ce818fbeb746478f00f31f96ee9613d538e4409c258c1bfb517acdeaba9",
287
287
  "spec/v2/id-field-bindings.json": "9f6fd280250447e26a6704811c352bb8f2ff6c4bd242befe3da0ba38962370e9",
288
- "spec/v2/interop-map.json": "a6bddce7f774f26ed178d2dd2e7268251acc9b1c9119e53dac116aa9c4fb2da0",
288
+ "spec/v2/interop-map.json": "02255dafe7eb55f61ca7f9ae7c21f079d1848a6756991343088975ac9d184b8d",
289
289
  "spec/v2/interop-map.schema.json": "0017c882df34ee215e2e0729de051490e09d43aac3d2c949285698ed51832f12",
290
290
  "spec/v2/migrations.json": "651954938a1e154c0b6f1e3dd6e105889d30de3bb65789f21de7f77a12869e9e",
291
291
  "spec/v2/migrations.schema.json": "b91ade6320ad1b1185c9495b9424f3aa63438ed8e412c45171c227d82c0afd86",
292
292
  "spec/v2/path-manifest.json": "ae9b56d03a701061fd48ad0b73cb0373493f6a4547da92763b422295b5e19535",
293
293
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
294
294
  "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
295
- "spec/v2/release.json": "9422440ed08e9d8ff3e8190c3e91e862025fa8b5b49d468a536806f5a841ac13",
295
+ "spec/v2/release.json": "602da823b9231be131b63eb8a2570958a7a90ab0d64a472e5927b47ec6ed2a35",
296
296
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
297
- "spec/v2/surface-baseline.json": "13cfe5c31c3727a2e36980aa3e5a42f8ce1474b35beb99c5b71f487db006e71a"
297
+ "spec/v2/surface-baseline.json": "3f375c183236e2b5cf50029a0d998101c4a3c3766db50f940f50f7039a81ef3f"
298
298
  },
299
- "corpusCommit": "0d370019eaaa841491be58d196e6d7a6934c71f0"
299
+ "corpusCommit": "d9ac404fd5724f9baf2adc568ee3b7f7e03a0347"
300
300
  }
package/src/cli.ts CHANGED
@@ -46,6 +46,7 @@ import { deriveRung, emittedByNewerSuite, type RowEvidence } from './lib/durabil
46
46
  import { deriveRequirementDispositions } from './lib/scenario-disposition.js';
47
47
  import { scrubEvidence, evidenceSecretsFromEnv, verifyBundleV2 } from './lib/certification-bundle-verify.js';
48
48
  import { publicKeyFromPrivate, signBundleV3, verifierSign, verifyBundleV3, witnessDigest, type BundleV3, type BundleV3Requirement } from './lib/certification-bundle-v3.js';
49
+ import { canonicalJSON, parseIJson } from './lib/jcs.js';
49
50
  import {
50
51
  deriveProfiles,
51
52
  isCoreStandard,
@@ -330,19 +331,6 @@ function suiteVersion(): string {
330
331
  return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
331
332
  }
332
333
 
333
- /**
334
- * Deterministic canonical-JSON serialization (RFC 8785 spirit): object keys
335
- * sorted lexicographically at every level, arrays preserved in order. Used to
336
- * compute `discovery.sha256` so a verifier can re-derive the same digest from
337
- * a live `/.well-known/openwop` fetch regardless of incidental key order.
338
- */
339
- function canonicalJSON(value: unknown): string {
340
- if (value === null || typeof value !== 'object') return JSON.stringify(value);
341
- if (Array.isArray(value)) return `[${value.map(canonicalJSON).join(',')}]`;
342
- const obj = value as Record<string, unknown>;
343
- const keys = Object.keys(obj).sort();
344
- return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJSON(obj[k])}`).join(',')}}`;
345
- }
346
334
 
347
335
  /**
348
336
  * The full set of profiles a discovery document derives — the closed
@@ -503,7 +491,11 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
503
491
  );
504
492
  process.exit(2);
505
493
  }
506
- document = (await resp.json()) as DiscoveryPayload;
494
+ // RFC 0212 §B — read the TEXT through the I-JSON parser: `resp.json()`
495
+ // keeps the last of two duplicate names and rounds an out-of-range integer,
496
+ // and `discovery.sha256` would then attest to a document other verifiers
497
+ // read differently.
498
+ document = parseIJson(await resp.text()) as DiscoveryPayload;
507
499
  } catch (err) {
508
500
  process.stderr.write(
509
501
  `openwop-conformance --certify: failed to fetch ${discoveryUrl}: ${String(err)}\n`,
@@ -939,9 +931,9 @@ async function main(): Promise<never> {
939
931
  }
940
932
  let bundle: BundleV3;
941
933
  try {
942
- bundle = JSON.parse(readFileSync(args.verifyPath, 'utf8')) as BundleV3;
934
+ bundle = parseIJson(readFileSync(args.verifyPath, 'utf8')) as BundleV3;
943
935
  } catch (e) {
944
- process.stderr.write(`openwop-conformance --verify: ${args.verifyPath} is not readable JSON — ${(e as Error).message}\n`);
936
+ process.stderr.write(`openwop-conformance --verify: ${args.verifyPath} is not readable I-JSON (RFC 0212 §B) — ${(e as Error).message}\n`);
945
937
  process.exit(3);
946
938
  }
947
939
  const hostKey = args.verifyHostKeyPath !== undefined ? readFileSync(args.verifyHostKeyPath, 'utf8') : undefined;
@@ -21,6 +21,7 @@ import { createHash, createPrivateKey, createPublicKey, sign as edSign, verify a
21
21
  import { profileDerivable, type DiscoveryPayload } from './profiles.js';
22
22
  import { checkRungClaim, type DurabilityRung, type RowEvidence } from './durability-evidence.js';
23
23
  import { profilesDeniedByObservedRelaxation, profilesRelaxedBy, v2RegistryAvailable } from './v2-profiles.js';
24
+ import { canonicalJSON, codeUnitCompare, JcsRefusal } from './jcs.js';
24
25
 
25
26
  export type BundleV3Result = 'executed-pass' | 'executed-fail' | 'skipped' | 'inapplicable' | 'blocked';
26
27
 
@@ -78,14 +79,12 @@ export interface BundleV3 {
78
79
 
79
80
  export const SIGNATURE_OVER = ['witnessSha256', 'host.build', 'suite.version', 'discovery.sha256'] as const;
80
81
 
81
- /** Deterministic JSON: keys sorted at every level, no whitespace. */
82
- export function canonicalJSON(value: unknown): string {
83
- if (Array.isArray(value)) return `[${value.map(canonicalJSON).join(',')}]`;
84
- if (value !== null && typeof value === 'object') {
85
- return `{${Object.keys(value as Record<string, unknown>).sort().map((k) => `${JSON.stringify(k)}:${canonicalJSON((value as Record<string, unknown>)[k])}`).join(',')}}`;
86
- }
87
- return JSON.stringify(value);
88
- }
82
+ /**
83
+ * RFC 0212 §A — the bytes every attestation and digest here covers are RFC 8785
84
+ * (JCS), and a non-I-JSON value is refused, never coerced. Re-exported so
85
+ * existing importers keep one name for one algorithm.
86
+ */
87
+ export { canonicalJSON } from './jcs.js';
89
88
 
90
89
  /**
91
90
  * RFC 0148 §C — the digest over the reporter record (the requirement rows),
@@ -100,9 +99,16 @@ export function canonicalJSON(value: unknown): string {
100
99
  * committed bundles digest unchanged, pinned in durability-evidence.test.ts),
101
100
  * and `{ rows, relaxations }` otherwise. An older verifier fails closed on a
102
101
  * 2.35.0 bundle that declares relaxations — `witness-digest`, never a pass.
102
+ *
103
+ * RFC 0212 §C — rows sort by `id` in UTF-16 code-unit order. This was
104
+ * `localeCompare` in the process default locale: a bundle cut on a machine
105
+ * whose locale is Czech, Slovak, Lithuanian or Hawaiian digested differently
106
+ * from the same bundle anywhere else (Czech sorts `ch` after `h`). Every
107
+ * committed bundle's ids are `[a-z0-9.-]`, where the two orders coincide, so no
108
+ * stored digest changes (v2-bundle-witness-preimage.test.ts pins that).
103
109
  */
104
110
  export function witnessDigest(rows: readonly BundleV3Requirement[], relaxations?: readonly BundleV3Relaxation[]): string {
105
- const canonicalRows = [...rows].sort((a, b) => a.id.localeCompare(b.id)).map((r) => ({ id: r.id, scenario: r.scenario, result: r.result, ...(r.assertions === undefined ? {} : { assertions: r.assertions }), ...(r.detail === undefined ? {} : { detail: r.detail }),
111
+ const canonicalRows = [...rows].sort((a, b) => codeUnitCompare(a.id, b.id)).map((r) => ({ id: r.id, scenario: r.scenario, result: r.result, ...(r.assertions === undefined ? {} : { assertions: r.assertions }), ...(r.detail === undefined ? {} : { detail: r.detail }),
106
112
  // ONLY WHEN PRESENT: every bundle cut before 2.34.0 has no `evidence` and digests byte-identically.
107
113
  ...(r.evidence === undefined ? {} : { evidence: r.evidence }) }));
108
114
  const preimage = relaxations !== undefined && relaxations.length > 0 ? { rows: canonicalRows, relaxations } : canonicalRows;
@@ -184,6 +190,21 @@ export function optedOutFromRows(rows: ReadonlyArray<{ readonly id: string; read
184
190
  return [...names].sort();
185
191
  }
186
192
 
193
+ /**
194
+ * RFC 0212 §B — a verifier that meets non-I-JSON in a value it must
195
+ * re-canonicalize MUST fail verification. The canonicalizer throws; a verifier
196
+ * returns a verdict, so the refusal becomes a rejection of the named kind rather
197
+ * than an exception out of `verifyBundleV3` (a library caller that parsed the
198
+ * bundle with `JSON.parse` can still hand it a lone surrogate).
199
+ */
200
+ function refusedAs<T>(rejections: V3Rejection[], kind: string, what: string, fn: () => T): T | undefined {
201
+ try { return fn(); } catch (e) {
202
+ if (!(e instanceof JcsRefusal)) throw e;
203
+ rejections.push({ kind, detail: `${what} is not I-JSON, so it has no canonical bytes (RFC 0212 §B): ${e.message}` });
204
+ return undefined;
205
+ }
206
+ }
207
+
187
208
  export function verifyBundleV3(bundle: BundleV3, opts: VerifyV3Options = {}): V3Verdict {
188
209
  const rejections: V3Rejection[] = [];
189
210
  if (bundle.bundleVersion !== '3') rejections.push({ kind: 'not-v3', detail: `bundleVersion is ${JSON.stringify(bundle.bundleVersion)}, expected "3"` });
@@ -198,8 +219,8 @@ export function verifyBundleV3(bundle: BundleV3, opts: VerifyV3Options = {}): V3
198
219
  const count = (d: BundleV3Result): number => rows.filter((r) => r.result === d).length;
199
220
  const expected = { executedPass: count('executed-pass'), executedFail: count('executed-fail'), skipped: count('skipped'), inapplicable: count('inapplicable'), blocked: count('blocked') };
200
221
  for (const k of Object.keys(expected) as (keyof typeof expected)[]) if (bundle.results?.totals?.[k] !== expected[k]) rejections.push({ kind: 'totals-mismatch', detail: `totals.${k} is ${String(bundle.results?.totals?.[k])} but the rows count ${expected[k]}` });
201
- const digest = witnessDigest(rows, bundle.host?.relaxations);
202
- if (bundle.witnessSha256 !== digest) rejections.push({ kind: 'witness-digest', detail: `witnessSha256 ${String(bundle.witnessSha256).slice(0, 12)} does not equal the digest of the rows and declared relaxations (${digest.slice(0, 12)})` });
222
+ const digest = refusedAs(rejections, 'witness-digest', 'the rows or declared relaxations', () => witnessDigest(rows, bundle.host?.relaxations));
223
+ if (digest !== undefined && bundle.witnessSha256 !== digest) rejections.push({ kind: 'witness-digest', detail: `witnessSha256 ${String(bundle.witnessSha256).slice(0, 12)} does not equal the digest of the rows and declared relaxations (${digest.slice(0, 12)})` });
203
224
  const assertions = rows.reduce((n, r) => n + (r.assertions ?? 0), 0);
204
225
  if (bundle.assertionCount !== assertions) rejections.push({ kind: 'assertion-count', detail: `assertionCount is ${String(bundle.assertionCount)} but the rows sum to ${assertions}` });
205
226
  const nonPass = rows.filter((r) => r.result !== 'executed-pass');
@@ -213,8 +234,9 @@ export function verifyBundleV3(bundle: BundleV3, opts: VerifyV3Options = {}): V3
213
234
  else if (JSON.stringify(sig.over) !== JSON.stringify(SIGNATURE_OVER)) rejections.push({ kind: 'signature-over', detail: `signature.over must be ${JSON.stringify(SIGNATURE_OVER)}` });
214
235
  else if (opts.hostPublicKeyPem) {
215
236
  const key: KeyObject = createPublicKey(opts.hostPublicKeyPem);
216
- signatureVerified = edVerify(null, attestationPayload(bundle), key, fromBase64url(sig.sig));
217
- if (!signatureVerified) rejections.push({ kind: 'signature-invalid', detail: 'the attestation does not verify under the host key' });
237
+ const payload = refusedAs(rejections, 'signature-invalid', 'the attested members', () => attestationPayload(bundle));
238
+ signatureVerified = payload !== undefined && edVerify(null, payload, key, fromBase64url(sig.sig));
239
+ if (payload !== undefined && !signatureVerified) rejections.push({ kind: 'signature-invalid', detail: 'the attestation does not verify under the host key' });
218
240
  }
219
241
  // Independent tier
220
242
  let verifierSignatureVerified = false;
@@ -224,8 +246,9 @@ export function verifyBundleV3(bundle: BundleV3, opts: VerifyV3Options = {}): V3
224
246
  if (!vs || !vs.keyId || !vs.sig) rejections.push({ kind: 'independent-unsigned', detail: 'evidenceTier independent requires a verifierSignature (RFC 0168 §E.2)' });
225
247
  else if (vs.keyId === sig?.keyId || vs.keyId === bundle.host?.signingKeyId) rejections.push({ kind: 'independent-self-signed', detail: 'the verifier key must be distinct from the host key (RFC 0148 R5)' });
226
248
  else if (opts.verifierPublicKeyPem) {
227
- verifierSignatureVerified = edVerify(null, attestationPayload(bundle), createPublicKey(opts.verifierPublicKeyPem), fromBase64url(vs.sig));
228
- if (!verifierSignatureVerified) rejections.push({ kind: 'verifier-signature-invalid', detail: 'the verifier attestation does not verify' });
249
+ const payload = refusedAs(rejections, 'verifier-signature-invalid', 'the attested members', () => attestationPayload(bundle));
250
+ verifierSignatureVerified = payload !== undefined && edVerify(null, payload, createPublicKey(opts.verifierPublicKeyPem), fromBase64url(vs.sig));
251
+ if (payload !== undefined && !verifierSignatureVerified) rejections.push({ kind: 'verifier-signature-invalid', detail: 'the verifier attestation does not verify' });
229
252
  } else rejections.push({ kind: 'independent-unverifiable', detail: 'an independent claim needs the verifier public key to verify; refused, not assumed' });
230
253
  }
231
254
  // Relaxations: a relaxed obligation's profile cannot certify (RFC 0173 §A.2).
@@ -263,8 +286,10 @@ export function verifyBundleV3(bundle: BundleV3, opts: VerifyV3Options = {}): V3
263
286
  const document = bundle.discovery?.document;
264
287
  let derivabilityChecked = false;
265
288
  if (document !== undefined) {
266
- const digest = createHash('sha256').update(canonicalJSON(document)).digest('hex');
267
- if (digest !== bundle.discovery?.sha256) {
289
+ const digest = refusedAs(rejections, 'discovery-digest', 'discovery.document', () => createHash('sha256').update(canonicalJSON(document)).digest('hex'));
290
+ if (digest === undefined) {
291
+ // Already rejected as non-I-JSON; nothing derivable from a document with no canonical bytes.
292
+ } else if (digest !== bundle.discovery?.sha256) {
268
293
  rejections.push({ kind: 'discovery-digest', detail: `discovery.document hashes to ${digest.slice(0, 12)} but discovery.sha256 is ${String(bundle.discovery?.sha256).slice(0, 12)} — the captured document is not the one the signature attests to` });
269
294
  } else {
270
295
  // WHICH catalog decides derivability is the bundle's own `targetMajor`,
@@ -67,8 +67,43 @@ export function unregisterBehindFront(frontEnv: string, nonce: string): void {
67
67
  * did (the caller then does nothing else). The prefix is stripped so the
68
68
  * target sees exactly the path it would have seen on its own listener.
69
69
  */
70
+ /**
71
+ * The PATH of the operator's front, without a trailing slash — `''` when the
72
+ * front is unset or names only an origin.
73
+ *
74
+ * A tunnel forwards the whole request path, so a front of
75
+ * `https://x.trycloudflare.com/hook` delivers `/hook/fx/<nonce>` to the pinned
76
+ * listener. Until 2.38.0 routing matched only paths STARTING with `/fx/`, so
77
+ * on a path-bearing front every delivery was answered as addressed elsewhere —
78
+ * measured on the 2026-09-24 public v2-reference cut, where `cut-public.sh`
79
+ * fronts the webhook receiver at `…/hook` and four webhook rows failed. A
80
+ * loopback cut cannot show it: there is no front, so there is no prefix.
81
+ */
82
+ export function frontPath(frontEnv: string): string {
83
+ const raw = process.env[frontEnv]?.trim();
84
+ if (!raw) return '';
85
+ try {
86
+ return new URL(raw).pathname.replace(/\/+$/, '');
87
+ } catch {
88
+ return '';
89
+ }
90
+ }
91
+
92
+ /** `url` with the front's own path prefix removed, when it carries it. */
93
+ export function withoutFrontPath(frontEnv: string, url: string): string {
94
+ const pre = frontPath(frontEnv);
95
+ if (pre === '') return url;
96
+ if (url === pre) return '/';
97
+ if (url.startsWith(`${pre}/`) || url.startsWith(`${pre}?`)) return url.slice(pre.length).replace(/^(?=\?)/, '/');
98
+ return url;
99
+ }
100
+
70
101
  export function routeFronted(frontEnv: string, req: IncomingMessage, res: ServerResponse): boolean {
71
- const url = req.url ?? '/';
102
+ // Strip the front's own path first, and leave the request rewritten either
103
+ // way: the pinned owner's OWN traffic arrives through the same front and
104
+ // must see the path it would see on a bare listener.
105
+ const url = withoutFrontPath(frontEnv, req.url ?? '/');
106
+ req.url = url;
72
107
  if (!url.startsWith(PREFIX)) return false;
73
108
  const rest = url.slice(PREFIX.length);
74
109
  const slash = rest.indexOf('/');
package/src/lib/jcs.ts ADDED
@@ -0,0 +1,229 @@
1
+ /**
2
+ * RFC 0212 — canonical JSON is RFC 8785 (JCS), and the input MUST be I-JSON.
3
+ *
4
+ * Every signature and digest in the corpus is over these bytes: pack
5
+ * signatures (`ed25519-canonical-json`), the certification-bundle attestation,
6
+ * `discovery.sha256`, `witnessSha256`, and the RFC 0150 semantic request digest.
7
+ * `conformance/vectors/jcs-v1.json` is the normative test of this module and of
8
+ * any other-language implementation.
9
+ *
10
+ * Two entry points, because two of the refusals are invisible after a parse:
11
+ *
12
+ * - `canonicalJSON(value)` — the VALUE boundary. Refuses what a native value
13
+ * can still show: non-finite numbers, non-JSON values (undefined, functions,
14
+ * bigint, symbols, class instances such as Date) and lone surrogates.
15
+ * - `parseIJson(text)` — the TEXT boundary. `JSON.parse` keeps the last of two
16
+ * duplicate names and rounds `9007199254740993` to `…992` silently; both are
17
+ * refused here, before coercion. Read a document you will re-canonicalize
18
+ * through this, not `JSON.parse`.
19
+ *
20
+ * Why not `Object.keys(v).sort()` + `JSON.stringify` with no checks (the code
21
+ * this replaces): it is JCS for well-formed input — the default sort IS UTF-16
22
+ * code-unit order and ES number/string serialization IS JCS §3.2.2 — but it
23
+ * turns NaN into `null`, emits the text `undefined`, and signs a rounded
24
+ * integer. Each of those is a document two verifiers read differently with no
25
+ * error on either side.
26
+ */
27
+
28
+ export type JcsRefusalKind = 'duplicate-name' | 'lone-surrogate' | 'integer-out-of-range' | 'non-finite' | 'not-json';
29
+
30
+ export class JcsRefusal extends Error {
31
+ readonly kind: JcsRefusalKind;
32
+ constructor(kind: JcsRefusalKind, message: string) {
33
+ super(`RFC 0212 §B refusal (${kind}): ${message}`);
34
+ this.name = 'JcsRefusal';
35
+ this.kind = kind;
36
+ }
37
+ }
38
+
39
+ /** A JSON number literal, matched in place (sticky) so parsing stays linear. */
40
+ const NUMBER = /-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/y;
41
+
42
+ /** ±(2^53 − 1), for the integer-literal refusal at the text boundary. */
43
+ const MAX_EXACT_BIG = 2n ** 53n - 1n;
44
+
45
+ /**
46
+ * RFC 8785 §3.2.3 — compare by UTF-16 code units. Explicit rather than the
47
+ * `Array.prototype.sort` default so the rule is visible at every call site, and
48
+ * never `localeCompare`: collation is locale-dependent (Czech sorts `ch` after
49
+ * `h`; English puts `a` before `A` and ignores `-`), so a digest computed with
50
+ * it depends on the machine that computed it.
51
+ */
52
+ export function codeUnitCompare(a: string, b: string): number {
53
+ const n = Math.min(a.length, b.length);
54
+ for (let i = 0; i < n; i += 1) {
55
+ const d = a.charCodeAt(i) - b.charCodeAt(i);
56
+ if (d !== 0) return d;
57
+ }
58
+ return a.length - b.length;
59
+ }
60
+
61
+ function assertWellFormed(s: string, where: string): void {
62
+ for (let i = 0; i < s.length; i += 1) {
63
+ const u = s.charCodeAt(i);
64
+ if (u >= 0xd800 && u <= 0xdbff) {
65
+ const next = s.charCodeAt(i + 1);
66
+ if (next >= 0xdc00 && next <= 0xdfff) { i += 1; continue; }
67
+ throw new JcsRefusal('lone-surrogate', `lone high surrogate U+${u.toString(16).toUpperCase()} in ${where}`);
68
+ }
69
+ if (u >= 0xdc00 && u <= 0xdfff) throw new JcsRefusal('lone-surrogate', `lone low surrogate U+${u.toString(16).toUpperCase()} in ${where}`);
70
+ }
71
+ }
72
+
73
+ function serializeNumber(n: number): string {
74
+ if (!Number.isFinite(n)) throw new JcsRefusal('non-finite', `${String(n)} is not a JSON number`);
75
+ // No magnitude check here: a double is already exact, and JCS serializes it
76
+ // (RFC 8785 Appendix B includes 9007199254740994). The integer-range refusal
77
+ // is about a LITERAL that no double holds, which only the text shows — see
78
+ // `parseIJson`.
79
+ return Object.is(n, -0) ? '0' : String(n);
80
+ }
81
+
82
+ function isPlainObject(v: object): boolean {
83
+ const proto = Object.getPrototypeOf(v) as unknown;
84
+ return proto === Object.prototype || proto === null;
85
+ }
86
+
87
+ /** RFC 8785 serialization of an I-JSON value. Throws `JcsRefusal` instead of coercing. */
88
+ export function canonicalJSON(value: unknown): string {
89
+ if (value === null) return 'null';
90
+ switch (typeof value) {
91
+ case 'boolean': return value ? 'true' : 'false';
92
+ case 'number': return serializeNumber(value);
93
+ case 'string': assertWellFormed(value, 'a string'); return JSON.stringify(value);
94
+ case 'object': break;
95
+ default: throw new JcsRefusal('not-json', `a ${typeof value} is not a JSON value`);
96
+ }
97
+ if (Array.isArray(value)) {
98
+ const parts: string[] = [];
99
+ for (let i = 0; i < value.length; i += 1) {
100
+ if (!(i in value)) throw new JcsRefusal('not-json', 'a sparse array hole is not a JSON value');
101
+ parts.push(canonicalJSON(value[i]));
102
+ }
103
+ return `[${parts.join(',')}]`;
104
+ }
105
+ if (!isPlainObject(value)) throw new JcsRefusal('not-json', `a ${(value as object).constructor?.name ?? 'non-plain'} object is not a JSON value`);
106
+ const obj = value as Record<string, unknown>;
107
+ const keys = Object.keys(obj).sort(codeUnitCompare);
108
+ return `{${keys.map((k) => { assertWellFormed(k, 'a member name'); return `${JSON.stringify(k)}:${canonicalJSON(obj[k])}`; }).join(',')}}`;
109
+ }
110
+
111
+ /**
112
+ * Parse JSON text, refusing what `JSON.parse` would silently accept or change:
113
+ * duplicate member names, integer literals outside ±(2^53 − 1), literals that
114
+ * overflow to ±Infinity, and lone surrogates. Members are defined, not
115
+ * assigned, so a member named `__proto__` is data, not a prototype write.
116
+ */
117
+ export function parseIJson(text: string): unknown {
118
+ let i = 0;
119
+ const fail = (m: string): never => { throw new JcsRefusal('not-json', `${m} at offset ${i}`); };
120
+ const ws = (): void => { while (i < text.length && (text[i] === ' ' || text[i] === '\t' || text[i] === '\n' || text[i] === '\r')) i += 1; };
121
+
122
+ const str = (): string => {
123
+ if (text[i] !== '"') fail('expected a string');
124
+ i += 1;
125
+ let out = '';
126
+ for (;;) {
127
+ if (i >= text.length) fail('unterminated string');
128
+ const c = text[i];
129
+ i += 1;
130
+ if (c === '"') break;
131
+ if (c === '\\') {
132
+ const e = text[i];
133
+ i += 1;
134
+ switch (e) {
135
+ case '"': out += '"'; break;
136
+ case '\\': out += '\\'; break;
137
+ case '/': out += '/'; break;
138
+ case 'b': out += '\b'; break;
139
+ case 'f': out += '\f'; break;
140
+ case 'n': out += '\n'; break;
141
+ case 'r': out += '\r'; break;
142
+ case 't': out += '\t'; break;
143
+ case 'u': {
144
+ const h = text.slice(i, i + 4);
145
+ if (!/^[0-9a-fA-F]{4}$/.test(h)) fail('bad \\u escape');
146
+ out += String.fromCharCode(parseInt(h, 16));
147
+ i += 4;
148
+ break;
149
+ }
150
+ default: fail('bad escape');
151
+ }
152
+ } else {
153
+ if ((c as string).charCodeAt(0) < 0x20) fail('raw control character in a string');
154
+ out += c;
155
+ }
156
+ }
157
+ assertWellFormed(out, 'a string');
158
+ return out;
159
+ };
160
+
161
+ const num = (): number => {
162
+ NUMBER.lastIndex = i;
163
+ const m = NUMBER.exec(text);
164
+ if (m === null) return fail('bad number');
165
+ i += m[0].length;
166
+ if (m[2] === undefined && m[3] === undefined) {
167
+ const b = BigInt(m[0]);
168
+ if (b > MAX_EXACT_BIG || b < -MAX_EXACT_BIG) throw new JcsRefusal('integer-out-of-range', `integer literal ${m[0]} is outside ±(2^53 − 1)`);
169
+ }
170
+ const v = Number(m[0]);
171
+ if (!Number.isFinite(v)) throw new JcsRefusal('non-finite', `${m[0]} overflows to a non-finite number`);
172
+ return v;
173
+ };
174
+
175
+ const val = (): unknown => {
176
+ ws();
177
+ const c = text[i];
178
+ if (c === '{') {
179
+ i += 1;
180
+ const obj: Record<string, unknown> = {};
181
+ ws();
182
+ if (text[i] === '}') { i += 1; return obj; }
183
+ for (;;) {
184
+ ws();
185
+ const k = str();
186
+ if (Object.prototype.hasOwnProperty.call(obj, k)) throw new JcsRefusal('duplicate-name', `duplicate member name ${JSON.stringify(k)}`);
187
+ ws();
188
+ if (text[i] !== ':') fail('expected :');
189
+ i += 1;
190
+ Object.defineProperty(obj, k, { value: val(), enumerable: true, writable: true, configurable: true });
191
+ ws();
192
+ const d = text[i];
193
+ i += 1;
194
+ if (d === '}') return obj;
195
+ if (d !== ',') fail('expected , or }');
196
+ }
197
+ }
198
+ if (c === '[') {
199
+ i += 1;
200
+ const arr: unknown[] = [];
201
+ ws();
202
+ if (text[i] === ']') { i += 1; return arr; }
203
+ for (;;) {
204
+ arr.push(val());
205
+ ws();
206
+ const d = text[i];
207
+ i += 1;
208
+ if (d === ']') return arr;
209
+ if (d !== ',') fail('expected , or ]');
210
+ }
211
+ }
212
+ if (c === '"') return str();
213
+ if (text.startsWith('true', i)) { i += 4; return true; }
214
+ if (text.startsWith('false', i)) { i += 5; return false; }
215
+ if (text.startsWith('null', i)) { i += 4; return null; }
216
+ if (c === '-' || (c !== undefined && c >= '0' && c <= '9')) return num();
217
+ return fail('unexpected token');
218
+ };
219
+
220
+ const v = val();
221
+ ws();
222
+ if (i !== text.length) fail('trailing data');
223
+ return v;
224
+ }
225
+
226
+ /** JCS bytes of a JSON text, with every §B refusal applied. */
227
+ export function canonicalizeText(text: string): string {
228
+ return canonicalJSON(parseIJson(text));
229
+ }