@openwop/openwop-conformance 2.45.2 → 2.45.3
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/CHANGELOG.md +52 -0
- package/README.md +2 -2
- package/dist/cli.js +1 -1
- package/dist/lib/certification-bundle-verify.js +22 -1
- package/dist/lib/profiles.js +17 -2
- package/dist/lib/requirement-registry.js +15 -0
- package/dist/lib/scenario-disposition.js +50 -9
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/conformance-fs-probe.json +25 -0
- package/fixtures/conformance-queue-consume.json +27 -0
- package/fixtures/conformance-queue-publish.json +29 -0
- package/fixtures/conformance-safefetch-probe.json +23 -0
- package/fixtures/conformance-secret-resolve-then-fail.json +33 -0
- package/fixtures/conformance-storage-probe.json +27 -0
- package/fixtures/conformance-tool-scope-probe.json +27 -0
- package/fixtures/openwop-secrets-run-witness.json +29 -0
- package/fixtures.md +102 -1
- package/package.json +2 -2
- package/requirement-aliases.json +72 -71
- package/requirements.json +1011 -43
- package/scenario-majors.json +39 -3
- package/schemas/CORPUS-STAMP.json +22 -22
- package/src/cli.ts +1 -1
- package/src/lib/certification-bundle-verify.ts +23 -2
- package/src/lib/driver.ts +61 -2
- package/src/lib/memoryAttribution.ts +41 -6
- package/src/lib/polling.ts +2 -18
- package/src/lib/profiles.ts +26 -2
- package/src/lib/requirement-registry.ts +12 -0
- package/src/lib/run-secrets-witness.ts +240 -0
- package/src/lib/scenario-disposition.ts +45 -7
- package/src/lib/secret-scan.ts +141 -0
- package/src/lib/timeout-scale.ts +25 -0
- package/src/lib/triggerBridge.ts +69 -1
- package/src/scenarios/byok-roundtrip.test.ts +20 -4
- package/src/scenarios/runner-ledger.test.ts +6 -1
- package/src/scenarios/secrets-run-witness.test.ts +154 -0
- package/src/scenarios/trigger-bridge-delivery.test.ts +183 -126
- package/src/scenarios/trigger-refused-event-keeps-subscription.test.ts +141 -0
- package/src/scenarios/v2-eval-mode-unadvertised-refused.test.ts +55 -0
- package/src/scenarios/v2-fs-sandbox-escape-refused.test.ts +161 -0
- package/src/scenarios/v2-memory-cross-tenant-isolation.test.ts +113 -0
- package/src/scenarios/v2-queue-cross-tenant-isolation.test.ts +118 -0
- package/src/scenarios/v2-safefetch-ssrf-refused.test.ts +198 -0
- package/src/scenarios/v2-secret-canary-absent.test.ts +211 -0
- package/src/scenarios/v2-secrets-run-witness.test.ts +167 -0
- package/src/scenarios/v2-storage-cross-tenant-isolation.test.ts +213 -0
- package/src/scenarios/v2-tool-authorization-fail-closed.test.ts +197 -0
- package/src/scenarios/v2-workspace-scope-from-identity.test.ts +146 -0
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":
|
|
5
|
-
"v1":
|
|
6
|
-
"v2":
|
|
4
|
+
"files": 587,
|
|
5
|
+
"v1": 454,
|
|
6
|
+
"v2": 148
|
|
7
7
|
},
|
|
8
8
|
"majors": {
|
|
9
9
|
"a2a-1-0-agent-card.test.ts": [
|
|
@@ -1149,6 +1149,9 @@
|
|
|
1149
1149
|
"secret-leakage-otel-attribute.test.ts": [
|
|
1150
1150
|
1
|
|
1151
1151
|
],
|
|
1152
|
+
"secrets-run-witness.test.ts": [
|
|
1153
|
+
1
|
|
1154
|
+
],
|
|
1152
1155
|
"self-hosted-runner.test.ts": [
|
|
1153
1156
|
1
|
|
1154
1157
|
],
|
|
@@ -1263,6 +1266,9 @@
|
|
|
1263
1266
|
"trigger-ingestion.test.ts": [
|
|
1264
1267
|
1
|
|
1265
1268
|
],
|
|
1269
|
+
"trigger-refused-event-keeps-subscription.test.ts": [
|
|
1270
|
+
1
|
|
1271
|
+
],
|
|
1266
1272
|
"trigger-stream-cdc-sources.test.ts": [
|
|
1267
1273
|
1
|
|
1268
1274
|
],
|
|
@@ -1386,6 +1392,9 @@
|
|
|
1386
1392
|
"v2-error-registry.test.ts": [
|
|
1387
1393
|
2
|
|
1388
1394
|
],
|
|
1395
|
+
"v2-eval-mode-unadvertised-refused.test.ts": [
|
|
1396
|
+
2
|
|
1397
|
+
],
|
|
1389
1398
|
"v2-event-type-closed.test.ts": [
|
|
1390
1399
|
2
|
|
1391
1400
|
],
|
|
@@ -1401,6 +1410,9 @@
|
|
|
1401
1410
|
"v2-form-when-reuses-edge-conditions.test.ts": [
|
|
1402
1411
|
2
|
|
1403
1412
|
],
|
|
1413
|
+
"v2-fs-sandbox-escape-refused.test.ts": [
|
|
1414
|
+
2
|
|
1415
|
+
],
|
|
1404
1416
|
"v2-header-scheme.test.ts": [
|
|
1405
1417
|
2
|
|
1406
1418
|
],
|
|
@@ -1452,6 +1464,9 @@
|
|
|
1452
1464
|
"v2-mcp-tasks.test.ts": [
|
|
1453
1465
|
2
|
|
1454
1466
|
],
|
|
1467
|
+
"v2-memory-cross-tenant-isolation.test.ts": [
|
|
1468
|
+
2
|
|
1469
|
+
],
|
|
1455
1470
|
"v2-min-client-version.test.ts": [
|
|
1456
1471
|
2
|
|
1457
1472
|
],
|
|
@@ -1515,6 +1530,9 @@
|
|
|
1515
1530
|
"v2-provider-conflict.test.ts": [
|
|
1516
1531
|
2
|
|
1517
1532
|
],
|
|
1533
|
+
"v2-queue-cross-tenant-isolation.test.ts": [
|
|
1534
|
+
2
|
|
1535
|
+
],
|
|
1518
1536
|
"v2-refresh-sla.test.ts": [
|
|
1519
1537
|
2
|
|
1520
1538
|
],
|
|
@@ -1566,12 +1584,24 @@
|
|
|
1566
1584
|
"v2-run-snapshot-etag.test.ts": [
|
|
1567
1585
|
2
|
|
1568
1586
|
],
|
|
1587
|
+
"v2-safefetch-ssrf-refused.test.ts": [
|
|
1588
|
+
2
|
|
1589
|
+
],
|
|
1590
|
+
"v2-secret-canary-absent.test.ts": [
|
|
1591
|
+
2
|
|
1592
|
+
],
|
|
1593
|
+
"v2-secrets-run-witness.test.ts": [
|
|
1594
|
+
2
|
|
1595
|
+
],
|
|
1569
1596
|
"v2-sse-last-event-id-cursor.test.ts": [
|
|
1570
1597
|
2
|
|
1571
1598
|
],
|
|
1572
1599
|
"v2-sse-last-event-id.test.ts": [
|
|
1573
1600
|
2
|
|
1574
1601
|
],
|
|
1602
|
+
"v2-storage-cross-tenant-isolation.test.ts": [
|
|
1603
|
+
2
|
|
1604
|
+
],
|
|
1575
1605
|
"v2-stream-mode-refusal.test.ts": [
|
|
1576
1606
|
2
|
|
1577
1607
|
],
|
|
@@ -1584,6 +1614,9 @@
|
|
|
1584
1614
|
"v2-terminal-event-once.test.ts": [
|
|
1585
1615
|
2
|
|
1586
1616
|
],
|
|
1617
|
+
"v2-tool-authorization-fail-closed.test.ts": [
|
|
1618
|
+
2
|
|
1619
|
+
],
|
|
1587
1620
|
"v2-tool-catalog-annotations.test.ts": [
|
|
1588
1621
|
2
|
|
1589
1622
|
],
|
|
@@ -1635,6 +1668,9 @@
|
|
|
1635
1668
|
"v2-well-known-one-resource.test.ts": [
|
|
1636
1669
|
2
|
|
1637
1670
|
],
|
|
1671
|
+
"v2-workspace-scope-from-identity.test.ts": [
|
|
1672
|
+
2
|
|
1673
|
+
],
|
|
1638
1674
|
"vector-knn-roundtrip.test.ts": [
|
|
1639
1675
|
1
|
|
1640
1676
|
],
|
|
@@ -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.45.
|
|
5
|
-
"corpusTag": "v2.45.
|
|
4
|
+
"version": "2.45.3",
|
|
5
|
+
"corpusTag": "v2.45.3",
|
|
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
|
-
"api/openapi.yaml": "
|
|
10
|
+
"api/openapi.yaml": "ad37a5a5c732ad46100fc03c3f4243fdd4f2976e330f02a8791b6b5b10a8a0c1",
|
|
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": "1f22ab7223019080332312b0fc9f7eb4c97bca2eebd57cab886acd1d6e3081d0",
|
|
13
|
+
"api/v2/asyncapi.yaml": "5b74151b1867c3c234203aa1f2dfb1ceeea216e013395e8696da02d4a8630412",
|
|
14
|
+
"api/v2/openapi.yaml": "05a07d9cd1e7f80da1e7044aace1073bf4ab584186cdd67979ba2664f90a80d0",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"schemas/artifact-type-pack-manifest.schema.json": "60342fcb15eb7f3ab46271f0641409a30d3a4a8f318b1020c53070886b191252",
|
|
32
32
|
"schemas/audit-verify-result.schema.json": "0d3a3088ac1c32f9df66d346c33d7adbd8037940dba040e14ea847565a1a14bd",
|
|
33
33
|
"schemas/budget-policy.schema.json": "3d4fe30ae129fe136ab9ece395f2a2a42a76360786b1b9a1d5f1cbc753eb5b39",
|
|
34
|
-
"schemas/capabilities.schema.json": "
|
|
34
|
+
"schemas/capabilities.schema.json": "d1d491248930e9b50ac6aca93a33cb1155eb997c4fbb2af0a940e8e889c76807",
|
|
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",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"schemas/suspend-request.schema.json": "7a90b622d5c619e3ffb5da696d272d6505e2780ab92ab631ac1b900f5daea908",
|
|
96
96
|
"schemas/tool-descriptor.schema.json": "7f6adbae63003c1f1311f6a436f7f286542be62aa19a27b46730f2c2810857f7",
|
|
97
97
|
"schemas/trigger-event.schema.json": "59d63b79dca3593f45d281776cbfe43ae4d85ee25519b82ad59f162a9efb592c",
|
|
98
|
-
"schemas/trigger-subscription-registration.schema.json": "
|
|
98
|
+
"schemas/trigger-subscription-registration.schema.json": "2ca680ca6ffdb3cfc5de7bb8b8db8c3ec8eab4b88b7c9f7fe5edaecc7cf61ad0",
|
|
99
99
|
"schemas/trigger-subscription.schema.json": "4f7b067f20032073b140c4bb15dc9a9914c4f6421a252ff1ff261cbdd8edf270",
|
|
100
100
|
"schemas/ui-plugin-message.schema.json": "346638babf1f8ea7b161cde75c0038b518cd2cc5bf0631772e20a9b74c96b5dd",
|
|
101
101
|
"schemas/v2/a2a-task-state.schema.json": "1810363e2d9bddb5997923bdac4044cc17c6f46ec5a40515d2ff21d9c82c1ad9",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"schemas/v2/artifact.schema.json": "85b9dc284d39b99d2a66fa8cbf47f2dd3d8526d3bd6cc411ad315b372de38975",
|
|
117
117
|
"schemas/v2/audit-verify-result.schema.json": "14af8e9283510f0a40e48a2059c929ee44e0fc9fa3cc66a95bfbe90b17c48297",
|
|
118
118
|
"schemas/v2/budget-policy.schema.json": "1de68095db0beb5c7a4be629de26e79f62ebbe748a88d46f36e768edfd1f0d68",
|
|
119
|
-
"schemas/v2/capabilities.schema.json": "
|
|
119
|
+
"schemas/v2/capabilities.schema.json": "3792aa9d292229cf9b82c7306600a5dfdb63b3274523e0ed5dd4f952a04cb833",
|
|
120
120
|
"schemas/v2/certification-bundle.schema.json": "82ce31a1ed6c7e21b0ba88ff62a7b8b768fc3b176b1d06fce023203830b94725",
|
|
121
121
|
"schemas/v2/channel-presence-payload.schema.json": "4eeb224910e6f7eee3ab5e7c3899559c25c83a26d0022e0ad195c4672611977a",
|
|
122
122
|
"schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"schemas/v2/conversation-turn.schema.json": "75bb6a4a03072a958feb5b34e2a1f40471e9e3996a81fa430b65717f0773c9a4",
|
|
131
131
|
"schemas/v2/credential-provenance.schema.json": "f86b09e94b1c524dec7d1c6353965665ae8891e9517faef6e742d639c5578c99",
|
|
132
132
|
"schemas/v2/credential-reference.schema.json": "ea699ce30f58fb2c32d4279158b3f730f70be7b2dc8c3037693a76e66eb3e001",
|
|
133
|
-
"schemas/v2/debug-bundle.schema.json": "
|
|
133
|
+
"schemas/v2/debug-bundle.schema.json": "0ec1e0bca9c0a359cc6cf3448e5200a1c3d3d5b1e5efaa6605231477daaf3fee",
|
|
134
134
|
"schemas/v2/dispatch-config.schema.json": "5718c1ed7d95672e618bcb1f1da8e38180e144b12151307afc3aba1314dfcef0",
|
|
135
135
|
"schemas/v2/effect-ledger-projection.schema.json": "0d118d385a43921118a76888449c5c3c5618eab1b80914b84a0a1b48d18822c1",
|
|
136
136
|
"schemas/v2/effect-seam-manifest.schema.json": "197ef9944ab027a50d1b9352f317cbbc12c8ad955fdb14650fb90477052ffef0",
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
"schemas/v2/suspend-request.schema.json": "368d9932efb08f85b4520cac44b3c553876209f55bada93b5fcac93cc890b411",
|
|
187
187
|
"schemas/v2/tool-descriptor.schema.json": "8c98fb5e93bc649dff859d3d1dde8075db929efd6c49c496252d0a88cccc73e1",
|
|
188
188
|
"schemas/v2/trigger-event.schema.json": "710ecca8b3ecdad3a532a96768177f062192b24a589a4f798c7eb24bab64b30a",
|
|
189
|
-
"schemas/v2/trigger-subscription-registration.schema.json": "
|
|
189
|
+
"schemas/v2/trigger-subscription-registration.schema.json": "5f7959f3e13f49399783e47f73101d6af95e5abccea1af2f0014ccbe3e51006c",
|
|
190
190
|
"schemas/v2/trigger-subscription.schema.json": "1f5c286f68577d531ef9445ab2493f8d69ac9ee42313d28d758a0f00dd0fab36",
|
|
191
191
|
"schemas/v2/ui-plugin-message.schema.json": "879d65867c01656b3962d64283e66364dcb8f626ad26c5fcf2d2d55a79ae0ce9",
|
|
192
192
|
"schemas/v2/webhook-dead-letter-page.schema.json": "543f5ccc26d8f777684a037172ab7b9fdf70735e5394eeb42e5fa6569503f528",
|
|
@@ -204,13 +204,13 @@
|
|
|
204
204
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
205
205
|
"spec/v1/alias-detectors.json": "0b2f28808ffe0b9d98a187775038c3406bb70919747a788a6e4f866e293ae6d3",
|
|
206
206
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
207
|
-
"spec/v1/core-standard-manifest.json": "
|
|
207
|
+
"spec/v1/core-standard-manifest.json": "746278bbf9cf46840ba3bd5a85683251ddbc379466585f54e582754ce684d637",
|
|
208
208
|
"spec/v1/deprecations.json": "a3293072ea951857395d05f0437264e88965f1e9019ff61c55bacfa9c1aaa17c",
|
|
209
209
|
"spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
|
|
210
210
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
211
211
|
"spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
|
|
212
212
|
"spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
|
|
213
|
-
"spec/v1/gaps.json": "
|
|
213
|
+
"spec/v1/gaps.json": "2705bd6f7be31392ba09da318e99d55905dd08aa80e76e1226cf8f1a85132d18",
|
|
214
214
|
"spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
|
|
215
215
|
"spec/v1/migrations.json": "4c50760be70cb644d02427bcaf5749606700edf4513f423903cf3b1d12f5c300",
|
|
216
216
|
"spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"spec/v2/core/execution.md": "8a0c9db1bbfb97c008ab9328b7333c1cc3318196e78bfbefb49e781bdf4f9d44",
|
|
228
228
|
"spec/v2/core/form-content-packs.md": "02931227899676fd4581a27017be2a6c4953904717be9b81c29d12f87ab3917d",
|
|
229
229
|
"spec/v2/core/headers.md": "21d6c8220c3ac75b10e31c4cf6179abb539e2558af5006e40228d8c383ed5c92",
|
|
230
|
-
"spec/v2/core/host-services.md": "
|
|
230
|
+
"spec/v2/core/host-services.md": "73f26ea32891044131d63f9861d2cbddccb5d08bf76e29e47afef9bb5b646791",
|
|
231
231
|
"spec/v2/core/i18n.md": "882904b7d59f992ab1e5f7346167161a54cf665b8fee47e64a3862cf9133a580",
|
|
232
232
|
"spec/v2/core/idempotency.md": "adbd317a6ebb57c2759c076297b31f67def9e391576bccd0bbea787b13e85872",
|
|
233
233
|
"spec/v2/core/identity.md": "f61dc646f56fc1c07016c3acec9e8eb8c51051c55ecbedc986de793669fb6e6b",
|
|
@@ -240,16 +240,16 @@
|
|
|
240
240
|
"spec/v2/core/persistence.md": "249a1de353f421cdf92b9006a13a10afdb002f4029eb6a13520cc95252edb2ec",
|
|
241
241
|
"spec/v2/core/portability.md": "ab13a6ca43544b9a6a91edfb377ec680c768d14be841c84fb79125b231426891",
|
|
242
242
|
"spec/v2/core/replay.md": "4a8b53325e18aa53400ece81168c76b4402b932e5e304a391754f293b126eabf",
|
|
243
|
-
"spec/v2/core/runs.md": "
|
|
243
|
+
"spec/v2/core/runs.md": "991757ded9b3645bafad2344f4ccb75c7d79993bd80269f9dd3489193841d71b",
|
|
244
244
|
"spec/v2/core/security-defaults.md": "887d8c4bc592094e3b931c23f1c2175c18ebbd47528b074016f9d2091bc513b2",
|
|
245
245
|
"spec/v2/core/storage.md": "dc94b985847c1341c7a064f04a6010f0fdbe6c3ab3e555d4362878c81d764c18",
|
|
246
246
|
"spec/v2/core/tool-catalog.md": "d16a9004418719c48301804aefa4d7ab6f41112a4ed83727f552aa1b92b418b0",
|
|
247
247
|
"spec/v2/core/versioning.md": "b7acb0d8b77425cb53ea700e47ba790bf7506de600c398690e6ec5bb9c7a220e",
|
|
248
|
-
"spec/v2/core/webhooks.md": "
|
|
248
|
+
"spec/v2/core/webhooks.md": "b0c08a3511b9319da7b0f7d83cddb15758acfc92e4c58c3604ac0f6b1ab13242",
|
|
249
249
|
"spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
|
|
250
|
-
"spec/v2/corrections.json": "
|
|
250
|
+
"spec/v2/corrections.json": "3a8d93e33865b692d26a3b6bfd3819766e206b2e0c2a69669baa4bf833d05a49",
|
|
251
251
|
"spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
|
|
252
|
-
"spec/v2/declaration.json": "
|
|
252
|
+
"spec/v2/declaration.json": "967f6db929fd7c4000d41a12d8b6d9f77bad90348de0dbc792b02f33e7026385",
|
|
253
253
|
"spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
|
|
254
254
|
"spec/v2/errors.json": "83f048b0a06952b4af6f125fb66c4212acab51056b411390f1010acd6ab9cdc0",
|
|
255
255
|
"spec/v2/errors.schema.json": "47404ad53e6147cf9017b813483712466573e419fb33e6c4b6c12d3e7deba1bc",
|
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
"spec/v2/ext/brand/README.md": "82989c018d7965bab029bcce7a67b1d76ffc433d08971a4cac91befcccc09438",
|
|
261
261
|
"spec/v2/ext/canvas/README.md": "00ca0856e3a0146f1eeaec87209d450e2900ed2748dc7520cc60250a0d3ffbeb",
|
|
262
262
|
"spec/v2/ext/chat/README.md": "f59b6706162abbfff96f8bccd4c0e1aeed721047cf62e9af58c875727a38d716",
|
|
263
|
-
"spec/v2/ext/coordination/README.md": "
|
|
263
|
+
"spec/v2/ext/coordination/README.md": "7185af14aa0a2b554095ddbf10b0443b535fd28311a1836c6aa733b203030a9a",
|
|
264
264
|
"spec/v2/ext/dataIntegration/README.md": "286dec8aa29e4d3225cbd8b3a361f5a438c3288e759966b9415ecbb99c576e70",
|
|
265
265
|
"spec/v2/ext/entities/README.md": "956d4a9a2aedb8cb9c79ce8145580480920474cc6943f0e33ede32d7b20414dc",
|
|
266
266
|
"spec/v2/ext/grpc-transport/README.md": "6ba378e3c6d1f2e3269b392cfdf0c40565ee9bfa259d1baaa9ee1d350e38e792",
|
|
@@ -303,9 +303,9 @@
|
|
|
303
303
|
"spec/v2/path-manifest.json": "27c2307b84376b2f22bea4e2213fc7e33ef3bf8fd1d18039f7955edb00dc9c9a",
|
|
304
304
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
305
305
|
"spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
|
|
306
|
-
"spec/v2/release.json": "
|
|
306
|
+
"spec/v2/release.json": "0befa21e6634c82cd3d6be9e487f79b7bf77fd3143ce3e0aed301230753b21bc",
|
|
307
307
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
308
|
-
"spec/v2/surface-baseline.json": "
|
|
308
|
+
"spec/v2/surface-baseline.json": "fd44c977c6646c0bcbd85f9f5c4dce620bba29ee56b10cd077e3f71437e0276a"
|
|
309
309
|
},
|
|
310
|
-
"corpusCommit": "
|
|
310
|
+
"corpusCommit": "f84011f0ce7ba068e53c1aa2b92d17fa97f5df5f"
|
|
311
311
|
}
|
package/src/cli.ts
CHANGED
|
@@ -685,7 +685,7 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
685
685
|
if (target.major === 2) return verdictFor(profile)?.witnessedPasses ?? 0;
|
|
686
686
|
const floor = PROFILE_FLOOR_SCENARIOS[profile];
|
|
687
687
|
if (!floor) return 0;
|
|
688
|
-
const onFloor = (scenario: string): boolean => floor.required.includes(scenario) || (floor.requiredAnyPrefix ?? []).some((pre) => scenario.startsWith(pre));
|
|
688
|
+
const onFloor = (scenario: string): boolean => floor.required.includes(scenario) || (floor.requiredAnyPrefix ?? []).some((pre) => scenario.startsWith(pre)) || (floor.requiredAnyOf ?? []).some((g) => g.includes(scenario));
|
|
689
689
|
return derived.requirements.filter((r) => r.disposition === 'executed-pass' && onFloor(r.scenarioId)).length;
|
|
690
690
|
};
|
|
691
691
|
// `certified` IS the verdict (RFC 0148 §A; RFC 0168 §E.1 adds the bundle-wide
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
type DiscoveryPayload,
|
|
32
32
|
} from './profiles.js';
|
|
33
33
|
import { CERTIFIABLE, DISPOSITIONS, type Disposition } from './requirement-ledger.js';
|
|
34
|
-
import { floorFilesFor, requirementIdForPrefix, requirementIdForScenario } from './requirement-registry.js';
|
|
34
|
+
import { floorFilesFor, requirementIdForAnyOf, requirementIdForPrefix, requirementIdForScenario } from './requirement-registry.js';
|
|
35
35
|
import { UNCLASSIFIED_RETURN_DETAIL } from './soft-skip.js';
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -149,7 +149,7 @@ export function findLiteral(value: unknown, literal: string): string[] {
|
|
|
149
149
|
* it is the prefix requirement id, and the rows that satisfy it are the
|
|
150
150
|
* matching scenario rows.
|
|
151
151
|
*/
|
|
152
|
-
function requiredFor(profile: string, document: Readonly<Record<string, unknown>>): { files: string[]; prefixes: string[]; discoveryOnly: boolean } | null {
|
|
152
|
+
function requiredFor(profile: string, document: Readonly<Record<string, unknown>>): { files: string[]; prefixes: string[]; anyOf: Array<readonly string[]>; discoveryOnly: boolean } | null {
|
|
153
153
|
const floor = PROFILE_FLOOR_SCENARIOS[profile];
|
|
154
154
|
if (floor === undefined) return null;
|
|
155
155
|
// Discovery-conditional floors (G7) are evaluated against the bundle's own
|
|
@@ -159,6 +159,7 @@ function requiredFor(profile: string, document: Readonly<Record<string, unknown>
|
|
|
159
159
|
return {
|
|
160
160
|
files: [...files],
|
|
161
161
|
prefixes: [...(floor.requiredAnyPrefix ?? [])],
|
|
162
|
+
anyOf: [...(floor.requiredAnyOf ?? [])],
|
|
162
163
|
discoveryOnly: floor.discoveryOnly === true,
|
|
163
164
|
};
|
|
164
165
|
}
|
|
@@ -272,6 +273,26 @@ export function verifyBundleV2(bundle: BundleV2Like): BundleV2Verdict {
|
|
|
272
273
|
if (!considered.some((r) => isWitnessedPass(r))) notCertifiable.push(id);
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
for (const group of req.anyOf) {
|
|
277
|
+
// RFC 0229 §E: one requirement, satisfied by a WITNESSED pass of any
|
|
278
|
+
// member with no member failing. The emitter writes a summary row under
|
|
279
|
+
// the group id; member rows are read when it is absent.
|
|
280
|
+
const id = requirementIdForAnyOf(group);
|
|
281
|
+
required.push(id);
|
|
282
|
+
const summary = byId.get(id)?.[0];
|
|
283
|
+
const members = group.map((f) => byId.get(requirementIdForScenario(scenarioBasename(f)))?.[0]).filter((r): r is BundleV2Requirement => r !== undefined);
|
|
284
|
+
if (summary === undefined && members.length === 0) {
|
|
285
|
+
profileRejections.push({ kind: 'unwitnessed-requirement', profile, requirementId: id, detail: `${id}: no alternative (${group.join(', ')}) recorded a row` });
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const considered = summary !== undefined ? [summary] : members;
|
|
289
|
+
if (considered.some((r) => r.disposition === 'executed-pass' && !isWitnessedPass(r))) {
|
|
290
|
+
profileRejections.push({ kind: 'vacuous-pass', profile, requirementId: id, detail: `${id}: executed-pass with no witnessed assertion` });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (considered.some((r) => r.disposition === 'executed-fail') || !considered.some((r) => isWitnessedPass(r))) notCertifiable.push(id);
|
|
294
|
+
}
|
|
295
|
+
|
|
275
296
|
const evidenceValid = profileRejections.length === 0;
|
|
276
297
|
const certified = derivable && evidenceValid && notCertifiable.length === 0 && (req.discoveryOnly || required.length > 0);
|
|
277
298
|
return { profile, derivable, floorUnspecified: false, required, rejections: profileRejections, notCertifiable, evidenceValid, certified };
|
package/src/lib/driver.ts
CHANGED
|
@@ -11,6 +11,46 @@
|
|
|
11
11
|
|
|
12
12
|
import { loadEnv } from './env.js';
|
|
13
13
|
import { seamPath, targetMajor } from './seams.js';
|
|
14
|
+
import { scaledTimeoutMs } from './timeout-scale.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How long one request may go unanswered before the driver gives up (#1829).
|
|
18
|
+
* Below vitest's 30 s `testTimeout`, so a lost response ends as a named
|
|
19
|
+
* {@link TransportError} and not as a bare harness timeout. Driver long-polls
|
|
20
|
+
* are at most 5 s. Scales with `OPENWOP_POLL_TIMEOUT_SCALE`.
|
|
21
|
+
*/
|
|
22
|
+
export const REQUEST_TIMEOUT_MS = 20_000;
|
|
23
|
+
|
|
24
|
+
/** Leads every {@link TransportError} message; `resolveItRecord` keys on it. */
|
|
25
|
+
export const TRANSPORT_LOSS_PREFIX = 'transport-loss: ';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* No response arrived: the request timed out or the connection failed. The
|
|
29
|
+
* suite observed nothing about the host, so this is never a verdict on it. A
|
|
30
|
+
* runner whose network dropped for 9 s once recorded `executed-fail` against a
|
|
31
|
+
* host that had answered in 2.6 ms (MyndHyve cut, suite 2.45.2).
|
|
32
|
+
*/
|
|
33
|
+
export class TransportError extends Error {
|
|
34
|
+
constructor(
|
|
35
|
+
readonly kind: 'timeout' | 'network',
|
|
36
|
+
readonly method: string,
|
|
37
|
+
/** Origin and path only. A query string can carry a token. */
|
|
38
|
+
readonly target: string,
|
|
39
|
+
reason: string,
|
|
40
|
+
) {
|
|
41
|
+
super(`${TRANSPORT_LOSS_PREFIX}${method} ${target} got no response (${reason}); the suite could not observe the host`);
|
|
42
|
+
this.name = 'TransportError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function withoutQuery(url: string): string {
|
|
47
|
+
try {
|
|
48
|
+
const u = new URL(url);
|
|
49
|
+
return `${u.origin}${u.pathname}`;
|
|
50
|
+
} catch {
|
|
51
|
+
return url.split('?')[0] ?? url;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
14
54
|
|
|
15
55
|
export interface OpenWOPResponse {
|
|
16
56
|
readonly status: number;
|
|
@@ -25,6 +65,10 @@ export interface OpenWOPRequestInit {
|
|
|
25
65
|
/** `false` sends no default credential. A caller-supplied `Authorization`
|
|
26
66
|
* header is always sent as given, whatever this says. */
|
|
27
67
|
readonly authenticated?: boolean;
|
|
68
|
+
/** Replaces the driver's own bound entirely; an abort it raises is rethrown as-is. */
|
|
69
|
+
readonly signal?: AbortSignal;
|
|
70
|
+
/** Overrides {@link REQUEST_TIMEOUT_MS} for one request (scaled the same way). */
|
|
71
|
+
readonly timeoutMs?: number;
|
|
28
72
|
}
|
|
29
73
|
|
|
30
74
|
class OpenWOPDriver {
|
|
@@ -83,9 +127,24 @@ class OpenWOPDriver {
|
|
|
83
127
|
fetchInit.body = JSON.stringify(init.body);
|
|
84
128
|
}
|
|
85
129
|
}
|
|
86
|
-
const
|
|
130
|
+
const boundMs = scaledTimeoutMs(init.timeoutMs ?? REQUEST_TIMEOUT_MS);
|
|
131
|
+
fetchInit.signal = init.signal ?? AbortSignal.timeout(boundMs);
|
|
132
|
+
let res: Response;
|
|
133
|
+
let text: string;
|
|
134
|
+
try {
|
|
135
|
+
res = await fetch(url, fetchInit);
|
|
136
|
+
// The body is read under the same bound: a response whose headers arrive
|
|
137
|
+
// and whose body never does is the same lost observation.
|
|
138
|
+
text = await res.text();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
// The caller's own signal aborted: that is the caller's event, not a loss.
|
|
141
|
+
if (init.signal?.aborted === true) throw err;
|
|
142
|
+
const name = err instanceof Error ? err.name : '';
|
|
143
|
+
if (name === 'TimeoutError') throw new TransportError('timeout', method, withoutQuery(url), `no response within ${boundMs} ms`);
|
|
144
|
+
const cause = err instanceof Error ? (err.cause instanceof Error ? err.cause.message : err.message) : String(err);
|
|
145
|
+
throw new TransportError('network', method, withoutQuery(url), cause.slice(0, 160));
|
|
146
|
+
}
|
|
87
147
|
|
|
88
|
-
const text = await res.text();
|
|
89
148
|
let json: unknown;
|
|
90
149
|
try {
|
|
91
150
|
json = text.length > 0 ? JSON.parse(text) : undefined;
|
|
@@ -42,9 +42,19 @@ export async function readMemoryAttributionCap(): Promise<Record<string, unknown
|
|
|
42
42
|
return attr && typeof attr === 'object' ? (attr as Record<string, unknown>) : null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* True when the host commits to emitting `memory.written`.
|
|
47
|
+
*
|
|
48
|
+
* Major 1: the v1 block's `supported: true` (its schema `const`) AND
|
|
49
|
+
* `emitsWriteEvents: true`. Major 2: there is no `supported` field — presence
|
|
50
|
+
* of the `memory` family record IS the claim (RFC 0169 §A.2), and
|
|
51
|
+
* `readMemoryAttributionCap` returns the `attribution` object only when that
|
|
52
|
+
* record is present — so the commitment is `emitsWriteEvents: true` alone.
|
|
53
|
+
* Requiring `supported` at major 2 soft-skipped every v2 host (suite 2.45.3).
|
|
54
|
+
*/
|
|
46
55
|
export function emitsWriteEvents(cap: Record<string, unknown> | null): boolean {
|
|
47
|
-
|
|
56
|
+
if (cap?.['emitsWriteEvents'] !== true) return false;
|
|
57
|
+
return targetMajor() === 2 ? true : cap['supported'] === true;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
const SEED_FIXTURE = 'conformance-noop';
|
|
@@ -71,9 +81,34 @@ interface RunEventLike {
|
|
|
71
81
|
payload?: Record<string, unknown>;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Fetches a run's events and returns only the `memory.written` ones.
|
|
86
|
+
*
|
|
87
|
+
* Major 2: `GET /runs/{runId}/events` is the SSE stream (`streamRunEvents`,
|
|
88
|
+
* `text/event-stream` only), so a JSON read of it yields nothing and every leg
|
|
89
|
+
* would soft-skip "run wrote no memory" — a `blocked` row that denies
|
|
90
|
+
* certification for a suite defect. The JSON projection at v2 is the poll
|
|
91
|
+
* (`events.md` §Poll), read from sequence 0 and paged on `afterSequence` until
|
|
92
|
+
* `lastSequence` is reached.
|
|
93
|
+
*/
|
|
75
94
|
export async function memoryWrittenEvents(runId: string): Promise<RunEventLike[]> {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
if (targetMajor() !== 2) {
|
|
96
|
+
const res = await driver.get(`${runsPath()}/${encodeURIComponent(runId)}/events`);
|
|
97
|
+
const events = (res.json as { events?: RunEventLike[] } | undefined)?.events ?? [];
|
|
98
|
+
return events.filter((e) => e.type === 'memory.written');
|
|
99
|
+
}
|
|
100
|
+
const out: RunEventLike[] = [];
|
|
101
|
+
let after = -1;
|
|
102
|
+
for (let page = 0; page < 50; page++) {
|
|
103
|
+
const q = after < 0 ? '' : `&afterSequence=${after}`;
|
|
104
|
+
const res = await driver.get(`/runs/${encodeURIComponent(runId)}/events/poll?timeout=1${q}`);
|
|
105
|
+
const body = res.json as { events?: Array<RunEventLike & { sequence?: number }>; lastSequence?: number } | undefined;
|
|
106
|
+
const events = res.status === 200 && Array.isArray(body?.events) ? body.events : [];
|
|
107
|
+
out.push(...events);
|
|
108
|
+
const seqs = events.map((e) => e.sequence).filter((s): s is number => typeof s === 'number');
|
|
109
|
+
const next = seqs.length ? Math.max(...seqs) : after;
|
|
110
|
+
if (next <= after || typeof body?.lastSequence !== 'number' || next >= body.lastSequence) break;
|
|
111
|
+
after = next;
|
|
112
|
+
}
|
|
113
|
+
return out.filter((e) => e.type === 'memory.written');
|
|
79
114
|
}
|
package/src/lib/polling.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { driver } from './driver.js';
|
|
31
|
+
import { scaledTimeoutMs } from './timeout-scale.js';
|
|
31
32
|
import { targetMajor } from './seams.js';
|
|
32
33
|
|
|
33
34
|
export interface RunSnapshot {
|
|
@@ -52,24 +53,7 @@ export interface RunSnapshot {
|
|
|
52
53
|
const POLL_INTERVAL_MS = 250;
|
|
53
54
|
const DEFAULT_TIMEOUT_MS = Number(process.env.OPENWOP_LIFECYCLE_TIMEOUT_MS ?? 10_000);
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
* Multiplier applied to every poll bound (see the module docstring). Invalid,
|
|
57
|
-
* non-positive, or non-finite values fall back to `1` rather than silently
|
|
58
|
-
* producing a zero or negative deadline — a mis-set knob must not turn every
|
|
59
|
-
* poll into an instant failure that looks like a host defect.
|
|
60
|
-
*/
|
|
61
|
-
function pollTimeoutScale(): number {
|
|
62
|
-
const raw = process.env.OPENWOP_POLL_TIMEOUT_SCALE;
|
|
63
|
-
if (raw === undefined || raw === '') return 1;
|
|
64
|
-
const n = Number(raw);
|
|
65
|
-
return Number.isFinite(n) && n > 0 ? n : 1;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Apply the scale to a bound, rounding up so a scale of 1 is exactly a no-op. */
|
|
69
|
-
export function scaledTimeoutMs(timeoutMs: number): number {
|
|
70
|
-
const scale = pollTimeoutScale();
|
|
71
|
-
return scale === 1 ? timeoutMs : Math.ceil(timeoutMs * scale);
|
|
72
|
-
}
|
|
56
|
+
export { scaledTimeoutMs };
|
|
73
57
|
|
|
74
58
|
/**
|
|
75
59
|
* Live-model scenarios (RFC 0111's `conformance-context-budget-live`) drive real
|
package/src/lib/profiles.ts
CHANGED
|
@@ -495,6 +495,16 @@ export interface ProfileFloor {
|
|
|
495
495
|
readonly required: readonly string[];
|
|
496
496
|
/** Prefix groups where ≥1 matching passed scenario satisfies the group. */
|
|
497
497
|
readonly requiredAnyPrefix?: readonly string[];
|
|
498
|
+
/**
|
|
499
|
+
* RFC 0229 §E — ANY-OF groups: each inner list names alternative scenario
|
|
500
|
+
* files, and the group is satisfied when at least one of them records a
|
|
501
|
+
* WITNESSED pass and none of them records a failure. A group is one
|
|
502
|
+
* requirement (`openwop.floor.anyof.<stem>+<stem>`, a summary row the runner
|
|
503
|
+
* derives from the members, like `requiredAnyPrefix`). An `inapplicable` or
|
|
504
|
+
* `skipped` member never satisfies it: `openwop-secrets` is certified by the
|
|
505
|
+
* canary round-trip OR the run-witness, not by the absence of both.
|
|
506
|
+
*/
|
|
507
|
+
readonly requiredAnyOf?: ReadonlyArray<readonly string[]>;
|
|
498
508
|
/**
|
|
499
509
|
* The profile is discovery-payload-only: its predicate IS the whole claim and
|
|
500
510
|
* it has no runtime floor. This flag exists so an EMPTY floor is a decision on
|
|
@@ -529,6 +539,11 @@ export interface ProfileFloor {
|
|
|
529
539
|
readonly conditional?: ReadonlyArray<{ readonly path: string; readonly includes: string; readonly required: readonly string[] }>;
|
|
530
540
|
}
|
|
531
541
|
|
|
542
|
+
/** Every scenario FILE a floor names: `required`, every `conditional` branch, and every `requiredAnyOf` member. */
|
|
543
|
+
export function floorMemberFiles(floor: ProfileFloor): string[] {
|
|
544
|
+
return [...floor.required, ...(floor.conditional ?? []).flatMap((c) => c.required), ...(floor.requiredAnyOf ?? []).flat()];
|
|
545
|
+
}
|
|
546
|
+
|
|
532
547
|
export const PROFILE_FLOOR_SCENARIOS: Readonly<Record<string, ProfileFloor>> = {
|
|
533
548
|
'openwop-core-standard': {
|
|
534
549
|
required: [
|
|
@@ -607,7 +622,11 @@ export const PROFILE_FLOOR_SCENARIOS: Readonly<Record<string, ProfileFloor>> = {
|
|
|
607
622
|
// `profiles.md` §`openwop-secrets`: credential resolution per `run-options.md`
|
|
608
623
|
// §"Credential references" — the BYOK canary round-trip (`fixtures.md`
|
|
609
624
|
// §conformance-secrets-roundtrip, SR-1) is the profile's proof.
|
|
610
|
-
|
|
625
|
+
// RFC 0229 §E (2026-09-30): a SECOND floor path, as an any-of group. The
|
|
626
|
+
// canary fixture's node is an oracle over whatever its name reaches, so a
|
|
627
|
+
// production host rightly withholds it; the run-witness reads only a value
|
|
628
|
+
// the caller supplied with the same run and outputs only `matched`.
|
|
629
|
+
'openwop-secrets': { required: [], requiredAnyOf: [['byok-roundtrip.test.ts', 'secrets-run-witness.test.ts']] },
|
|
611
630
|
|
|
612
631
|
// `profiles.md` §`openwop-provider-policy`: the four-mode taxonomy shape and
|
|
613
632
|
// its enforcement on the wire.
|
|
@@ -756,9 +775,14 @@ export function verifyBundleProfile(bundle: CertificationBundleLike, profile: st
|
|
|
756
775
|
}
|
|
757
776
|
const missingFloor = requiredFiles.filter((r) => !passed.has(scenarioBasename(r)));
|
|
758
777
|
const prefixOk = (floor.requiredAnyPrefix ?? []).every((p) => [...passed].some((s) => s.startsWith(p)));
|
|
778
|
+
// RFC 0229 §E any-of groups: a v1 bundle lists only what passed, so a group
|
|
779
|
+
// holds when one member is in `results.passed`; an unmet group is reported
|
|
780
|
+
// as missing, spelled as its alternatives.
|
|
781
|
+
const groups = floor.requiredAnyOf ?? [];
|
|
782
|
+
for (const g of groups) if (!g.some((f) => passed.has(scenarioBasename(f)))) missingFloor.push(g.join(' | '));
|
|
759
783
|
// A conditional floor none of whose branches matched requires nothing — that
|
|
760
784
|
// is unprovable for a non-discovery-only profile, not proven.
|
|
761
|
-
const evaluable = floor.discoveryOnly === true || requiredFiles.length > 0 || (floor.requiredAnyPrefix ?? []).length > 0;
|
|
785
|
+
const evaluable = floor.discoveryOnly === true || requiredFiles.length > 0 || (floor.requiredAnyPrefix ?? []).length > 0 || groups.length > 0;
|
|
762
786
|
const floorProven = evaluable && missingFloor.length === 0 && prefixOk;
|
|
763
787
|
return {
|
|
764
788
|
profile,
|
|
@@ -36,6 +36,15 @@ export function requirementIdForPrefix(prefix: string): string {
|
|
|
36
36
|
return `openwop.floor.any.${prefix}`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Any-of groups (RFC 0229 §E) become one requirement:
|
|
41
|
+
* `['byok-roundtrip.test.ts', 'secrets-run-witness.test.ts']` →
|
|
42
|
+
* `openwop.floor.anyof.byok-roundtrip+secrets-run-witness`.
|
|
43
|
+
*/
|
|
44
|
+
export function requirementIdForAnyOf(files: readonly string[]): string {
|
|
45
|
+
return `openwop.floor.anyof.${files.map((f) => f.replace(/\.test\.ts$/, '')).join('+')}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
/**
|
|
40
49
|
* The requirement IDs a profile's certification rests on.
|
|
41
50
|
*
|
|
@@ -122,6 +131,7 @@ export function requirementsFor(profile: string, document?: Readonly<Record<stri
|
|
|
122
131
|
return [
|
|
123
132
|
...files.map(requirementIdForScenario),
|
|
124
133
|
...(floor.requiredAnyPrefix ?? []).map(requirementIdForPrefix),
|
|
134
|
+
...(floor.requiredAnyOf ?? []).map(requirementIdForAnyOf),
|
|
125
135
|
];
|
|
126
136
|
}
|
|
127
137
|
|
|
@@ -134,6 +144,8 @@ export function allRequirements(): readonly string[] {
|
|
|
134
144
|
for (const f of floor.required) ids.add(requirementIdForScenario(f));
|
|
135
145
|
for (const c of floor.conditional ?? []) for (const f of c.required) ids.add(requirementIdForScenario(f));
|
|
136
146
|
for (const p of floor.requiredAnyPrefix ?? []) ids.add(requirementIdForPrefix(p));
|
|
147
|
+
// an any-of group is one requirement, and each member file records under its own floor id
|
|
148
|
+
for (const g of floor.requiredAnyOf ?? []) { ids.add(requirementIdForAnyOf(g)); for (const f of g) ids.add(requirementIdForScenario(f)); }
|
|
137
149
|
void profile;
|
|
138
150
|
}
|
|
139
151
|
return [...ids].sort();
|