@openwop/openwop-conformance 2.0.0-rc.9 → 2.0.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +8 -3
  3. package/dist/cli.js +24 -34
  4. package/dist/lib/requirement-registry.js +69 -0
  5. package/dist/lib/scenario-disposition.js +84 -11
  6. package/dist/lib/seams.js +72 -0
  7. package/dist/lib/soft-skip.js +39 -8
  8. package/dist/spec-artifacts.lock.json +2 -2
  9. package/package.json +2 -2
  10. package/requirement-aliases.json +4 -1
  11. package/requirements.json +1028 -55
  12. package/scenario-majors.json +57 -3
  13. package/schemas/CORPUS-STAMP.json +40 -37
  14. package/src/cli.ts +23 -30
  15. package/src/global-setup.ts +11 -0
  16. package/src/lib/corpus-stamp.ts +24 -2
  17. package/src/lib/era2-seed.ts +12 -1
  18. package/src/lib/fixtures.ts +31 -0
  19. package/src/lib/requirement-registry.ts +68 -0
  20. package/src/lib/scenario-disposition.ts +88 -9
  21. package/src/lib/seams.ts +31 -1
  22. package/src/lib/soft-skip.ts +42 -7
  23. package/src/lib/sse.ts +8 -0
  24. package/src/scenarios/era-key-stamped-v1.test.ts +156 -0
  25. package/src/scenarios/pause-resume.test.ts +159 -75
  26. package/src/scenarios/v2-advertised-fixtures-exist.test.ts +25 -43
  27. package/src/scenarios/v2-advertised-path-space-served.test.ts +165 -0
  28. package/src/scenarios/v2-assurance-downgrade-audited.test.ts +1 -1
  29. package/src/scenarios/v2-chain-pin-exact.test.ts +1 -1
  30. package/src/scenarios/v2-coherence-not-in-bundle.test.ts +11 -2
  31. package/src/scenarios/v2-created-run-readable.test.ts +98 -0
  32. package/src/scenarios/v2-dual-stack-negotiation.test.ts +29 -1
  33. package/src/scenarios/v2-effect-identity-business-key.test.ts +1 -1
  34. package/src/scenarios/v2-effect-seam-manifest.test.ts +21 -55
  35. package/src/scenarios/v2-effect-seam-no-refire.test.ts +104 -0
  36. package/src/scenarios/v2-era-2-append-vocabulary.test.ts +16 -2
  37. package/src/scenarios/v2-interrupt-token-scheme.test.ts +1 -1
  38. package/src/scenarios/v2-malformed-body-envelope.test.ts +79 -0
  39. package/src/scenarios/v2-manifest-ceiling-refused.test.ts +1 -1
  40. package/src/scenarios/v2-manifest-hatch-carried.test.ts +1 -1
  41. package/src/scenarios/v2-minimum-version-refused.test.ts +1 -1
  42. package/src/scenarios/v2-mrtr-rounds-ceiling.test.ts +1 -1
  43. package/src/scenarios/v2-negotiation-authenticated.test.ts +1 -1
  44. package/src/scenarios/v2-negotiation-decided-emitted.test.ts +1 -1
  45. package/src/scenarios/v2-pack-isolation.test.ts +1 -1
  46. package/src/scenarios/v2-peer-dependency-declared.test.ts +1 -1
  47. package/src/scenarios/v2-poll-cursor-v2.test.ts +18 -0
  48. package/src/scenarios/v2-revocation-honored.test.ts +1 -1
  49. package/src/scenarios/v2-run-annotation-not-event.test.ts +71 -0
  50. package/src/scenarios/v2-run-bulk-cancel.test.ts +91 -0
  51. package/src/scenarios/v2-run-cancel.test.ts +97 -0
  52. package/src/scenarios/v2-run-completed-outputs.test.ts +93 -0
  53. package/src/scenarios/v2-run-diff-identical.test.ts +75 -0
  54. package/src/scenarios/v2-run-fork-prefix.test.ts +160 -0
  55. package/src/scenarios/v2-run-fork-refusals.test.ts +70 -0
  56. package/src/scenarios/v2-run-options-limits.test.ts +69 -0
  57. package/src/scenarios/v2-run-pause-resume.test.ts +117 -0
  58. package/src/scenarios/v2-run-snapshot-etag.test.ts +57 -0
  59. package/src/scenarios/v2-sse-last-event-id.test.ts +93 -0
  60. package/src/scenarios/v2-stream-mode-refusal.test.ts +118 -0
  61. package/src/scenarios/v2-stream-sse-projection.test.ts +79 -0
  62. package/src/scenarios/v2-subject-link-record.test.ts +1 -1
  63. package/src/scenarios/v2-v1-signed-webhook-accepted.test.ts +1 -1
  64. package/src/scenarios/version-negotiation.test.ts +25 -3
  65. package/src/setup.ts +104 -50
@@ -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": 498,
5
- "v1": 444,
6
- "v2": 55
4
+ "files": 516,
5
+ "v1": 445,
6
+ "v2": 72
7
7
  },
8
8
  "majors": {
9
9
  "a2a-1-0-agent-card.test.ts": [
@@ -564,6 +564,9 @@
564
564
  "envelope-variant-discriminator-static.test.ts": [
565
565
  1
566
566
  ],
567
+ "era-key-stamped-v1.test.ts": [
568
+ 1
569
+ ],
567
570
  "errors.test.ts": [
568
571
  1
569
572
  ],
@@ -1231,6 +1234,9 @@
1231
1234
  "v2-advertised-fixtures-exist.test.ts": [
1232
1235
  2
1233
1236
  ],
1237
+ "v2-advertised-path-space-served.test.ts": [
1238
+ 2
1239
+ ],
1234
1240
  "v2-approver-enforced.test.ts": [
1235
1241
  2
1236
1242
  ],
@@ -1258,6 +1264,9 @@
1258
1264
  "v2-configurable-closed.test.ts": [
1259
1265
  2
1260
1266
  ],
1267
+ "v2-created-run-readable.test.ts": [
1268
+ 2
1269
+ ],
1261
1270
  "v2-dual-stack-negotiation.test.ts": [
1262
1271
  2
1263
1272
  ],
@@ -1267,6 +1276,9 @@
1267
1276
  "v2-effect-seam-manifest.test.ts": [
1268
1277
  2
1269
1278
  ],
1279
+ "v2-effect-seam-no-refire.test.ts": [
1280
+ 2
1281
+ ],
1270
1282
  "v2-enum-growth-rule.test.ts": [
1271
1283
  2
1272
1284
  ],
@@ -1309,6 +1321,9 @@
1309
1321
  "v2-legacy-profiles-absent.test.ts": [
1310
1322
  2
1311
1323
  ],
1324
+ "v2-malformed-body-envelope.test.ts": [
1325
+ 2
1326
+ ],
1312
1327
  "v2-manifest-ceiling-refused.test.ts": [
1313
1328
  2
1314
1329
  ],
@@ -1369,6 +1384,45 @@
1369
1384
  "v2-revocation-honored.test.ts": [
1370
1385
  2
1371
1386
  ],
1387
+ "v2-run-annotation-not-event.test.ts": [
1388
+ 2
1389
+ ],
1390
+ "v2-run-bulk-cancel.test.ts": [
1391
+ 2
1392
+ ],
1393
+ "v2-run-cancel.test.ts": [
1394
+ 2
1395
+ ],
1396
+ "v2-run-completed-outputs.test.ts": [
1397
+ 2
1398
+ ],
1399
+ "v2-run-diff-identical.test.ts": [
1400
+ 2
1401
+ ],
1402
+ "v2-run-fork-prefix.test.ts": [
1403
+ 2
1404
+ ],
1405
+ "v2-run-fork-refusals.test.ts": [
1406
+ 2
1407
+ ],
1408
+ "v2-run-options-limits.test.ts": [
1409
+ 2
1410
+ ],
1411
+ "v2-run-pause-resume.test.ts": [
1412
+ 2
1413
+ ],
1414
+ "v2-run-snapshot-etag.test.ts": [
1415
+ 2
1416
+ ],
1417
+ "v2-sse-last-event-id.test.ts": [
1418
+ 2
1419
+ ],
1420
+ "v2-stream-mode-refusal.test.ts": [
1421
+ 2
1422
+ ],
1423
+ "v2-stream-sse-projection.test.ts": [
1424
+ 2
1425
+ ],
1372
1426
  "v2-subject-link-record.test.ts": [
1373
1427
  2
1374
1428
  ],
@@ -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.0.0-rc.9",
5
- "corpusTag": null,
4
+ "version": "2.0.0",
5
+ "corpusTag": "v2.0.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": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "1c0444d1bb0a4684d5f9eb0553c0e25305695bb7bbfe36adaa2189bd759b2ed4",
13
- "api/v2/asyncapi.yaml": "3c5c5c4c2b4bda63d77b2da0e242c0399758f141aee5a89d5e8285c44b78afe7",
14
- "api/v2/openapi.yaml": "d6b6689388d410388dc506dbed4cade59f6f0ef87671dfdfa6af97027e7114c3",
12
+ "api/seams-v2.yaml": "0568c438db8a84b9e51c221f118b660bad9f96722ec5607bce25b2fe1395afa0",
13
+ "api/v2/asyncapi.yaml": "fe31d75cdee4505900fe206fb5d1bdedbaa7ab6f54fd7ff1c63f14c5a1cd9595",
14
+ "api/v2/openapi.yaml": "1e33a399c7b29b93e317cab6ba60b891a3b9153e79c0ff907bf253f2c48aa6a6",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
@@ -31,7 +31,7 @@
31
31
  "schemas/artifact-type-pack-manifest.schema.json": "13932ea58e819e01b3a0cc7a65590b70683dd5fe9208fc06c64c339943d3c4ce",
32
32
  "schemas/audit-verify-result.schema.json": "e8fd013fb610ba5db27ab0113ec2eb18e7da5f9310dd3de2dd3dc87a259c21fe",
33
33
  "schemas/budget-policy.schema.json": "3d4fe30ae129fe136ab9ece395f2a2a42a76360786b1b9a1d5f1cbc753eb5b39",
34
- "schemas/capabilities.schema.json": "e672c35d503532d8039b5552115f5ec150d60317f537f13180d830b0fe315947",
34
+ "schemas/capabilities.schema.json": "b413e0235cad412081df09234237e0f322a85903b7411b91aaff54032e946671",
35
35
  "schemas/certification-bundle-v2.schema.json": "2f85530dd04824a15810c94e96869362dee1e78396eca21fdd18e5501416bb7d",
36
36
  "schemas/channel-presence-payload.schema.json": "f9d0ef4219814c2b765baf3e68cbe5eea2cb42379f516a93a0459dcd5f7db47e",
37
37
  "schemas/channel-written-payload.schema.json": "54723a56bc5a35232f64544ca686ac1ac959a5fb05b32b2401facca6b3ef51a3",
@@ -86,7 +86,7 @@
86
86
  "schemas/run-event.schema.json": "c70c06932b9831295042836213e617c8e946f20ddd49cf4e889b74d89d2175a8",
87
87
  "schemas/run-options.schema.json": "f8241ece6ddc6527de9160d811d42c231b4f77b935f2e107b3c05608545c59ab",
88
88
  "schemas/run-orchestrator-decided-event.schema.json": "17a43a3c44680a24d411141357a867b3caed6107d56de88fe2e062eaf93fa2c1",
89
- "schemas/run-snapshot.schema.json": "8a45f997beec0a2fa596f4631129a869cfbc37c9ad38583530ed11655301d2ab",
89
+ "schemas/run-snapshot.schema.json": "3343550f6973a0975c61624df569d341e843ac8592a2aa2b3b7cf540d72563ad",
90
90
  "schemas/security-advisory.schema.json": "9c550f0bf10a3193dcca4a127081fb66fe5dc1b6f729c4c17bfaea3ba3ce05fc",
91
91
  "schemas/self-hosted-runner-dispatch-frame.schema.json": "3f1d7edd572c6747657299525d8e85329b93b6fadf670e3a19c74d53e725adec",
92
92
  "schemas/self-hosted-runner-registration.schema.json": "6c781adf1bb915a9e47796494f4a527353cbbf72b480e5a05be4aac65a6d0c6e",
@@ -100,9 +100,9 @@
100
100
  "schemas/ui-plugin-message.schema.json": "346638babf1f8ea7b161cde75c0038b518cd2cc5bf0631772e20a9b74c96b5dd",
101
101
  "schemas/v2/a2a-task-state.schema.json": "aa9bd0c15f653b7ecdb17427d61b1bccaf8156972c4b1a437dd3a8afb717b318",
102
102
  "schemas/v2/a2ui-surface-delta-frame.schema.json": "43c10ebfd13c987520f0448df92fe74b52b846833763dfbbd5d35636bf75e8d9",
103
- "schemas/v2/agent-deployment-transition.schema.json": "5871ec764f531b2d34a6bb8b2e16efd35aad642664ca6d41411ba8d699fd759e",
104
- "schemas/v2/agent-deployment.schema.json": "61979201c850c69360a0840f3e23373284b5445b6c11212012ea53d33be9a6e7",
105
- "schemas/v2/agent-eval-suite.schema.json": "77c4bb06f5e21584994d4b4aaafc2304904008a51f0c6d8892794d7074e4f6b5",
103
+ "schemas/v2/agent-deployment-transition.schema.json": "d9fe6f2fa4dda8598bab4e5c051f68473ac0729dc0ebf31605954dda7abf5a80",
104
+ "schemas/v2/agent-deployment.schema.json": "dd1293ef8f20678bc36fc95fc4e5fcd135acbb10c61c1ddec8bc023e3f22622c",
105
+ "schemas/v2/agent-eval-suite.schema.json": "b2875edfc28b39c104fbf6f854ecfb81fe2d90b18f828ffd8d3232ef883cd208",
106
106
  "schemas/v2/agent-inventory-response.schema.json": "bbf19724986a867a59ef51c115a43ece1cc4a003836b2f7f45dfefb3078db8f9",
107
107
  "schemas/v2/agent-manifest.schema.json": "006ca6163ca8f9dc9fb91ca1ce01bd4c523f118680f6cbad971a4ac09e975381",
108
108
  "schemas/v2/agent-org-chart.schema.json": "e64f1bcc6c0e9d3fc39c363e8e5e73b2b1fc49275b92f89b0cb941a7a80d67f2",
@@ -112,14 +112,14 @@
112
112
  "schemas/v2/ai-envelope.schema.json": "0dc7dfbf032a37239ffd81fc2d67f999a40782ff5074866a56e414164cdee809",
113
113
  "schemas/v2/annotation-create.schema.json": "c2fc19d055be29bc499e53c4975a562eb76981e12c521a82f63f5827ac6ff6c7",
114
114
  "schemas/v2/annotation.schema.json": "75c6785775d00e62bc1282626c2c27eafde0f93a01c7e80e4e5e985055907475",
115
- "schemas/v2/artifact-type-pack-manifest.schema.json": "c911757562e521cb4f31a47c097f562573b64087efe60c8acb81fd352367d300",
115
+ "schemas/v2/artifact-type-pack-manifest.schema.json": "85630d1b760d0b41cf5a6fad2acfada39abaf7f0befd95cc4334e1e883e9615d",
116
116
  "schemas/v2/audit-verify-result.schema.json": "cb54c4d89ba5dfb73f49aa8959a980be65c2dc2a87a12f71ba36d3e836fe85da",
117
117
  "schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
118
118
  "schemas/v2/capabilities.schema.json": "792669b2cc7b1fd44ce9a4fd3e79696ad1277734e29299a4504b39eda15913ed",
119
- "schemas/v2/certification-bundle.schema.json": "d441198893c01cd14d84abe9c5bf591b69f47554db749c01899512f36cea11fa",
119
+ "schemas/v2/certification-bundle.schema.json": "b893ffa4de998b8b2b69a1038ebba0d7d50c8a32cde91ccae2b7c653fe459aa6",
120
120
  "schemas/v2/channel-presence-payload.schema.json": "9475d46f358d21c19126e78204987a5633979917a2293653354ef5c5e044d54e",
121
121
  "schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
122
- "schemas/v2/chat-card-pack-manifest.schema.json": "566982ce509a96d1619d58a72e383eff0773fe43fc65e4bfc85a4fe158f4eaff",
122
+ "schemas/v2/chat-card-pack-manifest.schema.json": "8df0d9d273ae22efc6876d5586833f1d4c2e551edc3ac6cf6e8f0719329f03dd",
123
123
  "schemas/v2/compact-tool-descriptor.schema.json": "fc03f20c254c0ddc2cba7e7d5a6c46080ebd2cf9fae36f3c2510cbf88aadebdc",
124
124
  "schemas/v2/compensation-policy.schema.json": "b3c1142637f5bf980f2b0bb67adb47e8cb7abb99a0405a4b6816ccf5d9c089d6",
125
125
  "schemas/v2/compensation-projection.schema.json": "f46977c7076347b5473faa8c4bbe73d8321c33b33c98fff2f26ee7cbada4ed98",
@@ -129,10 +129,10 @@
129
129
  "schemas/v2/conversation-turn.schema.json": "4c24aa85dd83f6ad00945b4a13ee8ff0a071893e884011e9289a07e97d22989d",
130
130
  "schemas/v2/credential-provenance.schema.json": "f86b09e94b1c524dec7d1c6353965665ae8891e9517faef6e742d639c5578c99",
131
131
  "schemas/v2/credential-reference.schema.json": "ea699ce30f58fb2c32d4279158b3f730f70be7b2dc8c3037693a76e66eb3e001",
132
- "schemas/v2/debug-bundle.schema.json": "e792a588131290bccce3042a279e8e57677018df94b0861bee2b2b98a9ea2124",
132
+ "schemas/v2/debug-bundle.schema.json": "0bfa4b1d91fefbc12c95670dba9d5eacb72b879bb9a5c074e821ca9ed0fee6fd",
133
133
  "schemas/v2/dispatch-config.schema.json": "4a7db312e6b4a14cf5b1ad2b71367878c77b6321cc456f5b3644dd6434e94cbd",
134
- "schemas/v2/effect-ledger-projection.schema.json": "9e690f8e5dbf9aa7c82eef4bf7cf96c024f0f8626ad5bbaca3ff25ac63c7cab2",
135
- "schemas/v2/effect-seam-manifest.schema.json": "3402a2956c810b2bfa9ec1ad0cb79d607ecea122cbe7d1e05184af006b06ef15",
134
+ "schemas/v2/effect-ledger-projection.schema.json": "928340b243c60aaf04c1b40adb09be8a90340d44f57be1dfff837be10f104822",
135
+ "schemas/v2/effect-seam-manifest.schema.json": "197ef9944ab027a50d1b9352f317cbbc12c8ad955fdb14650fb90477052ffef0",
136
136
  "schemas/v2/envelopes/clarification.request.schema.json": "9e39d7b09f0b3ce7902d6aa19ca0eb52867b634efc78a881469c4d44fb3333e7",
137
137
  "schemas/v2/envelopes/error.schema.json": "6a11fa3b9d61736fcb5675e6ac9825919aa609dceba13ef5615993d54647802b",
138
138
  "schemas/v2/envelopes/media.audio.schema.json": "a422352240a592c12840172e70835729f72b6b21915d14e926b6ffcd5d52a704",
@@ -141,22 +141,22 @@
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": "d550beeab958b528c8734fd32e74c438006b99172c5c586f9e0af20ef4b2bee7",
145
- "schemas/v2/eval-summary.schema.json": "dfc995c8481ed212e6f3d83de982547898a7938be8c891ddb0d3efe84352908d",
144
+ "schemas/v2/error-envelope.schema.json": "72d3332fad8fa36d07439c830acf352eefc6da2379374faa2ebbe49e98b8cb08",
145
+ "schemas/v2/eval-summary.schema.json": "bcb165148531d33b7dc01de83da3a049d19ebbef6d12300b17a234c7b03d77d9",
146
146
  "schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
147
147
  "schemas/v2/form-content-pack-manifest.schema.json": "307884e57946703b6f86ec283034bf56beb68bea4a000743ebbc9eebeed31b07",
148
148
  "schemas/v2/frontend-plugin-manifest.schema.json": "fed6b8ddb3056c53b1bfc91525410a4445a3c37a8bf9eb9f6f9964ffea26d993",
149
- "schemas/v2/goal.schema.json": "d202ef21ed3dbff87c25fd4b553fa978862a3200999878d25c5918f6794b3060",
149
+ "schemas/v2/goal.schema.json": "146c4182e36d3538778f947e668a92cdb69ec42ee0820104b00063b035e342d7",
150
150
  "schemas/v2/heartbeat-evaluated.schema.json": "4086b1ba1d0547c08b038dfb263ac89416a3e406430db1250156fb2f9b74f8d6",
151
151
  "schemas/v2/heartbeat-state-changed.schema.json": "eb68d0082aa996650e3a6b8ccf32457f959cebc7e6728cc7c331b9d171a4bef6",
152
- "schemas/v2/ids.schema.json": "0ccf5eb3ec0744296b3379f7ced3b46ec51d908705a4f03dc4404d42236ebe19",
152
+ "schemas/v2/ids.schema.json": "05c98e3f8a72fa79be123765bd9c2ea7c6620188d69df8524160399225316cd6",
153
153
  "schemas/v2/localized-content-language-settings.schema.json": "92240f19a2b4b2f05ab41a797f703485a28813848035478e72f395edd04c1d35",
154
154
  "schemas/v2/localized-content-page-response.schema.json": "476b5f2775b42e35e679814a4ce846a7be1d5311d3a748c10df4275a78b0fb27",
155
155
  "schemas/v2/localized-content-page.schema.json": "717bb51fbc64d0b34eb0a4b35c8b463d08d505267bf251b4cbc67e0a016a567a",
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": "771328fa73691e7f08fca3c628c4e8ba68fa4e43529faaf1a6358aecf26b13f2",
159
+ "schemas/v2/node-pack-manifest.schema.json": "0da826ca63a29026f7a9551748048104910e0d9962db6bad2b50b97a24a9ea30",
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",
@@ -164,16 +164,16 @@
164
164
  "schemas/v2/prompt-pack-manifest.schema.json": "a51667da3d601043d2c33fddaafa572e9dd7e47b8840393742b80eaf6238a1a5",
165
165
  "schemas/v2/prompt-ref.schema.json": "11538fa36a7a652c93100cac4f1857eb4130137b546e3d94890618636d44b729",
166
166
  "schemas/v2/prompt-template.schema.json": "494fc83028cb3af5dbb8a943d0e83b2348f7775e9fa02369162bcb58a6f81559",
167
- "schemas/v2/proposal.schema.json": "72623553611f00e87b4c7972d69ebeba73c2222b01ead75a44a0c2114b679fea",
167
+ "schemas/v2/proposal.schema.json": "cb5ea4fcb54be94fd2a92cf2799357dc73335181a89f5766f432d69e21cc3a80",
168
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
- "schemas/v2/run-event-payloads.schema.json": "8eb0e05e1bd3399b0e2654a1ec5a13198ac4ec8459a1fcbfd800ab2d0a0ae8c6",
173
- "schemas/v2/run-event.schema.json": "4ec8eb048a79ca1343e00aa018acbf86814b5eda6768add6b17e8d7b83638fdf",
174
- "schemas/v2/run-options.schema.json": "6d6052596ab0f17acfb00fdfc89907da7724f952e5ffbe1f81d94ba23d4c77cd",
172
+ "schemas/v2/run-event-payloads.schema.json": "3a7161af78b0dd2730309371f5ea8b889d92a37c708db05e4354209f12ffbe30",
173
+ "schemas/v2/run-event.schema.json": "1a9946374af9323b71635d00fed22abe02c300fa2cf3ae3e9c30f3e553e408ec",
174
+ "schemas/v2/run-options.schema.json": "ff240805478158cbd2f661979c5eb4f5eda11f9c053b7ba587cb0211a9252d74",
175
175
  "schemas/v2/run-orchestrator-decided-event.schema.json": "bb76e15ff72e669f12938684033b55dc567470e1a378e19ffc3fa0e4034ab5a7",
176
- "schemas/v2/run-snapshot.schema.json": "e77bf491d11c1414e62ed84cbf0c1d7149279b133a2101dc8f5eec89d3e45204",
176
+ "schemas/v2/run-snapshot.schema.json": "0ee8a50c6be210db415a7c4345298e70a6ae3dd0dd764ccc514bc1c501437586",
177
177
  "schemas/v2/security-advisory.schema.json": "6a382f737730a738d634266315e9d335e5760020a0cdd9f30727c2f987a01770",
178
178
  "schemas/v2/self-hosted-runner-dispatch-frame.schema.json": "d8129f11d4e731d17ea951e66faaea2086081d13958b62ceaaa359fd6637dd7d",
179
179
  "schemas/v2/self-hosted-runner-registration.schema.json": "93d60003a668cacb0eeac37699ca2884c6b79332d9494cb9e0a1752ac8f8a16c",
@@ -186,8 +186,9 @@
186
186
  "schemas/v2/trigger-subscription-registration.schema.json": "202f7ab9c29e78249e0ad04341ac53377b506d24a0cbfbb7bb54494e80165832",
187
187
  "schemas/v2/trigger-subscription.schema.json": "074bec7f016aa9f4d60359c3a093296cc847045510f01e0a9dceaf4fa5084d12",
188
188
  "schemas/v2/ui-plugin-message.schema.json": "879d65867c01656b3962d64283e66364dcb8f626ad26c5fcf2d2d55a79ae0ce9",
189
- "schemas/v2/workflow-chain-pack-manifest.schema.json": "d1e43617ebacc9b32d1faf98dc7a2074d3e25054f383a1c264b87a19047f674d",
190
- "schemas/v2/workflow-definition.schema.json": "91e422faacb886c8a17fc86b6576d38b7bfd99a37aaff69c710dec598e361192",
189
+ "schemas/v2/webhook-delivery.schema.json": "66025cd7b7899b6897b61c61e99ec2c554e232a6c9742124441adcd81912cef0",
190
+ "schemas/v2/workflow-chain-pack-manifest.schema.json": "22801f14845a3df30660e6f31a41e1a3fbff2425bf56c8a475eab96f1c95882a",
191
+ "schemas/v2/workflow-definition.schema.json": "da7e565cee6ecf3bcc9d46833bad224f579f8db8a378ae22ff3fbf58b1348f38",
191
192
  "schemas/v2/workload-identity.schema.json": "1f589debfac6d70e0908da511550ec69a27b4bcc8314551cd843b6d923605efc",
192
193
  "schemas/v2/workspace-file-create.schema.json": "b6b5260857318c3b67de0231ed15e3a746f525880a763e7b36a7854178dd10d0",
193
194
  "schemas/v2/workspace-file.schema.json": "d2ed39e8c229dde33b56905c8df6f8b2ff056c97e1c5f615e3124baa54a6254f",
@@ -198,21 +199,21 @@
198
199
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
199
200
  "spec/v1/alias-detectors.json": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
200
201
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
201
- "spec/v1/core-standard-manifest.json": "322b72364fa918dd3d5dd939d947f7a66a410f7c39a9ffd6dd331c1916e38ceb",
202
- "spec/v1/deprecations.json": "520d79927a8ed43ccf62c207a7cf6cf7b19b16e00bbbbdf0410667e939c19d85",
203
- "spec/v1/deprecations.schema.json": "4cdbbb8eb9c0092913a5704c943880f0c8581bcf0f640a7df5a84105d53c8aee",
202
+ "spec/v1/core-standard-manifest.json": "08eb68a1f07020390cf3a76ce6ac9bec697cd35ba02a0d8618939e39cac87a0a",
203
+ "spec/v1/deprecations.json": "1d5acb69a9b8ccb57275a95605f74aef1d920685f8407c9d382a46b59dc803bb",
204
+ "spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
204
205
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
205
206
  "spec/v1/event-codemap.schema.json": "d52caf29071e3784d4c20259cdbd5a7b533986548a440bb632a80b82cadd7ea5",
206
- "spec/v1/extensions.json": "4d7ba68bae7c7f50cc655b94646ad25945a5dd63b703d43c1d015470141cd668",
207
+ "spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
207
208
  "spec/v1/gaps.json": "96ca051cb20f1a669b355d67a89421c56fef28aa5e6710c9519f592f26af33a3",
208
209
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
209
210
  "spec/v1/migrations.json": "8839497d6a4830b53fa7867014a8336f0d725ef2346352e54fb580b7947897af",
210
211
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
211
212
  "spec/v1/operation-path-manifest.json": "5f5f4e3842669371730ebbd1aace3fb794192615f0018dc144f484ba5db82ac3",
212
213
  "spec/v1/spec-gaps.json": "6cc9962c6b969f632e07a78b52a4f61447ff579e2990cbae989866f584a86042",
213
- "spec/v2/declaration.json": "52b8c24a2dd463d44086129bf792ad108746f5102778cbbf0ff469fa534eca48",
214
+ "spec/v2/declaration.json": "a624cfa6a46d0b10e0dac40fa49abbe05190aff87a8e852ce125d19379067b00",
214
215
  "spec/v2/declaration.schema.json": "b0881a76228dfbd1b85a2ec7b7259a60197f768344535cdb475db6ff719b0e54",
215
- "spec/v2/errors.json": "ad1725dc7c11002450e8abc682858eb5c11952a0718ea8ef6bdb2fb5ae6e7e21",
216
+ "spec/v2/errors.json": "f179414a92f30b5dadf26e9e57699649a67324bd7186d5287647d17605d137d3",
216
217
  "spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
217
218
  "spec/v2/event-codemap.schema.json": "ce2d0ca085d7668d7ef745d2560b385ec6881d24a6d4b47c49931986b2ba642d",
218
219
  "spec/v2/ext/provider-idempotency/registry.json": "5b5fecad604fb4ab38da39f5abe697a4e57b05406c398f8afeb377331b2bd965",
@@ -225,10 +226,12 @@
225
226
  "spec/v2/facets/replay.schema.json": "0a5aefb0108c708c6794fadd533cf256709704c4f16dbf44cd63fa8980c78018",
226
227
  "spec/v2/facets/sandbox.schema.json": "e75f54486d387b2d089791aa93b5093cd1450f1920753a0f97fc23acdfe978ed",
227
228
  "spec/v2/facets/webhooks.schema.json": "0b766033ef4b4c6c2ac6f9979a19843ba024d3a1d1576972dcb55aaba52f1b9a",
229
+ "spec/v2/id-field-bindings.json": "d9d1f8e266660b581f11020b9511a65c677e026b1a71ada870682ad77e18555d",
228
230
  "spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
229
231
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
230
- "spec/v2/profiles.json": "1f0ee40491131cf561ef19a0394c47ba476b4567514fe16befb6cada4a10a5a4",
231
- "spec/v2/release.json": "d8dc6315c0716621625edad0b150c5daa77e1352984f439ee4d1d8627efeec13"
232
+ "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
233
+ "spec/v2/release.json": "517c6d3ccefcc5e76cc8206a580222af2ae51dcca99869db83191e046c461a49",
234
+ "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
232
235
  },
233
- "corpusCommit": "e3480a200a224fb906d0c3f54043b6e229c5dff9"
236
+ "corpusCommit": "2e5c91a9376498ce8825f97fcb76f2edac0efb4f"
234
237
  }
package/src/cli.ts CHANGED
@@ -54,7 +54,7 @@ import {
54
54
  type DiscoveryPayload,
55
55
  PROFILE_FLOOR_SCENARIOS,
56
56
  } from './lib/profiles.js';
57
- import { setV2ProfileFloors } from './lib/requirement-registry.js';
57
+ import { setV2ProfileFloors, v2ProfileFloorFiles } from './lib/requirement-registry.js';
58
58
 
59
59
  interface ParsedArgs {
60
60
  readonly baseUrl: string | undefined;
@@ -377,35 +377,10 @@ function claimedProfilesForV2(doc: DiscoveryPayload, conformanceRoot: string): s
377
377
  * fail a host for the corpus's own backlog.
378
378
  */
379
379
  function v2ProfileFloors(conformanceRoot: string): Record<string, readonly string[]> {
380
- const registryPath = v2RegistryPath(conformanceRoot);
381
- if (registryPath === null) return {};
382
- let known: Set<string>;
383
- try {
384
- known = new Set(Object.keys((JSON.parse(readFileSync(resolvePath(conformanceRoot, 'scenario-majors.json'), 'utf8')) as { majors: Record<string, number[]> }).majors));
385
- } catch {
386
- known = new Set();
387
- }
388
- let registry: { profiles?: Array<{ id?: unknown; floorScenarios?: unknown }> };
389
- try {
390
- registry = JSON.parse(readFileSync(registryPath, 'utf8'));
391
- } catch {
392
- return {};
393
- }
394
- const out: Record<string, readonly string[]> = {};
395
- for (const p of registry.profiles ?? []) {
396
- if (typeof p.id !== 'string') continue;
397
- const raw = Array.isArray(p.floorScenarios) ? (p.floorScenarios as unknown[]).map(String) : [];
398
- const files: string[] = [];
399
- for (const entry of raw) {
400
- const name = entry.startsWith('planned:') ? `v2-${entry.slice('planned:'.length)}.test.ts` : entry.endsWith('.test.ts') ? entry : `${entry}.test.ts`;
401
- if (known.size === 0 || known.has(name)) files.push(name);
402
- }
403
- out[p.id] = files;
404
- }
405
- return out;
380
+ // One derivation, shared with the ledger's floor-file set (requirement-registry.ts).
381
+ return v2ProfileFloorFiles(conformanceRoot);
406
382
  }
407
383
 
408
- /** A single scenario test file's terminal state, derived from the vitest JSON report. */
409
384
  type ScenarioState = 'passed' | 'failed' | 'skipped';
410
385
 
411
386
  /** The subset of vitest's JSON reporter output we read. */
@@ -476,6 +451,16 @@ async function resolveTargetMajor(args: ParsedArgs, baseUrl: string | undefined,
476
451
  // §1.1), so auto-detection on a dual-stack host resolves to major 1 by design.
477
452
  // Say so, or an operator reads a v1 run as the host's only option.
478
453
  if (major === 1 && dualStack) source += ' (the host also serves 2.x — pass --target-major 2 to measure it)';
454
+ // The RUNNER's own process must see the target major, not only the vitest
455
+ // child (rc.55). `requirementIdForFile()` decides "is this file a floor?"
456
+ // through `targetMajor()` = process.env; until rc.55 `--target-major 2` set
457
+ // the env on the child alone, so the worker recorded every v2 floor file
458
+ // under `openwop.floor.<stem>` while the runner looked it up as
459
+ // `openwop.scenario.<stem>`, found nothing, emitted a report-derived pass
460
+ // with no assertion count, and its own verifier rejected the bundle as a
461
+ // `vacuous-pass` emitter defect (exit 2, nothing written). The child env is
462
+ // spread from process.env after this, so one assignment covers both.
463
+ process.env['OPENWOP_TARGET_MAJOR'] = String(major);
479
464
  const manifest = JSON.parse(readFileSync(resolvePath(conformanceRoot, 'scenario-majors.json'), 'utf8')) as { majors: Record<string, number[]> };
480
465
  const files = Object.entries(manifest.majors).filter(([, m]) => m.includes(major as number)).map(([f]) => `src/scenarios/${f}`);
481
466
  return { major, files, source };
@@ -666,14 +651,22 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
666
651
  const doc3 = document as Record<string, unknown>;
667
652
  const protocolVersions = Array.isArray(doc3['protocolVersions']) ? (doc3['protocolVersions'] as string[]) : [String(doc3['protocolVersion'] ?? '')];
668
653
  const preferredVersion = typeof doc3['preferredVersion'] === 'string' ? (doc3['preferredVersion'] as string) : (protocolVersions[0] ?? '');
669
- // witnessCount: executed-pass rows on the profile's floor (PROFILE_FLOOR_SCENARIOS required + requiredAnyPrefix).
654
+ // witnessCount: witnessed executed-pass rows on the profile's floor. At major
655
+ // 2 the verdict already counted them against the declaration's floor
656
+ // (`witnessedPasses`); the v1 hand table below knows no v2 file and printed
657
+ // 0 for every v2 profile until rc.45 — the third unjoined floor site.
658
+ const verdictFor = (profile: string) => derived.verdicts.find((v) => v.profile === profile);
670
659
  const witnessCountFor = (profile: string): number => {
660
+ if (target.major === 2) return verdictFor(profile)?.witnessedPasses ?? 0;
671
661
  const floor = PROFILE_FLOOR_SCENARIOS[profile];
672
662
  if (!floor) return 0;
673
663
  const onFloor = (scenario: string): boolean => floor.required.includes(scenario) || (floor.requiredAnyPrefix ?? []).some((pre) => scenario.startsWith(pre));
674
664
  return derived.requirements.filter((r) => r.disposition === 'executed-pass' && onFloor(r.scenarioId)).length;
675
665
  };
676
- 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 }));
666
+ // `certified` IS the verdict (RFC 0148 §A; RFC 0168 §E.1 adds the bundle-wide
667
+ // blocked rule). Until rc.45 it was `!notHeld && !rejected && blocked === 0`
668
+ // and never read `certifiable` — an empty v2 floor certified on no evidence.
669
+ const claimed3 = claimedProfiles.filter((p) => !(p in DEPRECATED_PROFILE_ALIASES)).map((p) => ({ id: p, evidenceTier: args.evidenceTier, witnessCount: witnessCountFor(p), certified: (verdictFor(p)?.certifiable ?? false) && !notHeld.has(p) && !rejectedProfiles.some((v) => v.profile === p) && totals3.blocked === 0 }));
677
670
  let relaxations: BundleV3['host']['relaxations'];
678
671
  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); } }
679
672
  const lockPath = resolvePath(conformanceRoot, 'dist', 'spec-artifacts.lock.json');
@@ -75,6 +75,17 @@ export function setup(): void {
75
75
  // Suite 2.0.0: in the published layout the contract is the spec-artifacts peer (RFC 0168 §D.2).
76
76
  const stamp = LAYOUT === 'published' ? verifyPeerContract(PKG_ROOT_PATH) : verifyCorpusStamp(PKG_ROOT_PATH, LAYOUT);
77
77
  process.stderr.write(`${describeVerdict(stamp)}\n`);
78
+ // A version skew and a digest mismatch both have to refuse, but they are
79
+ // different faults with different fixes, and reporting them in the same words
80
+ // sends the reader to debug a corrupt install when nothing is corrupt.
81
+ if (stamp.kind === 'peer-version') {
82
+ throw new Error(
83
+ `openwop-conformance: refusing to run — this suite was packed against @openwop/spec-artifacts@${stamp.lockVersion}, ` +
84
+ `but @openwop/spec-artifacts@${stamp.peerVersion} is installed. They are declared EXACT peers. ` +
85
+ `Install both at the same explicit version; do NOT install at a dist-tag such as \`next\`, which moves per package ` +
86
+ `and can name a pair that was never published together.`,
87
+ );
88
+ }
78
89
  if (stamp.kind === 'mismatch') {
79
90
  throw new Error('openwop-conformance: refusing to run — schemas/CORPUS-STAMP.json digests do not match the vendored api/ + schemas/ files. Reinstall the package; do not hand-patch vendored contract files.');
80
91
  }
@@ -39,7 +39,17 @@ export interface CorpusStamp {
39
39
  export type StampVerdict =
40
40
  | { readonly kind: 'verified'; readonly files: number }
41
41
  | { readonly kind: 'not-applicable'; readonly reason: string }
42
- | { readonly kind: 'mismatch'; readonly missing: readonly string[]; readonly altered: readonly string[]; readonly extra: readonly string[] };
42
+ | { readonly kind: 'mismatch'; readonly missing: readonly string[]; readonly altered: readonly string[]; readonly extra: readonly string[] }
43
+ /**
44
+ * The peer is INSTALLED and INTACT but is a different version than the suite
45
+ * was packed against. Its own kind because the remedy is completely different
46
+ * from a digest mismatch — nothing is corrupt, two versions are simply out of
47
+ * step — and because the generic message sends readers to debug a broken
48
+ * install. Reported by a tier-2 host that hit it through the `next` dist-tag:
49
+ * the tag moves per package, so `@next` can name an exact-peer PAIR that was
50
+ * never published together.
51
+ */
52
+ | { readonly kind: 'peer-version'; readonly peerVersion: string; readonly lockVersion: string };
43
53
 
44
54
  export const STAMP_RELATIVE_PATH = join('schemas', 'CORPUS-STAMP.json');
45
55
 
@@ -94,7 +104,11 @@ export function verifyPeerContract(pkgRoot: string): StampVerdict {
94
104
  if (!existsSync(stampPath)) return { kind: 'mismatch', missing: ['@openwop/spec-artifacts/CORPUS-STAMP.json'], altered: [], extra: [] };
95
105
  const stamp = JSON.parse(readFileSync(stampPath, 'utf8')) as { package: string; version: string; files: Record<string, string> };
96
106
  const digest = createHash('sha256').update(JSON.stringify({ package: stamp.package, version: stamp.version, files: stamp.files })).digest('hex');
97
- if (stamp.version !== lock.version || digest !== lock.stampSha256) return { kind: 'mismatch', missing: [], altered: [`@openwop/spec-artifacts ${stamp.version} (digest ${digest.slice(0, 12)}) ≠ the suite's lock ${lock.version} (${lock.stampSha256.slice(0, 12)})`], extra: [] };
107
+ // A plain version difference is NOT corruption; report it as itself so the
108
+ // message names the two versions and the fix, instead of sending the reader
109
+ // to hunt a damaged install.
110
+ if (stamp.version !== lock.version) return { kind: 'peer-version', peerVersion: stamp.version, lockVersion: lock.version };
111
+ if (digest !== lock.stampSha256) return { kind: 'mismatch', missing: [], altered: [`@openwop/spec-artifacts ${stamp.version} stamp digest ${digest.slice(0, 12)} ≠ the suite's lock ${lock.stampSha256.slice(0, 12)} — same version, different contents`], extra: [] };
98
112
  const missing: string[] = []; const altered: string[] = [];
99
113
  for (const [rel, d] of Object.entries(stamp.files)) { const p = join(peerRoot, ...rel.split('/')); if (!existsSync(p)) missing.push(rel); else if (sha256File(p) !== d) altered.push(rel); }
100
114
  if (missing.length || altered.length) return { kind: 'mismatch', missing, altered, extra: [] };
@@ -143,6 +157,14 @@ export function describeVerdict(v: StampVerdict): string {
143
157
  return `[openwop-conformance] corpus stamp VERIFIED — ${v.files} vendored api/ + schemas/ files match their SHA-256 digests`;
144
158
  case 'not-applicable':
145
159
  return `[openwop-conformance] corpus stamp not checked — ${v.reason}`;
160
+ case 'peer-version':
161
+ return (
162
+ `[openwop-conformance] peer version MISMATCH — this suite was packed against ` +
163
+ `@openwop/spec-artifacts@${v.lockVersion} but @openwop/spec-artifacts@${v.peerVersion} is installed. ` +
164
+ `Nothing is corrupt: the two are declared EXACT peers and are simply out of step. ` +
165
+ `Install both at the same explicit version — never at a dist-tag such as \`next\`, which moves per package ` +
166
+ `and can therefore name a pair that was never published together.`
167
+ );
146
168
  case 'mismatch':
147
169
  return (
148
170
  `[openwop-conformance] corpus stamp MISMATCH — the vendored contract is not the one this suite shipped ` +
@@ -69,7 +69,18 @@ async function http(fn: () => Promise<OpenWOPResponse>): Promise<OpenWOPResponse
69
69
  export function era2Gate(doc: Record<string, unknown> | null): Seeded | null {
70
70
  if (!doc) return { ok: false, kind: 'blocked', reason: 'discovery unreachable' };
71
71
  if (!seamsProfileAdvertised(doc)) {
72
- return { ok: false, kind: 'blocked', reason: `seams profile not advertised (conformance.seamsProfile !== openwop-conformance-seams-v2) an era-2 log can only be seeded through ${SEED_PATH}` };
72
+ // rc.63: `inapplicable`, not `blocked` — the host never claimed the
73
+ // instrument, so the obligation is out of scope for it (`lib/seams.ts`).
74
+ // rc.62 fixed the fifteen scenarios that call `seamsProfileAdvertised`
75
+ // directly and MISSED this one, which mints the same disposition one call
76
+ // deeper for six scenarios that import only `era2Gate`: era-2-append-
77
+ // vocabulary, fork-a-v1-run, pinned-run-disposition, stream-sse-projection,
78
+ // unmapped-type-refused, v1-events-translated. On MyndHyve's bundle that
79
+ // left 16 rows blocked on advert-absence after rc.62 — the same defect
80
+ // wearing a different import. `blocked` above (discovery unreachable) and
81
+ // below (the seam does not answer) is untouched: those are real failed
82
+ // measurements.
83
+ return { ok: false, kind: 'inapplicable', reason: `seams profile not advertised (conformance.seamsProfile !== openwop-conformance-seams-v2) — an era-2 log can only be seeded through ${SEED_PATH}` };
73
84
  }
74
85
  return null;
75
86
  }
@@ -41,6 +41,7 @@
41
41
  * @see RFCS/0003-fixture-gating.md
42
42
  */
43
43
 
44
+ import { softSkip } from './soft-skip.js';
44
45
  import type { DiscoveryPayload } from './profiles.js';
45
46
 
46
47
  let _advertisedFixtures: ReadonlySet<string> | null = null;
@@ -76,7 +77,29 @@ function loadOptedOutPredicate(): (id: string) => boolean {
76
77
  * filtered out of the cache before storage so downstream lookups can
77
78
  * stay a single sync set-membership test.
78
79
  */
80
+ let _discoveryUnreadable: string | null = null;
81
+
82
+ /**
83
+ * Suite init could not READ the discovery document. This is not "the host
84
+ * advertises no fixtures" and MUST NOT be recorded as such: with 311
85
+ * fixture-gated sites, an empty cache turns an entire lane into `inapplicable`
86
+ * rows that read exactly like a host that advertises nothing. Measured
87
+ * 2026-09-05: all four workers hit the 5 s init abort against a host that
88
+ * answers `/.well-known/openwop` in 120–200 ms (a Cloud Run cold start), and
89
+ * every fixture-gated scenario skipped as "not advertised". The cache stays
90
+ * null; every gate that consults it records `blocked` with this reason.
91
+ */
92
+ export function setDiscoveryUnreadable(reason: string): void {
93
+ _advertisedFixtures = null;
94
+ _discoveryUnreadable = reason;
95
+ }
96
+
97
+ export function discoveryUnreadableReason(): string | null {
98
+ return _discoveryUnreadable;
99
+ }
100
+
79
101
  export function setAdvertisedFixtures(c: DiscoveryPayload | null | undefined): void {
102
+ _discoveryUnreadable = null;
80
103
  if (c == null || !Array.isArray(c.fixtures)) {
81
104
  _advertisedFixtures = new Set();
82
105
  return;
@@ -103,6 +126,13 @@ export function setAdvertisedFixtures(c: DiscoveryPayload | null | undefined): v
103
126
  * );
104
127
  */
105
128
  export function isFixtureAdvertised(id: string): boolean {
129
+ if (_advertisedFixtures === null && _discoveryUnreadable !== null) {
130
+ // Evidence went unread, on the suite's side. `blocked` outranks the
131
+ // `inapplicable` the caller is about to record (soft-skip.ts RANK), so the
132
+ // file resolves to the honest word instead of the vacuous one.
133
+ softSkip('blocked', `suite init could not read /.well-known/openwop (${_discoveryUnreadable}) — fixture ${id} is UNKNOWN, not unadvertised`);
134
+ return false;
135
+ }
106
136
  return _advertisedFixtures?.has(id) ?? false;
107
137
  }
108
138
 
@@ -127,4 +157,5 @@ export function isFixtureCacheReady(): boolean {
127
157
  /** Test-only: reset the module-level cache. */
128
158
  export function __resetForTests(): void {
129
159
  _advertisedFixtures = null;
160
+ _discoveryUnreadable = null;
130
161
  }
@@ -22,6 +22,9 @@
22
22
  */
23
23
 
24
24
  import { PROFILE_FLOOR_SCENARIOS } from './profiles.js';
25
+ import { createRequire } from 'node:module';
26
+ import { dirname, resolve as resolvePath } from 'node:path';
27
+ import { existsSync, readFileSync } from 'node:fs';
25
28
 
26
29
  /** `runs-lifecycle.test.ts` → `openwop.floor.runs-lifecycle`. */
27
30
  export function requirementIdForScenario(scenarioFile: string): string {
@@ -94,6 +97,17 @@ export function setV2ProfileFloors(floors: Readonly<Record<string, readonly stri
94
97
  v2Floors = floors;
95
98
  }
96
99
 
100
+ /**
101
+ * True while a major-2 floor map is installed. The v1 hand table
102
+ * (`PROFILE_FLOOR_SCENARIOS`) MUST NOT be consulted for any per-profile fact
103
+ * (`discoveryOnly`, `runtimeDerived`, the floor itself, the witness count) while
104
+ * this is true — every one of those reads is a floor site, and rc.41 found two
105
+ * of four unjoined; rc.45 found the other two.
106
+ */
107
+ export function v2FloorsActive(): boolean {
108
+ return v2Floors !== null;
109
+ }
110
+
97
111
  export function requirementsFor(profile: string, document?: Readonly<Record<string, unknown>>): readonly string[] | null {
98
112
  if (v2Floors !== null) {
99
113
  const files = v2Floors[profile];
@@ -124,3 +138,57 @@ export function allRequirements(): readonly string[] {
124
138
  }
125
139
  return [...ids].sort();
126
140
  }
141
+
142
+ /**
143
+ * The MAJOR-2 floors, derived from `spec/v2/profiles.json` (itself generated
144
+ * from `spec/v2/declaration.json`), keyed by profile id and resolved to
145
+ * scenario file names. A `planned:<stem>` entry names `v2-<stem>.test.ts`; an
146
+ * entry is kept only if `scenario-majors.json` knows the file.
147
+ *
148
+ * This function exists in ONE place on purpose. Until 2026-09-05 the CLI
149
+ * derived these floors privately for `--certify` while the ledger decided
150
+ * "is this file a floor?" from PROFILE_FLOOR_SCENARIOS — the v1 hand table,
151
+ * which knows no v2 file. So a v2 floor file was MINTED as
152
+ * `openwop.scenario.v2-…` and LOOKED UP at certify time as
153
+ * `openwop.floor.v2-…`: 101 executed-pass rows, `witnessCount: 0` on both
154
+ * claimed profiles, and `REJECTING — openwop-discovery-core: unclassified` on
155
+ * a tier-1 host's first production bundle. Two sources of the same fact, one
156
+ * of them stale, and the join between them silent.
157
+ */
158
+ export function v2ProfileFloorFiles(conformanceRoot: string): Record<string, readonly string[]> {
159
+ const candidates: string[] = [];
160
+ try {
161
+ const req = createRequire(resolvePath(conformanceRoot, 'package.json'));
162
+ candidates.push(resolvePath(dirname(req.resolve('@openwop/spec-artifacts/package.json')), 'spec', 'v2', 'profiles.json'));
163
+ } catch { /* not installed as a package; the repo-layout candidates below */ }
164
+ candidates.push(
165
+ resolvePath(conformanceRoot, 'spec', 'v2', 'profiles.json'),
166
+ resolvePath(conformanceRoot, '..', 'spec', 'v2', 'profiles.json'),
167
+ );
168
+ const registryPath = candidates.find((c) => existsSync(c));
169
+ if (registryPath === undefined) return {};
170
+ let known: Set<string>;
171
+ try {
172
+ known = new Set(Object.keys((JSON.parse(readFileSync(resolvePath(conformanceRoot, 'scenario-majors.json'), 'utf8')) as { majors: Record<string, number[]> }).majors));
173
+ } catch {
174
+ known = new Set();
175
+ }
176
+ let registry: { profiles?: Array<{ id?: unknown; floorScenarios?: unknown }> };
177
+ try {
178
+ registry = JSON.parse(readFileSync(registryPath, 'utf8'));
179
+ } catch {
180
+ return {};
181
+ }
182
+ const out: Record<string, readonly string[]> = {};
183
+ for (const p of registry.profiles ?? []) {
184
+ if (typeof p.id !== 'string') continue;
185
+ const raw = Array.isArray(p.floorScenarios) ? (p.floorScenarios as unknown[]).map(String) : [];
186
+ const files: string[] = [];
187
+ for (const entry of raw) {
188
+ const name = entry.startsWith('planned:') ? `v2-${entry.slice('planned:'.length)}.test.ts` : entry.endsWith('.test.ts') ? entry : `${entry}.test.ts`;
189
+ if (known.size === 0 || known.has(name)) files.push(name);
190
+ }
191
+ out[p.id] = files;
192
+ }
193
+ return out;
194
+ }