@openwop/openwop-conformance 2.0.0-rc.0 → 2.0.0-rc.2
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/README.md +2 -2
- package/dist/cli.js +209 -14
- package/dist/lib/certification-bundle-v3.js +142 -0
- package/dist/lib/requirement-ids.js +23 -0
- package/dist/lib/requirement-ledger.js +1 -0
- package/dist/lib/scenario-disposition.js +5 -1
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +2744 -4
- package/scenario-majors.json +155 -2
- package/schemas/CORPUS-STAMP.json +17 -17
- package/src/cli.ts +190 -19
- package/src/lib/certification-bundle-v3.ts +175 -0
- package/src/lib/driver.ts +9 -1
- package/src/lib/era2-seed.ts +197 -0
- package/src/lib/requirement-ids.ts +21 -0
- package/src/lib/requirement-ledger.ts +7 -1
- package/src/lib/scenario-disposition.ts +5 -1
- package/src/lib/seams.ts +45 -0
- package/src/lib/v2.ts +62 -0
- package/src/scenarios/v2-approver-enforced.test.ts +97 -0
- package/src/scenarios/v2-assurance-downgrade-audited.test.ts +75 -0
- package/src/scenarios/v2-bundle-v3-signed.test.ts +96 -0
- package/src/scenarios/v2-capabilities-root-closed.test.ts +63 -0
- package/src/scenarios/v2-capability-record-shape.test.ts +104 -0
- package/src/scenarios/v2-chain-pin-exact.test.ts +118 -0
- package/src/scenarios/v2-coherence-not-in-bundle.test.ts +98 -0
- package/src/scenarios/v2-compensation-read-projection.test.ts +85 -0
- package/src/scenarios/v2-configurable-closed.test.ts +67 -0
- package/src/scenarios/v2-dual-stack-negotiation.test.ts +118 -0
- package/src/scenarios/v2-effect-identity-business-key.test.ts +132 -0
- package/src/scenarios/v2-effect-seam-manifest.test.ts +163 -0
- package/src/scenarios/v2-enum-growth-rule.test.ts +69 -0
- package/src/scenarios/v2-era-2-append-vocabulary.test.ts +140 -0
- package/src/scenarios/v2-era-key.test.ts +86 -0
- package/src/scenarios/v2-error-registry.test.ts +89 -0
- package/src/scenarios/v2-event-type-closed.test.ts +97 -0
- package/src/scenarios/v2-fork-a-v1-run.test.ts +109 -0
- package/src/scenarios/v2-form-when-reuses-edge-conditions.test.ts +70 -0
- package/src/scenarios/v2-header-scheme.test.ts +67 -0
- package/src/scenarios/v2-id-grammar.test.ts +77 -0
- package/src/scenarios/v2-idempotency-key-grammar.test.ts +65 -0
- package/src/scenarios/v2-interrupt-token-scheme.test.ts +55 -0
- package/src/scenarios/v2-lane-issuer-advertised.test.ts +62 -0
- package/src/scenarios/v2-legacy-profiles-absent.test.ts +78 -0
- package/src/scenarios/v2-manifest-ceiling-refused.test.ts +130 -0
- package/src/scenarios/v2-manifest-hatch-carried.test.ts +120 -0
- package/src/scenarios/v2-min-client-version.test.ts +48 -0
- package/src/scenarios/v2-minimum-version-refused.test.ts +141 -0
- package/src/scenarios/v2-mrtr-rounds-ceiling.test.ts +95 -0
- package/src/scenarios/v2-negotiation-authenticated.test.ts +125 -0
- package/src/scenarios/v2-negotiation-decided-emitted.test.ts +149 -0
- package/src/scenarios/v2-no-transport-advertisement.test.ts +57 -0
- package/src/scenarios/v2-owner-subject-required.test.ts +72 -0
- package/src/scenarios/v2-pack-isolation.test.ts +147 -0
- package/src/scenarios/v2-payload-registry-closed.test.ts +110 -0
- package/src/scenarios/v2-peer-dependency-declared.test.ts +132 -0
- package/src/scenarios/v2-pinned-run-disposition.test.ts +124 -0
- package/src/scenarios/v2-poll-cursor-v2.test.ts +114 -0
- package/src/scenarios/v2-preferred-version-default.test.ts +83 -0
- package/src/scenarios/v2-profiles-derived-only.test.ts +70 -0
- package/src/scenarios/v2-provider-conflict.test.ts +115 -0
- package/src/scenarios/v2-refresh-sla.test.ts +78 -0
- package/src/scenarios/v2-relaxation-recorded.test.ts +134 -0
- package/src/scenarios/v2-revocation-honored.test.ts +66 -0
- package/src/scenarios/v2-subject-link-record.test.ts +102 -0
- package/src/scenarios/v2-unmapped-type-refused.test.ts +95 -0
- package/src/scenarios/v2-v1-events-translated.test.ts +105 -0
- package/src/scenarios/v2-v1-signed-webhook-accepted.test.ts +109 -0
- package/src/scenarios/v2-webhook-durable-delivery.test.ts +238 -0
- package/src/scenarios/v2-well-known-one-resource.test.ts +129 -0
- package/src/setup.ts +40 -4
package/scenario-majors.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "GENERATED by conformance/scripts/generate-scenario-majors.mjs (RFC 0168 §D.3). Do not edit; add a file to BOTH_MAJORS in the generator to target both majors.",
|
|
3
3
|
"counts": {
|
|
4
|
-
"files":
|
|
4
|
+
"files": 495,
|
|
5
5
|
"v1": 444,
|
|
6
|
-
"v2":
|
|
6
|
+
"v2": 52
|
|
7
7
|
},
|
|
8
8
|
"majors": {
|
|
9
9
|
"a2a-1-0-agent-card.test.ts": [
|
|
@@ -1228,6 +1228,159 @@
|
|
|
1228
1228
|
"trigger-stream-cdc-sources.test.ts": [
|
|
1229
1229
|
1
|
|
1230
1230
|
],
|
|
1231
|
+
"v2-approver-enforced.test.ts": [
|
|
1232
|
+
2
|
|
1233
|
+
],
|
|
1234
|
+
"v2-assurance-downgrade-audited.test.ts": [
|
|
1235
|
+
2
|
|
1236
|
+
],
|
|
1237
|
+
"v2-bundle-v3-signed.test.ts": [
|
|
1238
|
+
2
|
|
1239
|
+
],
|
|
1240
|
+
"v2-capabilities-root-closed.test.ts": [
|
|
1241
|
+
2
|
|
1242
|
+
],
|
|
1243
|
+
"v2-capability-record-shape.test.ts": [
|
|
1244
|
+
2
|
|
1245
|
+
],
|
|
1246
|
+
"v2-chain-pin-exact.test.ts": [
|
|
1247
|
+
2
|
|
1248
|
+
],
|
|
1249
|
+
"v2-coherence-not-in-bundle.test.ts": [
|
|
1250
|
+
2
|
|
1251
|
+
],
|
|
1252
|
+
"v2-compensation-read-projection.test.ts": [
|
|
1253
|
+
2
|
|
1254
|
+
],
|
|
1255
|
+
"v2-configurable-closed.test.ts": [
|
|
1256
|
+
2
|
|
1257
|
+
],
|
|
1258
|
+
"v2-dual-stack-negotiation.test.ts": [
|
|
1259
|
+
2
|
|
1260
|
+
],
|
|
1261
|
+
"v2-effect-identity-business-key.test.ts": [
|
|
1262
|
+
2
|
|
1263
|
+
],
|
|
1264
|
+
"v2-effect-seam-manifest.test.ts": [
|
|
1265
|
+
2
|
|
1266
|
+
],
|
|
1267
|
+
"v2-enum-growth-rule.test.ts": [
|
|
1268
|
+
2
|
|
1269
|
+
],
|
|
1270
|
+
"v2-era-2-append-vocabulary.test.ts": [
|
|
1271
|
+
2
|
|
1272
|
+
],
|
|
1273
|
+
"v2-era-key.test.ts": [
|
|
1274
|
+
2
|
|
1275
|
+
],
|
|
1276
|
+
"v2-error-registry.test.ts": [
|
|
1277
|
+
2
|
|
1278
|
+
],
|
|
1279
|
+
"v2-event-type-closed.test.ts": [
|
|
1280
|
+
2
|
|
1281
|
+
],
|
|
1282
|
+
"v2-fork-a-v1-run.test.ts": [
|
|
1283
|
+
2
|
|
1284
|
+
],
|
|
1285
|
+
"v2-form-when-reuses-edge-conditions.test.ts": [
|
|
1286
|
+
2
|
|
1287
|
+
],
|
|
1288
|
+
"v2-header-scheme.test.ts": [
|
|
1289
|
+
2
|
|
1290
|
+
],
|
|
1291
|
+
"v2-id-grammar.test.ts": [
|
|
1292
|
+
2
|
|
1293
|
+
],
|
|
1294
|
+
"v2-idempotency-key-grammar.test.ts": [
|
|
1295
|
+
2
|
|
1296
|
+
],
|
|
1297
|
+
"v2-interrupt-token-scheme.test.ts": [
|
|
1298
|
+
2
|
|
1299
|
+
],
|
|
1300
|
+
"v2-lane-issuer-advertised.test.ts": [
|
|
1301
|
+
2
|
|
1302
|
+
],
|
|
1303
|
+
"v2-legacy-profiles-absent.test.ts": [
|
|
1304
|
+
2
|
|
1305
|
+
],
|
|
1306
|
+
"v2-manifest-ceiling-refused.test.ts": [
|
|
1307
|
+
2
|
|
1308
|
+
],
|
|
1309
|
+
"v2-manifest-hatch-carried.test.ts": [
|
|
1310
|
+
2
|
|
1311
|
+
],
|
|
1312
|
+
"v2-min-client-version.test.ts": [
|
|
1313
|
+
2
|
|
1314
|
+
],
|
|
1315
|
+
"v2-minimum-version-refused.test.ts": [
|
|
1316
|
+
2
|
|
1317
|
+
],
|
|
1318
|
+
"v2-mrtr-rounds-ceiling.test.ts": [
|
|
1319
|
+
2
|
|
1320
|
+
],
|
|
1321
|
+
"v2-negotiation-authenticated.test.ts": [
|
|
1322
|
+
2
|
|
1323
|
+
],
|
|
1324
|
+
"v2-negotiation-decided-emitted.test.ts": [
|
|
1325
|
+
2
|
|
1326
|
+
],
|
|
1327
|
+
"v2-no-transport-advertisement.test.ts": [
|
|
1328
|
+
2
|
|
1329
|
+
],
|
|
1330
|
+
"v2-owner-subject-required.test.ts": [
|
|
1331
|
+
2
|
|
1332
|
+
],
|
|
1333
|
+
"v2-pack-isolation.test.ts": [
|
|
1334
|
+
2
|
|
1335
|
+
],
|
|
1336
|
+
"v2-payload-registry-closed.test.ts": [
|
|
1337
|
+
2
|
|
1338
|
+
],
|
|
1339
|
+
"v2-peer-dependency-declared.test.ts": [
|
|
1340
|
+
2
|
|
1341
|
+
],
|
|
1342
|
+
"v2-pinned-run-disposition.test.ts": [
|
|
1343
|
+
2
|
|
1344
|
+
],
|
|
1345
|
+
"v2-poll-cursor-v2.test.ts": [
|
|
1346
|
+
2
|
|
1347
|
+
],
|
|
1348
|
+
"v2-preferred-version-default.test.ts": [
|
|
1349
|
+
2
|
|
1350
|
+
],
|
|
1351
|
+
"v2-profiles-derived-only.test.ts": [
|
|
1352
|
+
2
|
|
1353
|
+
],
|
|
1354
|
+
"v2-provider-conflict.test.ts": [
|
|
1355
|
+
2
|
|
1356
|
+
],
|
|
1357
|
+
"v2-refresh-sla.test.ts": [
|
|
1358
|
+
2
|
|
1359
|
+
],
|
|
1360
|
+
"v2-relaxation-recorded.test.ts": [
|
|
1361
|
+
2
|
|
1362
|
+
],
|
|
1363
|
+
"v2-revocation-honored.test.ts": [
|
|
1364
|
+
2
|
|
1365
|
+
],
|
|
1366
|
+
"v2-subject-link-record.test.ts": [
|
|
1367
|
+
2
|
|
1368
|
+
],
|
|
1369
|
+
"v2-unmapped-type-refused.test.ts": [
|
|
1370
|
+
2
|
|
1371
|
+
],
|
|
1372
|
+
"v2-v1-events-translated.test.ts": [
|
|
1373
|
+
2
|
|
1374
|
+
],
|
|
1375
|
+
"v2-v1-signed-webhook-accepted.test.ts": [
|
|
1376
|
+
2
|
|
1377
|
+
],
|
|
1378
|
+
"v2-webhook-durable-delivery.test.ts": [
|
|
1379
|
+
2
|
|
1380
|
+
],
|
|
1381
|
+
"v2-well-known-one-resource.test.ts": [
|
|
1382
|
+
2
|
|
1383
|
+
],
|
|
1231
1384
|
"vector-knn-roundtrip.test.ts": [
|
|
1232
1385
|
1
|
|
1233
1386
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
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.0.0-rc.
|
|
4
|
+
"version": "2.0.0-rc.2",
|
|
5
5
|
"corpusTag": null,
|
|
6
6
|
"files": {
|
|
7
7
|
"api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
|
|
10
10
|
"api/openapi.yaml": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
|
|
11
11
|
"api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
|
|
12
|
-
"api/seams-v2.yaml": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
12
|
+
"api/seams-v2.yaml": "7997be82785d963789a452f76455dd91f51a03c227e83706325a8b67d3d16aeb",
|
|
13
|
+
"api/v2/asyncapi.yaml": "aeed4cbeb832cfcd4de46639a2b5a8981406d307b73e3a9d11a645eb0726caff",
|
|
14
|
+
"api/v2/openapi.yaml": "cbbf5df16473902c3c36b5b048ba80b5b35336c06fd48c8ba32f4b1858dd1807",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"schemas/v2/artifact-type-pack-manifest.schema.json": "c911757562e521cb4f31a47c097f562573b64087efe60c8acb81fd352367d300",
|
|
116
116
|
"schemas/v2/audit-verify-result.schema.json": "cb54c4d89ba5dfb73f49aa8959a980be65c2dc2a87a12f71ba36d3e836fe85da",
|
|
117
117
|
"schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
|
|
118
|
-
"schemas/v2/capabilities.schema.json": "
|
|
119
|
-
"schemas/v2/certification-bundle.schema.json": "
|
|
118
|
+
"schemas/v2/capabilities.schema.json": "b6191deddf6bee19470eee9a45bb520de0d17385c195de6e3b8c5cb6d9520238",
|
|
119
|
+
"schemas/v2/certification-bundle.schema.json": "02aa15f17644d902a9df06a99cfdaf51273b0a9cca0d0ef515d5da03be01fb09",
|
|
120
120
|
"schemas/v2/channel-presence-payload.schema.json": "9475d46f358d21c19126e78204987a5633979917a2293653354ef5c5e044d54e",
|
|
121
121
|
"schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
|
|
122
122
|
"schemas/v2/chat-card-pack-manifest.schema.json": "566982ce509a96d1619d58a72e383eff0773fe43fc65e4bfc85a4fe158f4eaff",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"schemas/v2/envelopes/schema.request.schema.json": "be2c5f7aa881cdd252ccff47ac02193659e1393633a1541f4297383205d384a2",
|
|
142
142
|
"schemas/v2/envelopes/schema.response.schema.json": "d049a081267c06c28879c331b147f3ed821b14ccfd93005c67aff64db0a8c68e",
|
|
143
143
|
"schemas/v2/envelopes/ui.a2ui-surface.schema.json": "0a06fbbdc8ba797a6818c79336a98e211ac5ba5747945b4e4098072cd3974d79",
|
|
144
|
-
"schemas/v2/error-envelope.schema.json": "
|
|
144
|
+
"schemas/v2/error-envelope.schema.json": "d550beeab958b528c8734fd32e74c438006b99172c5c586f9e0af20ef4b2bee7",
|
|
145
145
|
"schemas/v2/eval-summary.schema.json": "dfc995c8481ed212e6f3d83de982547898a7938be8c891ddb0d3efe84352908d",
|
|
146
146
|
"schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
|
|
147
147
|
"schemas/v2/form-content-pack-manifest.schema.json": "307884e57946703b6f86ec283034bf56beb68bea4a000743ebbc9eebeed31b07",
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"schemas/v2/localized-content-section.schema.json": "29c7bad0ad9ebc6c45211282e8afb3fdb35a4ca075ce0404b6e2c29f35fa5313",
|
|
157
157
|
"schemas/v2/memory-entry.schema.json": "290576ae84f4fef4cdf975932b87963283b11c50ea3d2654b32cd96aa6537c3d",
|
|
158
158
|
"schemas/v2/memory-list-options.schema.json": "f66703bccb68efb28391c273abb66aa117ee5b5ce475c477846ed700c336afcd",
|
|
159
|
-
"schemas/v2/node-pack-manifest.schema.json": "
|
|
159
|
+
"schemas/v2/node-pack-manifest.schema.json": "771328fa73691e7f08fca3c628c4e8ba68fa4e43529faaf1a6358aecf26b13f2",
|
|
160
160
|
"schemas/v2/orchestrator-decision.schema.json": "2da17538e84cfbe70151eb118f1ccf670cff58b8697148e394074d32ac4af6b4",
|
|
161
161
|
"schemas/v2/org-chart-responsibility-view.schema.json": "ebac7d2889bfd659bcf30311efb0d6eb0c7e940b972b6b62da69c7351b5bb586",
|
|
162
162
|
"schemas/v2/pack-lockfile.schema.json": "2685417702c105146b780cd99154296a46578fbb10d81c8c713348ee26fea898",
|
|
@@ -165,12 +165,12 @@
|
|
|
165
165
|
"schemas/v2/prompt-ref.schema.json": "11538fa36a7a652c93100cac4f1857eb4130137b546e3d94890618636d44b729",
|
|
166
166
|
"schemas/v2/prompt-template.schema.json": "494fc83028cb3af5dbb8a943d0e83b2348f7775e9fa02369162bcb58a6f81559",
|
|
167
167
|
"schemas/v2/proposal.schema.json": "72623553611f00e87b4c7972d69ebeba73c2222b01ead75a44a0c2114b679fea",
|
|
168
|
-
"schemas/v2/registry-version-manifest.schema.json": "
|
|
168
|
+
"schemas/v2/registry-version-manifest.schema.json": "270704ebc6bae63be949efbf9503578938b103733db9da3cb8fcbee1cd7684dd",
|
|
169
169
|
"schemas/v2/residency.schema.json": "af2658a7104cbcacd0e20a91226271c4a77f91ef46ae97612768fe743e6091d8",
|
|
170
170
|
"schemas/v2/run-ancestry-response.schema.json": "907e36341ba83a2147a5d76963950b6dcd47506b663ecbccae9dfa553f3724d3",
|
|
171
171
|
"schemas/v2/run-diff-response.schema.json": "6ab6cd265dc7e0a2086c6659c46aa0e7b43e6302949f0adac5d7deb1c7c0ecd3",
|
|
172
172
|
"schemas/v2/run-event-payloads.schema.json": "8eb0e05e1bd3399b0e2654a1ec5a13198ac4ec8459a1fcbfd800ab2d0a0ae8c6",
|
|
173
|
-
"schemas/v2/run-event.schema.json": "
|
|
173
|
+
"schemas/v2/run-event.schema.json": "4ec8eb048a79ca1343e00aa018acbf86814b5eda6768add6b17e8d7b83638fdf",
|
|
174
174
|
"schemas/v2/run-options.schema.json": "6d6052596ab0f17acfb00fdfc89907da7724f952e5ffbe1f81d94ba23d4c77cd",
|
|
175
175
|
"schemas/v2/run-orchestrator-decided-event.schema.json": "bb76e15ff72e669f12938684033b55dc567470e1a378e19ffc3fa0e4034ab5a7",
|
|
176
176
|
"schemas/v2/run-snapshot.schema.json": "e77bf491d11c1414e62ed84cbf0c1d7149279b133a2101dc8f5eec89d3e45204",
|
|
@@ -198,21 +198,21 @@
|
|
|
198
198
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
199
199
|
"spec/v1/alias-detectors.json": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
|
|
200
200
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
201
|
-
"spec/v1/core-standard-manifest.json": "
|
|
201
|
+
"spec/v1/core-standard-manifest.json": "6d0dd7cec8085554565060d2b5ed3abdcd3d13d9940404de33cac17633f3c23a",
|
|
202
202
|
"spec/v1/deprecations.json": "520d79927a8ed43ccf62c207a7cf6cf7b19b16e00bbbbdf0410667e939c19d85",
|
|
203
203
|
"spec/v1/deprecations.schema.json": "4cdbbb8eb9c0092913a5704c943880f0c8581bcf0f640a7df5a84105d53c8aee",
|
|
204
204
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
205
205
|
"spec/v1/event-codemap.schema.json": "d52caf29071e3784d4c20259cdbd5a7b533986548a440bb632a80b82cadd7ea5",
|
|
206
206
|
"spec/v1/extensions.json": "4d7ba68bae7c7f50cc655b94646ad25945a5dd63b703d43c1d015470141cd668",
|
|
207
|
-
"spec/v1/gaps.json": "
|
|
207
|
+
"spec/v1/gaps.json": "96ca051cb20f1a669b355d67a89421c56fef28aa5e6710c9519f592f26af33a3",
|
|
208
208
|
"spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
|
|
209
209
|
"spec/v1/migrations.json": "8839497d6a4830b53fa7867014a8336f0d725ef2346352e54fb580b7947897af",
|
|
210
210
|
"spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
|
|
211
211
|
"spec/v1/operation-path-manifest.json": "5f5f4e3842669371730ebbd1aace3fb794192615f0018dc144f484ba5db82ac3",
|
|
212
212
|
"spec/v1/spec-gaps.json": "6cc9962c6b969f632e07a78b52a4f61447ff579e2990cbae989866f584a86042",
|
|
213
|
-
"spec/v2/declaration.json": "
|
|
213
|
+
"spec/v2/declaration.json": "892b6fae1e87f279ff75451f67ffab2a63ef883203f8066886bd60f165469348",
|
|
214
214
|
"spec/v2/declaration.schema.json": "b0881a76228dfbd1b85a2ec7b7259a60197f768344535cdb475db6ff719b0e54",
|
|
215
|
-
"spec/v2/errors.json": "
|
|
215
|
+
"spec/v2/errors.json": "ad1725dc7c11002450e8abc682858eb5c11952a0718ea8ef6bdb2fb5ae6e7e21",
|
|
216
216
|
"spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
|
|
217
217
|
"spec/v2/event-codemap.schema.json": "ce2d0ca085d7668d7ef745d2560b385ec6881d24a6d4b47c49931986b2ba642d",
|
|
218
218
|
"spec/v2/ext/provider-idempotency/registry.json": "5b5fecad604fb4ab38da39f5abe697a4e57b05406c398f8afeb377331b2bd965",
|
|
@@ -224,11 +224,11 @@
|
|
|
224
224
|
"spec/v2/facets/mcp.schema.json": "f56609fe24ba6160ad29ae31aa16043354ff005e5cb6a0f3871c4d226087cc08",
|
|
225
225
|
"spec/v2/facets/replay.schema.json": "0a5aefb0108c708c6794fadd533cf256709704c4f16dbf44cd63fa8980c78018",
|
|
226
226
|
"spec/v2/facets/sandbox.schema.json": "e75f54486d387b2d089791aa93b5093cd1450f1920753a0f97fc23acdfe978ed",
|
|
227
|
-
"spec/v2/facets/webhooks.schema.json": "
|
|
227
|
+
"spec/v2/facets/webhooks.schema.json": "0b766033ef4b4c6c2ac6f9979a19843ba024d3a1d1576972dcb55aaba52f1b9a",
|
|
228
228
|
"spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
|
|
229
229
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
230
230
|
"spec/v2/profiles.json": "1f0ee40491131cf561ef19a0394c47ba476b4567514fe16befb6cada4a10a5a4",
|
|
231
|
-
"spec/v2/release.json": "
|
|
231
|
+
"spec/v2/release.json": "ef05b0ec4ef4296dfbbacf37e97612b3c1f8d11b701890caa7b9c8462eab8359"
|
|
232
232
|
},
|
|
233
|
-
"corpusCommit": "
|
|
233
|
+
"corpusCommit": "18c32663eb8acfe76d9b5906f69c82ed214987e0"
|
|
234
234
|
}
|
package/src/cli.ts
CHANGED
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
import { spawnSync } from 'node:child_process';
|
|
36
36
|
import { fileURLToPath } from 'node:url';
|
|
37
37
|
import { dirname, resolve as resolvePath, join } from 'node:path';
|
|
38
|
+
import { createRequire } from 'node:module';
|
|
38
39
|
import { createHash } from 'node:crypto';
|
|
39
|
-
import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
40
|
+
import { existsSync, readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
40
41
|
import { tmpdir } from 'node:os';
|
|
41
42
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
42
43
|
import addFormats from 'ajv-formats';
|
|
@@ -44,12 +45,14 @@ import { SCHEMAS_DIR } from './lib/paths.js';
|
|
|
44
45
|
import { readLedgerFile } from './lib/requirement-ledger.js';
|
|
45
46
|
import { deriveRequirementDispositions } from './lib/scenario-disposition.js';
|
|
46
47
|
import { scrubEvidence, evidenceSecretsFromEnv, verifyBundleV2 } from './lib/certification-bundle-verify.js';
|
|
48
|
+
import { publicKeyFromPrivate, signBundleV3, verifierSign, verifyBundleV3, witnessDigest, type BundleV3, type BundleV3Requirement } from './lib/certification-bundle-v3.js';
|
|
47
49
|
import {
|
|
48
50
|
deriveProfiles,
|
|
49
51
|
isCoreStandard,
|
|
50
52
|
agentPlatformStatus,
|
|
51
53
|
DEPRECATED_PROFILE_ALIASES,
|
|
52
54
|
type DiscoveryPayload,
|
|
55
|
+
PROFILE_FLOOR_SCENARIOS,
|
|
53
56
|
} from './lib/profiles.js';
|
|
54
57
|
|
|
55
58
|
interface ParsedArgs {
|
|
@@ -64,7 +67,14 @@ interface ParsedArgs {
|
|
|
64
67
|
readonly implVersion: string | undefined;
|
|
65
68
|
/** RFC 0089 — emit a conformance certification bundle to this path. */
|
|
66
69
|
readonly certify: string | undefined;
|
|
67
|
-
readonly bundleVersion: '
|
|
70
|
+
readonly bundleVersion: '2' | '3';
|
|
71
|
+
/** Suite 2.0.0 (RFC 0168 §E): v3 needs the build identity, the signing key, and the tier. */
|
|
72
|
+
readonly hostBuild: { kind: 'image-digest' | 'commit' | 'artifact-sha256'; id: string } | undefined;
|
|
73
|
+
readonly evidenceTier: 'self' | 'steward' | 'independent';
|
|
74
|
+
readonly signingKeyPath: string | undefined;
|
|
75
|
+
readonly signingKeyId: string | undefined;
|
|
76
|
+
readonly verifierKeyPath: string | undefined;
|
|
77
|
+
readonly verifierKeyId: string | undefined;
|
|
68
78
|
/**
|
|
69
79
|
* S43 (2026-08-18) — cap on concurrently running scenario FILES, forwarded to
|
|
70
80
|
* vitest `--maxWorkers`. Unset = vitest's default (one worker per CPU), which
|
|
@@ -86,7 +96,10 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
86
96
|
// Suite 1.152.0: bundle v2 (RFC 0148) is the default. v1 stays reachable via
|
|
87
97
|
// `--bundle-version 1` through the RFC 0148 migration window (ends
|
|
88
98
|
// 2026-11-10) and is removed at v2.0 (spec/v1/deprecations.json).
|
|
89
|
-
let bundleVersion: '
|
|
99
|
+
let bundleVersion: '2' | '3' = '3';
|
|
100
|
+
let hostBuild: ParsedArgs['hostBuild'];
|
|
101
|
+
let evidenceTier: ParsedArgs['evidenceTier'] = 'self';
|
|
102
|
+
let signingKeyPath: string | undefined, signingKeyId: string | undefined, verifierKeyPath: string | undefined, verifierKeyId: string | undefined;
|
|
90
103
|
let targetMajor: 1 | 2 | undefined;
|
|
91
104
|
let maxWorkers: number | undefined = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
|
|
92
105
|
|
|
@@ -138,19 +151,29 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
138
151
|
implVersion = nextValue();
|
|
139
152
|
break;
|
|
140
153
|
case '--bundle-version': {
|
|
141
|
-
const v =
|
|
142
|
-
if (v !== '
|
|
143
|
-
|
|
144
|
-
process.exit(2);
|
|
145
|
-
}
|
|
154
|
+
const v = argv[++i];
|
|
155
|
+
if (v !== '2' && v !== '3') { process.stderr.write(`openwop-conformance: --bundle-version must be 2 or 3 (got ${String(v)}); v1 is gone in suite 2.0.0 (RFC 0168 §E.3)\n`); process.exit(2); }
|
|
156
|
+
if (v === '2') process.stderr.write('openwop-conformance: --bundle-version 2 is DEPRECATED — v2 bundles stop substantiating a certification at v1 end-of-support (RFC 0168 §E.3); the default is 3.\n');
|
|
146
157
|
bundleVersion = v;
|
|
147
|
-
if (v === '1') {
|
|
148
|
-
process.stderr.write(
|
|
149
|
-
'openwop-conformance: --bundle-version 1 is DEPRECATED (RFC 0148). A v1 bundle ceases to substantiate a new certification after 2026-11-10 and the format is removed at v2.0; omit the flag to emit bundle v2.\n',
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
158
|
break;
|
|
153
159
|
}
|
|
160
|
+
case '--host-build': {
|
|
161
|
+
const v = argv[++i] ?? '';
|
|
162
|
+
const mm = /^(image-digest|commit|artifact-sha256):(.+)$/.exec(v);
|
|
163
|
+
if (!mm) { process.stderr.write(`openwop-conformance: --host-build must be <image-digest|commit|artifact-sha256>:<id> (got ${JSON.stringify(v)})\n`); process.exit(2); }
|
|
164
|
+
hostBuild = { kind: mm[1] as 'image-digest' | 'commit' | 'artifact-sha256', id: mm[2] };
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case '--evidence-tier': {
|
|
168
|
+
const v = argv[++i];
|
|
169
|
+
if (v !== 'self' && v !== 'steward' && v !== 'independent') { process.stderr.write('openwop-conformance: --evidence-tier must be self, steward or independent\n'); process.exit(2); }
|
|
170
|
+
evidenceTier = v;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case '--signing-key': signingKeyPath = argv[++i]; break;
|
|
174
|
+
case '--signing-key-id': signingKeyId = argv[++i]; break;
|
|
175
|
+
case '--verifier-key': verifierKeyPath = argv[++i]; break;
|
|
176
|
+
case '--verifier-key-id': verifierKeyId = argv[++i]; break;
|
|
154
177
|
case '--certify':
|
|
155
178
|
certify = nextValue();
|
|
156
179
|
break;
|
|
@@ -175,6 +198,12 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
175
198
|
certify,
|
|
176
199
|
bundleVersion,
|
|
177
200
|
targetMajor,
|
|
201
|
+
hostBuild,
|
|
202
|
+
evidenceTier,
|
|
203
|
+
signingKeyPath,
|
|
204
|
+
signingKeyId,
|
|
205
|
+
verifierKeyPath,
|
|
206
|
+
verifierKeyId,
|
|
178
207
|
maxWorkers,
|
|
179
208
|
};
|
|
180
209
|
}
|
|
@@ -216,7 +245,11 @@ Certification (RFC 0089):
|
|
|
216
245
|
--target-major <1|2> Suite 2.0.0 (RFC 0168 §D.3): run the scenarios for this protocol
|
|
217
246
|
major. Default: the host's preferredVersion (RFC 0179), else
|
|
218
247
|
max(protocolVersions[]), else 1. Selection is scenario-majors.json.
|
|
219
|
-
--
|
|
248
|
+
--host-build <k>:<id> v3: the build identity (image-digest|commit|artifact-sha256), or OPENWOP_HOST_BUILD.
|
|
249
|
+
--signing-key <pem> v3: Ed25519 private key (PKCS8 PEM) that signs the bundle, or OPENWOP_BUNDLE_SIGNING_KEY.
|
|
250
|
+
--signing-key-id <id> v3: the keyId the host publishes for that key, or OPENWOP_BUNDLE_SIGNING_KEY_ID.
|
|
251
|
+
--evidence-tier <t> v3: self | steward | independent (independent needs --verifier-key/--verifier-key-id).
|
|
252
|
+
--bundle-version <2|3> Certification bundle format. Default 2 (corrected 2026-09-03, RFC 0168 §E.4; the text said 1 while the code set 2). Version 2 (RFC 0148
|
|
220
253
|
§C) records per-requirement DISPOSITIONS instead of pass/fail/skip
|
|
221
254
|
file lists, so "we could not check" stops being indistinguishable
|
|
222
255
|
from "checked and it holds". See the note it prints.
|
|
@@ -278,6 +311,59 @@ function claimedProfilesFor(doc: DiscoveryPayload): string[] {
|
|
|
278
311
|
return profiles;
|
|
279
312
|
}
|
|
280
313
|
|
|
314
|
+
/**
|
|
315
|
+
* The v2 profile registry is a set of predicates over the DECLARATION
|
|
316
|
+
* (RFC 0169 §C.1): every listed family present as a record, every listed
|
|
317
|
+
* metadata key present. The v1 derivation cannot stand in — `isCore` wants a
|
|
318
|
+
* root `protocolVersion` plus `supportedEnvelopes`/`schemaVersions`/`limits`,
|
|
319
|
+
* shapes a closed v2 root does not have — so a major-2 run claimed NOTHING and
|
|
320
|
+
* no v2 host could ever certify. Falls back to the empty set only when the
|
|
321
|
+
* registry is genuinely absent from the layout, and says so.
|
|
322
|
+
*/
|
|
323
|
+
function claimedProfilesForV2(doc: DiscoveryPayload, conformanceRoot: string): string[] {
|
|
324
|
+
// Resolve the peer the way `lib/paths.ts` does — through Node's resolver from
|
|
325
|
+
// this package — instead of guessing directory shapes. Hand-rolled candidates
|
|
326
|
+
// found the registry in a repo checkout and missed it in every published
|
|
327
|
+
// install, where npm hoists the peer to a SIBLING package dir: the probe that
|
|
328
|
+
// walked one level up landed on the `@openwop/` scope directory, not a
|
|
329
|
+
// package, so a real host run silently claimed nothing.
|
|
330
|
+
const candidates = [];
|
|
331
|
+
try {
|
|
332
|
+
const req = createRequire(resolvePath(conformanceRoot, 'package.json'));
|
|
333
|
+
candidates.push(resolvePath(dirname(req.resolve('@openwop/spec-artifacts/package.json')), 'spec', 'v2', 'profiles.json'));
|
|
334
|
+
} catch { /* not installed as a package; the repo-layout candidates below */ }
|
|
335
|
+
candidates.push(
|
|
336
|
+
resolvePath(conformanceRoot, 'spec', 'v2', 'profiles.json'),
|
|
337
|
+
resolvePath(conformanceRoot, '..', 'spec', 'v2', 'profiles.json'),
|
|
338
|
+
resolvePath(conformanceRoot, '..', 'spec-artifacts', 'spec', 'v2', 'profiles.json'),
|
|
339
|
+
);
|
|
340
|
+
const found = candidates.find((c) => existsSync(c));
|
|
341
|
+
if (found === undefined) {
|
|
342
|
+
process.stderr.write('openwop-conformance --certify: spec/v2/profiles.json not found in this layout; claimedProfiles is empty (RFC 0169 §C.1).\n');
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
let registry: { profiles?: Array<{ id?: unknown; predicate?: { families?: unknown; metadata?: unknown } }> };
|
|
346
|
+
try {
|
|
347
|
+
registry = JSON.parse(readFileSync(found, 'utf8'));
|
|
348
|
+
} catch {
|
|
349
|
+
process.stderr.write(`openwop-conformance --certify: ${found} is unreadable; claimedProfiles is empty.\n`);
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
const root = doc as unknown as Record<string, unknown>;
|
|
353
|
+
const isRecord = (k: string): boolean => {
|
|
354
|
+
const v = root[k];
|
|
355
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
356
|
+
};
|
|
357
|
+
const out: string[] = [];
|
|
358
|
+
for (const p of registry.profiles ?? []) {
|
|
359
|
+
if (typeof p.id !== 'string') continue;
|
|
360
|
+
const families = Array.isArray(p.predicate?.families) ? (p.predicate.families as unknown[]).map(String) : [];
|
|
361
|
+
const metadata = Array.isArray(p.predicate?.metadata) ? (p.predicate.metadata as unknown[]).map(String) : [];
|
|
362
|
+
if (families.every(isRecord) && metadata.every((k) => root[k] !== undefined)) out.push(p.id);
|
|
363
|
+
}
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
366
|
+
|
|
281
367
|
/** A single scenario test file's terminal state, derived from the vitest JSON report. */
|
|
282
368
|
type ScenarioState = 'passed' | 'failed' | 'skipped';
|
|
283
369
|
|
|
@@ -334,15 +420,21 @@ function scenarioStatesFromReport(report: VitestJsonReport): Map<string, Scenari
|
|
|
334
420
|
async function resolveTargetMajor(args: ParsedArgs, baseUrl: string | undefined, apiKey: string | undefined, conformanceRoot: string): Promise<{ major: 1 | 2; files: string[]; source: string }> {
|
|
335
421
|
let major: 1 | 2 | undefined = args.targetMajor;
|
|
336
422
|
let source = 'flag';
|
|
423
|
+
let dualStack = false;
|
|
337
424
|
if (major === undefined && baseUrl) {
|
|
338
425
|
try {
|
|
339
426
|
const res = await fetch(`${baseUrl.replace(/\/$/, '')}/.well-known/openwop`, { headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {} });
|
|
340
427
|
const doc = (await res.json()) as { preferredVersion?: string; protocolVersions?: string[] };
|
|
341
428
|
const pv = doc.preferredVersion ?? (doc.protocolVersions ?? []).map((v) => v).sort((a, b) => Number(b.split('.')[0]) - Number(a.split('.')[0]))[0];
|
|
429
|
+
if ((doc.protocolVersions ?? []).some((v) => v.startsWith('2.'))) dualStack = true;
|
|
342
430
|
if (pv) { major = Number(pv.split('.')[0]) >= 2 ? 2 : 1; source = doc.preferredVersion ? 'preferredVersion' : 'max(protocolVersions[])'; }
|
|
343
431
|
} catch { /* unreachable host: the default below; the run itself will report the failure */ }
|
|
344
432
|
}
|
|
345
433
|
if (major === undefined) { major = 1; source = 'default'; }
|
|
434
|
+
// Through the overlap `preferredVersion` names the 1.x member (versioning.md
|
|
435
|
+
// §1.1), so auto-detection on a dual-stack host resolves to major 1 by design.
|
|
436
|
+
// Say so, or an operator reads a v1 run as the host's only option.
|
|
437
|
+
if (major === 1 && dualStack) source += ' (the host also serves 2.x — pass --target-major 2 to measure it)';
|
|
346
438
|
const manifest = JSON.parse(readFileSync(resolvePath(conformanceRoot, 'scenario-majors.json'), 'utf8')) as { majors: Record<string, number[]> };
|
|
347
439
|
const files = Object.entries(manifest.majors).filter(([, m]) => m.includes(major as number)).map(([f]) => `src/scenarios/${f}`);
|
|
348
440
|
return { major, files, source };
|
|
@@ -353,10 +445,19 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
353
445
|
if (outPath === undefined) process.exit(2);
|
|
354
446
|
|
|
355
447
|
// (a) Fetch /.well-known/openwop verbatim + its canonical-JSON SHA-256.
|
|
448
|
+
// The target is resolved FIRST: `/.well-known/openwop` is one resource whose
|
|
449
|
+
// representation the `OpenWOP-Version` header selects, and through the overlap
|
|
450
|
+
// the header-less representation is the v1 document (spec/v2/core/versioning.md
|
|
451
|
+
// §1.1). Fetching it header-less on a major-2 run captured the v1 rendering, so
|
|
452
|
+
// `claimedProfiles` came out as the v1 set and the bundle described a contract
|
|
453
|
+
// the run never measured.
|
|
454
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
455
|
+
const conformanceRoot = resolvePath(here, '..');
|
|
456
|
+
const target = await resolveTargetMajor(args, baseUrl, apiKey, conformanceRoot);
|
|
356
457
|
const discoveryUrl = `${baseUrl.replace(/\/$/, '')}/.well-known/openwop`;
|
|
357
458
|
let document: DiscoveryPayload;
|
|
358
459
|
try {
|
|
359
|
-
const resp = await fetch(discoveryUrl, { headers: { Accept: 'application/json' } });
|
|
460
|
+
const resp = await fetch(discoveryUrl, { headers: { Accept: 'application/json', ...(target.major === 2 ? { 'OpenWOP-Version': '2.0' } : {}) } });
|
|
360
461
|
if (!resp.ok) {
|
|
361
462
|
process.stderr.write(
|
|
362
463
|
`openwop-conformance --certify: GET ${discoveryUrl} returned HTTP ${resp.status}.\n`,
|
|
@@ -373,12 +474,10 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
373
474
|
const sha256 = createHash('sha256').update(canonicalJSON(document)).digest('hex');
|
|
374
475
|
|
|
375
476
|
// (b) Derive claimedProfiles from the captured document.
|
|
376
|
-
const claimedProfiles = claimedProfilesFor(document);
|
|
477
|
+
const claimedProfiles = target.major === 2 ? claimedProfilesForV2(document, conformanceRoot) : claimedProfilesFor(document);
|
|
377
478
|
|
|
378
479
|
// (c) Run the suite, capturing per-scenario terminal state via the vitest
|
|
379
480
|
// JSON reporter. server-targeted scenarios live under src/scenarios/.
|
|
380
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
381
|
-
const conformanceRoot = resolvePath(here, '..');
|
|
382
481
|
const reportDir = mkdtempSync(join(tmpdir(), 'owp-certify-'));
|
|
383
482
|
const reportFile = join(reportDir, 'vitest-report.json');
|
|
384
483
|
// RFC 0148 §A ledger sink (S6): every scenario file records its disposition
|
|
@@ -392,7 +491,6 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
392
491
|
if (args.impl) env.OPENWOP_IMPLEMENTATION_NAME = args.impl;
|
|
393
492
|
if (args.implVersion) env.OPENWOP_IMPLEMENTATION_VERSION = args.implVersion;
|
|
394
493
|
|
|
395
|
-
const target = await resolveTargetMajor(args, baseUrl, apiKey, conformanceRoot);
|
|
396
494
|
env.OPENWOP_TARGET_MAJOR = String(target.major);
|
|
397
495
|
process.stderr.write(`openwop-conformance --certify: target major ${target.major} (${target.source}); ${target.files.length} scenario file(s)\n`);
|
|
398
496
|
const vitestArgs: string[] = [
|
|
@@ -506,6 +604,79 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
506
604
|
);
|
|
507
605
|
}
|
|
508
606
|
|
|
607
|
+
if (args.bundleVersion === '3') {
|
|
608
|
+
// ── Bundle v3 (RFC 0168 §E) ─────────────────────────────────────────────
|
|
609
|
+
const build = args.hostBuild ?? (() => {
|
|
610
|
+
const mm = /^(image-digest|commit|artifact-sha256):(.+)$/.exec(process.env['OPENWOP_HOST_BUILD'] ?? '');
|
|
611
|
+
return mm ? { kind: mm[1] as BundleV3['host']['build']['kind'], id: mm[2] } : undefined;
|
|
612
|
+
})();
|
|
613
|
+
const signingKeyPem = args.signingKeyPath ? readFileSync(args.signingKeyPath, 'utf8') : process.env['OPENWOP_BUNDLE_SIGNING_KEY'];
|
|
614
|
+
const keyId = args.signingKeyId ?? process.env['OPENWOP_BUNDLE_SIGNING_KEY_ID'];
|
|
615
|
+
if (!build || !signingKeyPem || !keyId) {
|
|
616
|
+
process.stderr.write('openwop-conformance --certify: a v3 bundle needs --host-build <kind>:<id> (or OPENWOP_HOST_BUILD), --signing-key <pem> (or OPENWOP_BUNDLE_SIGNING_KEY) and --signing-key-id (or OPENWOP_BUNDLE_SIGNING_KEY_ID) — an unsigned bundle does not exist in v3 (RFC 0168 §E.2).\n');
|
|
617
|
+
process.exit(2);
|
|
618
|
+
}
|
|
619
|
+
const rows3: BundleV3Requirement[] = derived.requirements.map((r) => ({ id: r.requirementId, scenario: r.scenarioId, result: r.disposition as BundleV3Requirement['result'], ...(r.assertionCount === undefined ? {} : { assertions: r.assertionCount }), ...(r.detail === undefined ? {} : { detail: r.detail }) }));
|
|
620
|
+
const totals3 = derived.totals;
|
|
621
|
+
const doc3 = document as Record<string, unknown>;
|
|
622
|
+
const protocolVersions = Array.isArray(doc3['protocolVersions']) ? (doc3['protocolVersions'] as string[]) : [String(doc3['protocolVersion'] ?? '')];
|
|
623
|
+
const preferredVersion = typeof doc3['preferredVersion'] === 'string' ? (doc3['preferredVersion'] as string) : (protocolVersions[0] ?? '');
|
|
624
|
+
// witnessCount: executed-pass rows on the profile's floor (PROFILE_FLOOR_SCENARIOS required + requiredAnyPrefix).
|
|
625
|
+
const witnessCountFor = (profile: string): number => {
|
|
626
|
+
const floor = PROFILE_FLOOR_SCENARIOS[profile];
|
|
627
|
+
if (!floor) return 0;
|
|
628
|
+
const onFloor = (scenario: string): boolean => floor.required.includes(scenario) || (floor.requiredAnyPrefix ?? []).some((pre) => scenario.startsWith(pre));
|
|
629
|
+
return derived.requirements.filter((r) => r.disposition === 'executed-pass' && onFloor(r.scenarioId)).length;
|
|
630
|
+
};
|
|
631
|
+
const claimed3 = claimedProfiles.filter((p) => !(p in DEPRECATED_PROFILE_ALIASES)).map((p) => ({ id: p, evidenceTier: args.evidenceTier, witnessCount: witnessCountFor(p), certified: !notHeld.has(p) && !rejectedProfiles.some((v) => v.profile === p) && totals3.blocked === 0 }));
|
|
632
|
+
let relaxations: BundleV3['host']['relaxations'];
|
|
633
|
+
if (process.env['OPENWOP_HOST_RELAXATIONS']) { try { relaxations = JSON.parse(process.env['OPENWOP_HOST_RELAXATIONS']) as BundleV3['host']['relaxations']; } catch { process.stderr.write('openwop-conformance --certify: OPENWOP_HOST_RELAXATIONS is not JSON\n'); process.exit(2); } }
|
|
634
|
+
const lockPath = resolvePath(conformanceRoot, 'dist', 'spec-artifacts.lock.json');
|
|
635
|
+
const lock = existsSync(lockPath) ? (JSON.parse(readFileSync(lockPath, 'utf8')) as { version: string; stampSha256: string }) : undefined;
|
|
636
|
+
const nonPass = rows3.filter((r) => r.result !== 'executed-pass');
|
|
637
|
+
const unsigned: Omit<BundleV3, 'signature'> = {
|
|
638
|
+
bundleVersion: '3',
|
|
639
|
+
generatedAt: new Date().toISOString(),
|
|
640
|
+
suite: { name: '@openwop/openwop-conformance', version, targetMajor: target.major, specArtifactsVersion: lock?.version ?? 'repo-layout', ...(lock ? { stampSha256: lock.stampSha256 } : {}) },
|
|
641
|
+
host: { name: host.name, version: host.version, ...(host.vendor ? { vendor: host.vendor } : {}), build, signingKeyId: keyId, ...(relaxations && relaxations.length ? { relaxations } : {}) },
|
|
642
|
+
discovery: { url: discoveryUrl, sha256, protocolVersions, preferredVersion },
|
|
643
|
+
claimedProfiles: claimed3,
|
|
644
|
+
results: { totals: totals3, requirements: rows3 },
|
|
645
|
+
witnessSha256: witnessDigest(rows3),
|
|
646
|
+
assertionCount: rows3.reduce((n, r) => n + (r.assertions ?? 0), 0),
|
|
647
|
+
...(nonPass.length ? { detail: { nonPass: nonPass.map((r) => ({ id: r.id, result: r.result, reason: r.detail ?? '' })) } } : {}),
|
|
648
|
+
};
|
|
649
|
+
const signature = signBundleV3(unsigned, signingKeyPem, keyId);
|
|
650
|
+
const v3: BundleV3 = { ...unsigned, signature };
|
|
651
|
+
if (args.evidenceTier === 'independent') {
|
|
652
|
+
const vk = args.verifierKeyPath ? readFileSync(args.verifierKeyPath, 'utf8') : process.env['OPENWOP_BUNDLE_VERIFIER_KEY'];
|
|
653
|
+
const vkId = args.verifierKeyId ?? process.env['OPENWOP_BUNDLE_VERIFIER_KEY_ID'];
|
|
654
|
+
if (!vk || !vkId) { process.stderr.write('openwop-conformance --certify: --evidence-tier independent needs --verifier-key and --verifier-key-id (RFC 0168 §E.2)\n'); process.exit(2); }
|
|
655
|
+
v3.verifierSignature = verifierSign(unsigned, vk, vkId);
|
|
656
|
+
}
|
|
657
|
+
const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem]);
|
|
658
|
+
const scrubbed3 = scrubEvidence(v3, secrets3);
|
|
659
|
+
const v3Out = scrubbed3.value as BundleV3;
|
|
660
|
+
const audit3 = verifyBundleV3(v3Out, { hostPublicKeyPem: publicKeyFromPrivate(signingKeyPem) });
|
|
661
|
+
const emitterDefects = audit3.rejections.filter((r) => !['blocked-certified', 'relaxed-profile-certified', 'independent-unverifiable'].includes(r.kind));
|
|
662
|
+
if (emitterDefects.length > 0) {
|
|
663
|
+
process.stderr.write('openwop-conformance --certify: assembled v3 bundle FAILED self-verification (emitter defect):\n' + emitterDefects.map((r) => ` - [${r.kind}] ${r.detail}`).join('\n') + '\n');
|
|
664
|
+
process.exit(2);
|
|
665
|
+
}
|
|
666
|
+
const v3Schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'v2', 'certification-bundle.schema.json'), 'utf8')) as Record<string, unknown>;
|
|
667
|
+
const v3Ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
668
|
+
addFormats(v3Ajv);
|
|
669
|
+
v3Ajv.addSchema(JSON.parse(readFileSync(join(SCHEMAS_DIR, 'v2', 'ids.schema.json'), 'utf8')) as Record<string, unknown>);
|
|
670
|
+
const v3Validate = v3Ajv.compile(v3Schema);
|
|
671
|
+
if (!v3Validate(v3Out)) {
|
|
672
|
+
process.stderr.write('openwop-conformance --certify: assembled v3 bundle FAILED schema validation:\n' + `${JSON.stringify(v3Validate.errors, null, 2)}\n`);
|
|
673
|
+
process.exit(2);
|
|
674
|
+
}
|
|
675
|
+
writeFileSync(outPath, `${JSON.stringify(v3Out, null, 2)}\n`);
|
|
676
|
+
process.stdout.write(`openwop-conformance --certify: wrote bundle v3 → ${outPath} (${rows3.length} requirement rows, ${v3Out.assertionCount} assertions, witness ${v3Out.witnessSha256.slice(0, 12)}, signed by ${keyId}; certified: ${audit3.certifiedProfiles.join(', ') || 'none'})\n`);
|
|
677
|
+
process.exit(rejectedProfiles.length > 0 ? 3 : failed.length > 0 ? 1 : 0);
|
|
678
|
+
}
|
|
679
|
+
|
|
509
680
|
if (args.bundleVersion === '2') {
|
|
510
681
|
const scenarioIds = [...passed, ...failed, ...skipped].sort();
|
|
511
682
|
const manifestSha = createHash('sha256').update(scenarioIds.join('\n'), 'utf8').digest('hex');
|