@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.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +209 -14
  3. package/dist/lib/certification-bundle-v3.js +142 -0
  4. package/dist/lib/requirement-ids.js +23 -0
  5. package/dist/lib/requirement-ledger.js +1 -0
  6. package/dist/lib/scenario-disposition.js +5 -1
  7. package/dist/spec-artifacts.lock.json +2 -2
  8. package/package.json +2 -2
  9. package/requirements.json +2744 -4
  10. package/scenario-majors.json +155 -2
  11. package/schemas/CORPUS-STAMP.json +17 -17
  12. package/src/cli.ts +190 -19
  13. package/src/lib/certification-bundle-v3.ts +175 -0
  14. package/src/lib/driver.ts +9 -1
  15. package/src/lib/era2-seed.ts +197 -0
  16. package/src/lib/requirement-ids.ts +21 -0
  17. package/src/lib/requirement-ledger.ts +7 -1
  18. package/src/lib/scenario-disposition.ts +5 -1
  19. package/src/lib/seams.ts +45 -0
  20. package/src/lib/v2.ts +62 -0
  21. package/src/scenarios/v2-approver-enforced.test.ts +97 -0
  22. package/src/scenarios/v2-assurance-downgrade-audited.test.ts +75 -0
  23. package/src/scenarios/v2-bundle-v3-signed.test.ts +96 -0
  24. package/src/scenarios/v2-capabilities-root-closed.test.ts +63 -0
  25. package/src/scenarios/v2-capability-record-shape.test.ts +104 -0
  26. package/src/scenarios/v2-chain-pin-exact.test.ts +118 -0
  27. package/src/scenarios/v2-coherence-not-in-bundle.test.ts +98 -0
  28. package/src/scenarios/v2-compensation-read-projection.test.ts +85 -0
  29. package/src/scenarios/v2-configurable-closed.test.ts +67 -0
  30. package/src/scenarios/v2-dual-stack-negotiation.test.ts +118 -0
  31. package/src/scenarios/v2-effect-identity-business-key.test.ts +132 -0
  32. package/src/scenarios/v2-effect-seam-manifest.test.ts +163 -0
  33. package/src/scenarios/v2-enum-growth-rule.test.ts +69 -0
  34. package/src/scenarios/v2-era-2-append-vocabulary.test.ts +140 -0
  35. package/src/scenarios/v2-era-key.test.ts +86 -0
  36. package/src/scenarios/v2-error-registry.test.ts +89 -0
  37. package/src/scenarios/v2-event-type-closed.test.ts +97 -0
  38. package/src/scenarios/v2-fork-a-v1-run.test.ts +109 -0
  39. package/src/scenarios/v2-form-when-reuses-edge-conditions.test.ts +70 -0
  40. package/src/scenarios/v2-header-scheme.test.ts +67 -0
  41. package/src/scenarios/v2-id-grammar.test.ts +77 -0
  42. package/src/scenarios/v2-idempotency-key-grammar.test.ts +65 -0
  43. package/src/scenarios/v2-interrupt-token-scheme.test.ts +55 -0
  44. package/src/scenarios/v2-lane-issuer-advertised.test.ts +62 -0
  45. package/src/scenarios/v2-legacy-profiles-absent.test.ts +78 -0
  46. package/src/scenarios/v2-manifest-ceiling-refused.test.ts +130 -0
  47. package/src/scenarios/v2-manifest-hatch-carried.test.ts +120 -0
  48. package/src/scenarios/v2-min-client-version.test.ts +48 -0
  49. package/src/scenarios/v2-minimum-version-refused.test.ts +141 -0
  50. package/src/scenarios/v2-mrtr-rounds-ceiling.test.ts +95 -0
  51. package/src/scenarios/v2-negotiation-authenticated.test.ts +125 -0
  52. package/src/scenarios/v2-negotiation-decided-emitted.test.ts +149 -0
  53. package/src/scenarios/v2-no-transport-advertisement.test.ts +57 -0
  54. package/src/scenarios/v2-owner-subject-required.test.ts +72 -0
  55. package/src/scenarios/v2-pack-isolation.test.ts +147 -0
  56. package/src/scenarios/v2-payload-registry-closed.test.ts +110 -0
  57. package/src/scenarios/v2-peer-dependency-declared.test.ts +132 -0
  58. package/src/scenarios/v2-pinned-run-disposition.test.ts +124 -0
  59. package/src/scenarios/v2-poll-cursor-v2.test.ts +114 -0
  60. package/src/scenarios/v2-preferred-version-default.test.ts +83 -0
  61. package/src/scenarios/v2-profiles-derived-only.test.ts +70 -0
  62. package/src/scenarios/v2-provider-conflict.test.ts +115 -0
  63. package/src/scenarios/v2-refresh-sla.test.ts +78 -0
  64. package/src/scenarios/v2-relaxation-recorded.test.ts +134 -0
  65. package/src/scenarios/v2-revocation-honored.test.ts +66 -0
  66. package/src/scenarios/v2-subject-link-record.test.ts +102 -0
  67. package/src/scenarios/v2-unmapped-type-refused.test.ts +95 -0
  68. package/src/scenarios/v2-v1-events-translated.test.ts +105 -0
  69. package/src/scenarios/v2-v1-signed-webhook-accepted.test.ts +109 -0
  70. package/src/scenarios/v2-webhook-durable-delivery.test.ts +238 -0
  71. package/src/scenarios/v2-well-known-one-resource.test.ts +129 -0
  72. package/src/setup.ts +40 -4
@@ -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": 444,
4
+ "files": 495,
5
5
  "v1": 444,
6
- "v2": 1
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.0",
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": "b54c5a6ebd08577854d8f3f55e20ebf5aae0e57e457779e1de0937648aef4ef8",
13
- "api/v2/asyncapi.yaml": "bb5468d6a2c00fbeab030ec365df2ba47c362b464b6d91162ded81064074d926",
14
- "api/v2/openapi.yaml": "78b3f5a41ef1839f7628696465d3180ad2681616046918f6880575836bd20b57",
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": "87048be5c04cd88df0bc21938f371a31f9f28a1df8560f6f811d99276fca83a4",
119
- "schemas/v2/certification-bundle.schema.json": "06d450510b87fcfe22e4f3ba9dceef5f3ca9031ef65832d7b9d2f22b89028964",
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": "6fd825be2dc71559487ea7f68c42b1503f0606dddce764ea2d14f66533b9319c",
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": "3b7343dffbbaa4a539760e14714968ab9855d55a226f62bcb110f78a2ee47b56",
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": "e704e08b11be9a5c7dc7237c8cb8b99b079d7c29615909320c2bb9bad285b8c1",
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": "6be6c366af34eea1606b92a17ebae40d3d3390f4d54c38d9fce293ab820ea118",
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": "1d20ac568c5fc3e1e4d30bc0e4cd7e450118a447150912338f0fc3d3cf205a47",
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": "1986d648cd1ade06823f33edd20bd00d3ccf09781aa0a9992bb5f4117687b484",
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": "91409933c089d2415254b32ade698549ed9b4caf16877fbfa7c9b578df6aaf12",
213
+ "spec/v2/declaration.json": "892b6fae1e87f279ff75451f67ffab2a63ef883203f8066886bd60f165469348",
214
214
  "spec/v2/declaration.schema.json": "b0881a76228dfbd1b85a2ec7b7259a60197f768344535cdb475db6ff719b0e54",
215
- "spec/v2/errors.json": "204b2ae8029cd37becd9bf65e70a78b27fa7637818adfdd0d97677167c7d3405",
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": "9b40d658af3ac7d8c26edcef275b01080607e10124a871b6adde05df686a69f1",
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": "324675b23d36fd84e5ae0daecfc1aeacea53d12377cd40283ba2442bf57836c3"
231
+ "spec/v2/release.json": "ef05b0ec4ef4296dfbbacf37e97612b3c1f8d11b701890caa7b9c8462eab8359"
232
232
  },
233
- "corpusCommit": "7c81ace8312cd2e6a059a8d383534d67dccfaca7"
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: '1' | '2';
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: '1' | '2' = '2';
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 = nextValue();
142
- if (v !== '1' && v !== '2') {
143
- process.stderr.write(`--bundle-version must be 1 or 2 (got '${v}')\n`);
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
- --bundle-version <1|2> 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
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');