@openwop/openwop-conformance 2.45.22 → 2.45.24

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 (35) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +227 -608
  3. package/coverage.md +118 -90
  4. package/dist/spec-artifacts.lock.json +2 -2
  5. package/fixtures/conformance-multi-party-council-oversize.json +244 -0
  6. package/fixtures/conformance-multi-party-council.json +61 -0
  7. package/fixtures/conformance-nondeterminism.json +23 -0
  8. package/fixtures/conformance-wasm-pack-memory-cap-breach.json +23 -5
  9. package/fixtures/frontend-plugin-packs/ui/narrow.html +4 -0
  10. package/fixtures/frontend-plugin-packs/ui-plugin-pack-narrow.json +16 -0
  11. package/fixtures/wasm-packs/build.mjs +43 -0
  12. package/fixtures/wasm-packs/misbehaving-abi/module.wasm +0 -0
  13. package/fixtures/wasm-packs/misbehaving-abi/module.wat +24 -0
  14. package/fixtures/wasm-packs/misbehaving-abi/pack.json +27 -0
  15. package/fixtures/wasm-packs/misbehaving-memory/module.wasm +0 -0
  16. package/fixtures/wasm-packs/misbehaving-memory/module.wat +30 -0
  17. package/fixtures/wasm-packs/misbehaving-memory/pack.json +27 -0
  18. package/fixtures.md +109 -55
  19. package/package.json +3 -3
  20. package/requirements.json +319 -75
  21. package/scenario-majors.json +17 -2
  22. package/schemas/CORPUS-STAMP.json +32 -30
  23. package/src/lib/council-roster-witness.ts +144 -0
  24. package/src/lib/effect-receiver.ts +12 -1
  25. package/src/lib/ui-plugin-boundary-witness.ts +180 -0
  26. package/src/lib/wasm-memory-cap-witness.ts +46 -0
  27. package/src/scenarios/v2-bound-id-path-projection.test.ts +20 -0
  28. package/src/scenarios/v2-envelope-contracts-advertisement.test.ts +8 -0
  29. package/src/scenarios/v2-multi-party-council.test.ts +56 -0
  30. package/src/scenarios/v2-nondeterminism-policy-advertisement.test.ts +11 -1
  31. package/src/scenarios/v2-nondeterminism-sources.test.ts +83 -0
  32. package/src/scenarios/v2-purpose-propagation-advertisement.test.ts +1 -1
  33. package/src/scenarios/v2-purpose-propagation-onward.test.ts +119 -0
  34. package/src/scenarios/v2-ui-plugin-boundary.test.ts +68 -0
  35. package/src/scenarios/v2-wasm-memory-cap.test.ts +34 -0
@@ -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": 644,
4
+ "files": 649,
5
5
  "v1": 455,
6
- "v2": 204
6
+ "v2": 209
7
7
  },
8
8
  "majors": {
9
9
  "a2a-1-0-agent-card.test.ts": [
@@ -1569,6 +1569,9 @@
1569
1569
  "v2-multi-party-conversation-advertisement.test.ts": [
1570
1570
  2
1571
1571
  ],
1572
+ "v2-multi-party-council.test.ts": [
1573
+ 2
1574
+ ],
1572
1575
  "v2-negotiation-authenticated.test.ts": [
1573
1576
  2
1574
1577
  ],
@@ -1584,6 +1587,9 @@
1584
1587
  "v2-nondeterminism-policy-advertisement.test.ts": [
1585
1588
  2
1586
1589
  ],
1590
+ "v2-nondeterminism-sources.test.ts": [
1591
+ 2
1592
+ ],
1587
1593
  "v2-oauth-client-pkce-state-iss.test.ts": [
1588
1594
  2
1589
1595
  ],
@@ -1671,6 +1677,9 @@
1671
1677
  "v2-purpose-propagation-advertisement.test.ts": [
1672
1678
  2
1673
1679
  ],
1680
+ "v2-purpose-propagation-onward.test.ts": [
1681
+ 2
1682
+ ],
1674
1683
  "v2-queue-cross-tenant-isolation.test.ts": [
1675
1684
  2
1676
1685
  ],
@@ -1788,6 +1797,9 @@
1788
1797
  "v2-trigger-dead-letter-read.test.ts": [
1789
1798
  2
1790
1799
  ],
1800
+ "v2-ui-plugin-boundary.test.ts": [
1801
+ 2
1802
+ ],
1791
1803
  "v2-ui-plugins-advertisement.test.ts": [
1792
1804
  2
1793
1805
  ],
@@ -1806,6 +1818,9 @@
1806
1818
  "v2-version-header-honored.test.ts": [
1807
1819
  2
1808
1820
  ],
1821
+ "v2-wasm-memory-cap.test.ts": [
1822
+ 2
1823
+ ],
1809
1824
  "v2-webhook-delivery-isolation.test.ts": [
1810
1825
  2
1811
1826
  ],
@@ -1,19 +1,19 @@
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.22",
5
- "corpusTag": "v2.45.22",
4
+ "version": "2.45.24",
5
+ "corpusTag": "v2.45.24",
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": "ac6de5303872664b78ff419c035915593e01599e02835e777a0123de1baeb398",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "a3a2dd8f2691fe7fa831af534f41789abaea6e5a03ededf88dd4bc96803cdaea",
13
- "api/v2/asyncapi.yaml": "587bab6ff008b0bb1d3144029bc7ed4c3badc52ac916a0afd49d6c85923bbe1b",
14
- "api/v2/openapi.yaml": "33a60f1454fb0efeb22c667ecf505447775a0de84772b1003c3226a6e419685b",
12
+ "api/seams-v2.yaml": "139cd5f7411c714ef1f207ee3284dc75633be3817d9946c1bdd998220fa79eec",
13
+ "api/v2/asyncapi.yaml": "e9cc564d30c8ff03da39e2c1b64fa33f2f2e627dc6afebf2fddc4262e3b0a7c5",
14
+ "api/v2/openapi.yaml": "15fb4ae5e02a5366b9bb60a3c9eb05f79695d1ae3669c0746bcae34cc04c77a2",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
- "schemas/README.md": "a829e3208413062388e52ab6ba714d35889c9c7645db38a290b0c4f4992cd3da",
16
+ "schemas/README.md": "276fc1d51eb08e14e9127b9fd27761d7046ee33c12000f3a64a801322db8bf58",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
18
18
  "schemas/a2ui-surface-delta-frame.schema.json": "970d434e4fdcb925de64aaace91eb83bfbcfd8f76757a9ce9f339eda5fb3f4e8",
19
19
  "schemas/agent-deployment-transition.schema.json": "1ac987fed0f380fa5b4e9a40e8e13316e5ae42d03a0ff42db4be6273e1bfe9f4",
@@ -117,8 +117,8 @@
117
117
  "schemas/v2/artifact.schema.json": "85b9dc284d39b99d2a66fa8cbf47f2dd3d8526d3bd6cc411ad315b372de38975",
118
118
  "schemas/v2/audit-verify-result.schema.json": "14af8e9283510f0a40e48a2059c929ee44e0fc9fa3cc66a95bfbe90b17c48297",
119
119
  "schemas/v2/budget-policy.schema.json": "1de68095db0beb5c7a4be629de26e79f62ebbe748a88d46f36e768edfd1f0d68",
120
- "schemas/v2/capabilities.schema.json": "a24c99d07971dde0e4596eba0bf5616e3df4459dc2174e8aebb2c544f964337c",
121
- "schemas/v2/certification-bundle.schema.json": "9a3c67860f3b0867ff4733d3f8ae0f5d5d747b815ab39b6c255589f103119fa6",
120
+ "schemas/v2/capabilities.schema.json": "d859a413ff463ec39acc32a9cb76f2e8c7419e45683d6b7f886861765a9c36c7",
121
+ "schemas/v2/certification-bundle.schema.json": "ee3b10966458a10568a32b8a7eb4ab7bdb2f2d00da3ef6b698cd06462d52de40",
122
122
  "schemas/v2/channel-presence-payload.schema.json": "fc03df0c20cc12a25f7ee6a47503a4c5931d79fe3d5ff24815e8fd7c1bcb9a87",
123
123
  "schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
124
124
  "schemas/v2/chat-card-pack-manifest.schema.json": "92a57db2fd4b2463f14ef155fef8e3f18afc78960b646459dcf32efc79ff6bf5",
@@ -144,7 +144,7 @@
144
144
  "schemas/v2/envelopes/schema.request.schema.json": "be2c5f7aa881cdd252ccff47ac02193659e1393633a1541f4297383205d384a2",
145
145
  "schemas/v2/envelopes/schema.response.schema.json": "d049a081267c06c28879c331b147f3ed821b14ccfd93005c67aff64db0a8c68e",
146
146
  "schemas/v2/envelopes/ui.a2ui-surface.schema.json": "e92a2548ba65454c9e0a34f72ea6768d18f59e61fa5da7668d0b2d5baddaddc8",
147
- "schemas/v2/error-envelope.schema.json": "fb85e2020a91e0c1590a78f57bbf3bbf1cffbc1bc60e1ae692265a77e9d937ff",
147
+ "schemas/v2/error-envelope.schema.json": "8ddb1d80330dfef17572a74dfe3a53e1a06411e4b0af6a6f1dd6e7e7461da987",
148
148
  "schemas/v2/eval-summary.schema.json": "2f384daf05b8f3b4f604688cae504d6d9971ba41b8c771e55fe9a17e09e86b7a",
149
149
  "schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
150
150
  "schemas/v2/form-content-pack-manifest.schema.json": "a1942a124e250a6e9fbdb6b811c5a125e78fbcbcaa46dfc71d0636f24710a339",
@@ -192,7 +192,7 @@
192
192
  "schemas/v2/trigger-event.schema.json": "710ecca8b3ecdad3a532a96768177f062192b24a589a4f798c7eb24bab64b30a",
193
193
  "schemas/v2/trigger-subscription-registration.schema.json": "5f7959f3e13f49399783e47f73101d6af95e5abccea1af2f0014ccbe3e51006c",
194
194
  "schemas/v2/trigger-subscription.schema.json": "06abe021712a4bfc5a107a067053cd3cc9704f47ae7066d8449a508c368a0ab2",
195
- "schemas/v2/ui-plugin-message.schema.json": "879d65867c01656b3962d64283e66364dcb8f626ad26c5fcf2d2d55a79ae0ce9",
195
+ "schemas/v2/ui-plugin-message.schema.json": "2211d58ddc3c42586586a61f03e637212465aff6c922755131f0beab58823435",
196
196
  "schemas/v2/webhook-dead-letter-page.schema.json": "78f3284722c2b42d52ff8a1e6d9b9092ba1ac35c2dbe036f3ff2470b34086d1e",
197
197
  "schemas/v2/webhook-delivery.schema.json": "5f5361ede12098b5c2d685f0aa70fcb07219fd6b68c6ad6af393ca8dd266d56a",
198
198
  "schemas/v2/webhook-verification.schema.json": "76e4e0d81143ffd2649bc7adba2aa34f2525e2c4e9f0300a425d908b3163196b",
@@ -206,33 +206,33 @@
206
206
  "schemas/workload-identity.schema.json": "64ed93e12cc9f0657e56ff9b8f045e5d68d397df5276d103c07f42c7f34f0579",
207
207
  "schemas/workspace-file-create.schema.json": "70a23b92c2958c0165d7d29aea9020a045dc7816af82c80c5f50d0aac19c4856",
208
208
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
209
- "spec/v1/alias-detectors.json": "0b2f28808ffe0b9d98a187775038c3406bb70919747a788a6e4f866e293ae6d3",
209
+ "spec/v1/alias-detectors.json": "22444c22bb88c568339ef512553f9fc38146e22b566d11ab8608f655d688d19c",
210
210
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
211
- "spec/v1/core-standard-manifest.json": "b1d45d8fc106392a46ea67fd227bcdf623454ec0499e115487ed4e8dc5647d7f",
212
- "spec/v1/deprecations.json": "a3293072ea951857395d05f0437264e88965f1e9019ff61c55bacfa9c1aaa17c",
211
+ "spec/v1/core-standard-manifest.json": "b1d32d221b43e1897cd65fc22bdac4384376fa720733be1a736dea31141eeee9",
212
+ "spec/v1/deprecations.json": "e723bc11d7272c71789ad1303bb7f21809a8a024abf57594f9d5c35dc199ece9",
213
213
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
214
214
  "spec/v1/end-of-support.json": "35f0bf3b70fe78b16e2a2fd81a153fc27e703f6c16376c6fe7cdf1f4f2c4e6d4",
215
215
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
216
216
  "spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
217
217
  "spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
218
- "spec/v1/gaps.json": "335186074848531561262525967bd85b379b3137a2233dd4c8b63914222dfa71",
218
+ "spec/v1/gaps.json": "ab22f506255eef90fba09585776dce09faa803207807eccbb3ea1c1a6cc80480",
219
219
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
220
220
  "spec/v1/migrations.json": "4c50760be70cb644d02427bcaf5749606700edf4513f423903cf3b1d12f5c300",
221
221
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
222
222
  "spec/v1/operation-path-manifest.json": "f988f7ebf58ff192e999e2a71192066052f837acc03c91d047d6050ca21a83b0",
223
223
  "spec/v1/spec-gaps.json": "ea9612af55bea9dae3e82223d4f714243f8f590b0193f1188257b13491a38afc",
224
- "spec/v2/README.md": "ddea90d7cf14aefffe1a43eeeecd4222dadad1d1e5ac64dceb073990eb93d9ef",
224
+ "spec/v2/README.md": "c58acd84bb67c69043d9f777cbaa16d03e4f0f0a8158d2984526b69981067a14",
225
225
  "spec/v2/core/artifact-type-packs.md": "f734740cc3963f86c9f273a7dace26291bba2855161ddd3540618131a2c8d13e",
226
- "spec/v2/core/capabilities.md": "503fd78ce55aca2cf83b872a5d6086f016470df2d9bcd7fb427b8f16fb9d044a",
226
+ "spec/v2/core/capabilities.md": "8218c44f90782df9630dd276e004def162dad989464cba85e7fc42d987bb61f8",
227
227
  "spec/v2/core/conformance.md": "3ca291e63bc70a6ef829ae8759ad0ded3741ea7aa507bbbdffc57de443ab2ae2",
228
228
  "spec/v2/core/connection-packs.md": "c80f95bd6a03698f35c72cdb2b46bd973f49203bbd786c26760e35fa86e6f23d",
229
- "spec/v2/core/conversation.md": "e0351883a57e90f13d2e9f6f5d3e80080b46be4988617448cb7a0e0c7c0bfbad",
230
- "spec/v2/core/errors.md": "91f46acc9c9a5532a98cf62c69285d68575c100ff6064f0b1bf94731a087e34a",
231
- "spec/v2/core/events.md": "00746062621ec98052ee323755fbdb10f4fb070439058039e44feb6bb19b073a",
229
+ "spec/v2/core/conversation.md": "8d6abedf6af408a74577753bfca24719d318e2632e10b45ff5d63bdcf31a8aaa",
230
+ "spec/v2/core/errors.md": "e48838701b86bbfe0ffee77b18a8ffc4e5f04724b4318507d9771da653f8da0f",
231
+ "spec/v2/core/events.md": "3eb297c1ca8a4c20017248051fbe9c5319bb51ef1ba4cb476e29840cbbe141a6",
232
232
  "spec/v2/core/execution.md": "8a0c9db1bbfb97c008ab9328b7333c1cc3318196e78bfbefb49e781bdf4f9d44",
233
233
  "spec/v2/core/form-content-packs.md": "02931227899676fd4581a27017be2a6c4953904717be9b81c29d12f87ab3917d",
234
- "spec/v2/core/headers.md": "24b0d84ab5521e20f2669fca8006a11f9faf5c2311199e57375b8fa64ce763ed",
235
- "spec/v2/core/host-services.md": "73f26ea32891044131d63f9861d2cbddccb5d08bf76e29e47afef9bb5b646791",
234
+ "spec/v2/core/headers.md": "18e52477898e67b0b3778d1fdd22e07bc43d561c03b501573404cd53245edef6",
235
+ "spec/v2/core/host-services.md": "168a40935a03867ee82461b5d83d05578be25ee3e5af4eb080587b891231e242",
236
236
  "spec/v2/core/i18n.md": "882904b7d59f992ab1e5f7346167161a54cf665b8fee47e64a3862cf9133a580",
237
237
  "spec/v2/core/idempotency.md": "adbd317a6ebb57c2759c076297b31f67def9e391576bccd0bbea787b13e85872",
238
238
  "spec/v2/core/identity.md": "02656ffa413b5546342b8580ae928a74e621460746df74d7db50ff4df41d2310",
@@ -241,10 +241,10 @@
241
241
  "spec/v2/core/node-pack-runtimes.md": "e1088cb69b8c3c67256c494063328916c2980ab1aa97a7063de25bab82fdb1e5",
242
242
  "spec/v2/core/oauth.md": "c069ae4f9aae2eafe14c07c0bf709aa7ade0dcc821973d740400d49d901f7061",
243
243
  "spec/v2/core/overview.md": "999cb657711d6c28e62032fc31a013cc175f7a981a3b012e4bf4a90a2b84344c",
244
- "spec/v2/core/packs.md": "0d1516c4b08fe67b85759feb9ccbafdd766216435a4a0a7e58e27e6f9fe6b6cc",
244
+ "spec/v2/core/packs.md": "74d9c804a71e0e0400e7a5667a918670bcb435be0702b4625ec774ef0f1aa359",
245
245
  "spec/v2/core/persistence.md": "249a1de353f421cdf92b9006a13a10afdb002f4029eb6a13520cc95252edb2ec",
246
246
  "spec/v2/core/portability.md": "ab13a6ca43544b9a6a91edfb377ec680c768d14be841c84fb79125b231426891",
247
- "spec/v2/core/replay.md": "4a8b53325e18aa53400ece81168c76b4402b932e5e304a391754f293b126eabf",
247
+ "spec/v2/core/replay.md": "9e66e2029b65866187c0920428a8c5bf8ad1c2b39feefbd5c02f0e59909afc7d",
248
248
  "spec/v2/core/runs.md": "1c292c0ab4a3119aeff3a6e9b6e99cff69782df78c3872179f9f0661b6204f23",
249
249
  "spec/v2/core/security-defaults.md": "887d8c4bc592094e3b931c23f1c2175c18ebbd47528b074016f9d2091bc513b2",
250
250
  "spec/v2/core/storage.md": "dc94b985847c1341c7a064f04a6010f0fdbe6c3ab3e555d4362878c81d764c18",
@@ -254,10 +254,10 @@
254
254
  "spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
255
255
  "spec/v2/corrections.json": "3a8d93e33865b692d26a3b6bfd3819766e206b2e0c2a69669baa4bf833d05a49",
256
256
  "spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
257
- "spec/v2/declaration.json": "5ae9b7c84ab841930dd2885f048d99342ee81ba08cf6a6c07015070f8e17b9d1",
257
+ "spec/v2/declaration.json": "396ac924068f25d4a178062a54e244f870c556c3db9fe7a98fcc933fb318ed30",
258
258
  "spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
259
259
  "spec/v2/eos-override.json": "4c72d2f6a658943b01c1de1643cf294642091933af7832f38ee5495df684cb2d",
260
- "spec/v2/errors.json": "83f048b0a06952b4af6f125fb66c4212acab51056b411390f1010acd6ab9cdc0",
260
+ "spec/v2/errors.json": "1f0e29455edbd22e28f67f221766209c3c55ac4987beb77993b5702dd7cae131",
261
261
  "spec/v2/errors.schema.json": "47404ad53e6147cf9017b813483712466573e419fb33e6c4b6c12d3e7deba1bc",
262
262
  "spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
263
263
  "spec/v2/event-codemap.schema.json": "b173f2a9bcc0bf9b62a474ce3b91e1431d597e64fd1560fc58662cd8606eaf9c",
@@ -291,6 +291,7 @@
291
291
  "spec/v2/facets/hostEvents.schema.json": "973ff452cb91356b4c625d0b06c8d2ef335e18183de09e1afde8acead22290b3",
292
292
  "spec/v2/facets/interrupt.schema.json": "2110ef32c3f880bf706475ef4cf8ddf97ff06871b8e6003a5edbcf4439f1fb70",
293
293
  "spec/v2/facets/mcp.schema.json": "7060657dfcff170f079beed160e3db7c1d3cc4410cd3cf64863baf6459f781cd",
294
+ "spec/v2/facets/nondeterminismPolicy.schema.json": "e9dfaa32e0778a1a837fdad3b8a0673fccda9579ab08c8bfe9777362dd15e989",
294
295
  "spec/v2/facets/oauth.schema.json": "3a0cbe249d3bd5a5982349ae7ba2534776cc958e4a57e0fbeabf2bdab6e99e69",
295
296
  "spec/v2/facets/packs.schema.json": "d544a280419c0052ce0f3c3b378f7aa04085cadc31df52a3c8f43da600b131b1",
296
297
  "spec/v2/facets/replay.schema.json": "0a5aefb0108c708c6794fadd533cf256709704c4f16dbf44cd63fa8980c78018",
@@ -298,9 +299,10 @@
298
299
  "spec/v2/facets/sandbox.schema.json": "e75f54486d387b2d089791aa93b5093cd1450f1920753a0f97fc23acdfe978ed",
299
300
  "spec/v2/facets/schemaVersions.schema.json": "890d3416c4b66511d51f329abc68e6f467f4338f16807e489fe682999b2b56b9",
300
301
  "spec/v2/facets/supportedEnvelopes.schema.json": "644f22cc2236e30a611b5b7f1ba0458ff1925d68b94c514d9672583224e126bd",
302
+ "spec/v2/facets/uiPlugins.schema.json": "1fd3f8440cfb6e4422553a7c4b5f091eb3e10a40da0d43e8b977a18929116758",
301
303
  "spec/v2/facets/webhooks.schema.json": "54b576f1d150e28e1642693d846ce471c118e9c521bdf3924185305fbd1d0de4",
302
304
  "spec/v2/facets/workflowChainPacks.schema.json": "ea625ce818fbeb746478f00f31f96ee9613d538e4409c258c1bfb517acdeaba9",
303
- "spec/v2/generated/error-codes.md": "f171c0ca1be40221f9b4fbfcbce73c728c315a1e8feb8dd6bf738d6ca9e29526",
305
+ "spec/v2/generated/error-codes.md": "eafb66f1632da783c9a0347a416ae32bcf322f41b9f8783375f579791e9cdcf0",
304
306
  "spec/v2/harness-trust-anchors.json": "6007b92eae16a070fbc3777424efb0abfe865fda89d096963109b96282b91205",
305
307
  "spec/v2/harness-trust-anchors.schema.json": "372b5d92337c893d3449eff3f6bfd009ac09fdac285296d8bdbe0a08fff9e34c",
306
308
  "spec/v2/id-field-bindings.json": "aa3e7c3590a490a77286e3ae60c1ab7407e7d627e05f981c3e08f221af4bd5dc",
@@ -308,12 +310,12 @@
308
310
  "spec/v2/interop-map.schema.json": "0017c882df34ee215e2e0729de051490e09d43aac3d2c949285698ed51832f12",
309
311
  "spec/v2/migrations.json": "651954938a1e154c0b6f1e3dd6e105889d30de3bb65789f21de7f77a12869e9e",
310
312
  "spec/v2/migrations.schema.json": "b91ade6320ad1b1185c9495b9424f3aa63438ed8e412c45171c227d82c0afd86",
311
- "spec/v2/path-manifest.json": "b84c7561f50c7df0cb61b202bd6595acad3ee7edca7a45f711949bea3c69bd27",
313
+ "spec/v2/path-manifest.json": "50fc251f296b06ec5fad35724f5c63b1a32e0e8fc222a85cd29b785cebdb9c98",
312
314
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
313
315
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
314
- "spec/v2/release.json": "72fec975046a00db77ff1666dc76ada8663cbd18366104e451091e3f82c02455",
316
+ "spec/v2/release.json": "013b562cc396f4c6f000ed977be781f6f3ace49f609ee5fae045ff57aacb53a1",
315
317
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
316
- "spec/v2/surface-baseline.json": "12f193f56af68402c747503b465f8d7e18580ef832ac7b95c218ab06286478ea"
318
+ "spec/v2/surface-baseline.json": "1bc7b1cbf33591b9e9f406d2ce2de08b306dc6986e365a29d20a78331bce7344"
317
319
  },
318
- "corpusCommit": "b768269329be09d06da2de25b668ce27d5cc51df"
320
+ "corpusCommit": "2941c09ee028be32e129b96dde8b269e2a098854"
319
321
  }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The council roster (RFC 0239; `spec/v2/core/conversation.md`
3
+ * §multiPartyConversation), observed through normative operations only: run
4
+ * creation, the run snapshot and event poll, and `resolveInterrupt`.
5
+ *
6
+ * Each leg observes through the suite driver and returns findings or the
7
+ * reason it could not run, so the scenario and the self-test double run the
8
+ * same code. The `judge*` functions are pure.
9
+ */
10
+
11
+ import { driver } from './driver.js';
12
+ import { v2Discovery } from './v2.js';
13
+ import { readErrorCode } from './error-envelope.js';
14
+ import { majorProfile } from './major-profile.js';
15
+ import { readEvents, readRun, type Ev } from './fixture-run-observer.js';
16
+
17
+ export const COUNCIL = 'conformance-multi-party-council';
18
+ export const OVERSIZE = 'conformance-multi-party-council-oversize';
19
+ export const OVERSIZE_COUNT = 64;
20
+ export const GATE_NODE = 'convo';
21
+ export const ROSTER = ['host:conformance-council-a', 'host:conformance-council-b', 'host:conformance-council-c'] as const;
22
+ export const INTRUDER = 'host:conformance-intruder';
23
+
24
+ export type Skip = { readonly kind: 'skip'; readonly skip: 'blocked' | 'inapplicable'; readonly reason: string };
25
+ export type LegOutcome = { readonly kind: 'observed'; readonly findings: string[] } | Skip;
26
+ const skip = (s: 'blocked' | 'inapplicable', reason: string): Skip => ({ kind: 'skip', skip: s, reason });
27
+ const P = majorProfile(2);
28
+ const WAITING = new Set(['waiting-input', 'waiting-approval', 'waiting-external', 'paused']);
29
+ const TERMINAL = new Set(['completed', 'failed', 'cancelled']);
30
+
31
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
32
+
33
+ /** `conversation.opened.participants` agent ids, in order. */
34
+ export function rosterOf(events: readonly Ev[]): string[] | null {
35
+ const opened = events.find((e) => e.type === 'conversation.opened');
36
+ if (!opened) return null;
37
+ const p = opened.payload['participants'];
38
+ return Array.isArray(p) ? p.map((x) => (isRecord(x) ? String(x['agentId']) : String(x))) : [];
39
+ }
40
+
41
+ /** §A.2 — the configured roster is carried unchanged. */
42
+ export function judgeRoster(events: readonly Ev[]): string[] {
43
+ const got = rosterOf(events);
44
+ if (got === null) return ['no conversation.opened event was emitted'];
45
+ return JSON.stringify(got) === JSON.stringify([...ROSTER]) ? [] : [`conversation.opened.participants MUST carry the configured roster ${JSON.stringify(ROSTER)}; got ${JSON.stringify(got)}`];
46
+ }
47
+
48
+ /** §B.2–§B.3 — the non-member turn is refused with its code, unconsumed. */
49
+ export function judgeRefusal(status: number, body: unknown, statusAfter: string | null, exchangedAfter: number): string[] {
50
+ const out: string[] = [];
51
+ const code = readErrorCode(body);
52
+ if (status !== 422 || code !== 'conversation_speaker_not_participant') out.push(`a turn from ${INTRUDER} MUST be refused 422 conversation_speaker_not_participant; got ${status} ${String(code)}`);
53
+ const speaker = isRecord(body) && isRecord(body['details']) ? body['details']['speakerId'] : undefined;
54
+ if (status === 422 && speaker !== INTRUDER) out.push(`details.speakerId MUST name the refused speaker ${INTRUDER}; got ${String(speaker)}`);
55
+ if (statusAfter === null || !WAITING.has(statusAfter)) out.push(`the refused turn MUST NOT be consumed: the run MUST still wait on the interrupt; it is ${String(statusAfter)}`);
56
+ if (exchangedAfter > 0) out.push(`a refused turn MUST NOT emit conversation.exchanged; ${exchangedAfter} were emitted`);
57
+ return out;
58
+ }
59
+
60
+ /** The control — a member's turn still resolves the interrupt. */
61
+ export function judgeMemberAccepted(status: number, exchanged: number): string[] {
62
+ if (status >= 200 && status < 300 && exchanged > 0) return [];
63
+ return [`after the refusal, a turn from ${ROSTER[0]} MUST resolve the interrupt and emit conversation.exchanged; got ${status} with ${exchanged} conversation.exchanged`];
64
+ }
65
+
66
+ /** §C — the oversized roster is refused at creation with its code. */
67
+ export function judgeOversize(status: number, body: unknown, max: number): string[] {
68
+ const code = readErrorCode(body);
69
+ const out: string[] = [];
70
+ if (status !== 422 || code !== 'conversation_roster_exceeded') out.push(`a ${OVERSIZE_COUNT}-member roster over maxParticipants ${max} MUST be refused 422 conversation_roster_exceeded at run creation; got ${status} ${String(code)}`);
71
+ const d = isRecord(body) && isRecord(body['details']) ? body['details']['maxParticipants'] : undefined;
72
+ if (status === 422 && d !== max) out.push(`details.maxParticipants MUST be the advertised ${max}; got ${String(d)}`);
73
+ return out;
74
+ }
75
+
76
+ // ------------------------------------------------------------------ the legs
77
+
78
+ async function gate(fixture: string): Promise<{ family: Record<string, unknown> } | Skip> {
79
+ let doc: Record<string, unknown> | null = null;
80
+ try { doc = await v2Discovery(); } catch { doc = null; }
81
+ if (!doc) return skip('blocked', 'v2 discovery unreachable');
82
+ const family = doc['multiPartyConversation'];
83
+ if (!isRecord(family)) return skip('inapplicable', 'the host does not advertise multiPartyConversation');
84
+ const fixtures = Array.isArray(doc['fixtures']) ? (doc['fixtures'] as unknown[]) : [];
85
+ if (!fixtures.includes(fixture)) return skip('inapplicable', `the host does not advertise the ${fixture} fixture (RFC 0239 §E; RFC 0003)`);
86
+ return { family };
87
+ }
88
+
89
+ async function waitRun(runId: string, timeoutMs = 15_000): Promise<string | null> {
90
+ const deadline = Date.now() + timeoutMs;
91
+ for (;;) {
92
+ const r = await readRun(P, runId);
93
+ const s = typeof r.snapshot['status'] === 'string' ? (r.snapshot['status'] as string) : null;
94
+ if ((s && (WAITING.has(s) || TERMINAL.has(s))) || Date.now() > deadline) return s;
95
+ await new Promise((res) => setTimeout(res, 250));
96
+ }
97
+ }
98
+
99
+ async function openCouncil(): Promise<{ runId: string } | Skip> {
100
+ const g = await gate(COUNCIL);
101
+ if ('kind' in g) return g;
102
+ const created = await driver.post(P.runsPath, { workflowId: COUNCIL });
103
+ const runId = isRecord(created.json) ? created.json['runId'] : undefined;
104
+ if (created.status !== 201 || typeof runId !== 'string') return skip('blocked', `POST /runs for the advertised ${COUNCIL} answered ${created.status} ${String(readErrorCode(created.json) ?? '')}`.trim());
105
+ const s = await waitRun(runId);
106
+ if (s === null || !WAITING.has(s)) return skip('blocked', `the council run did not suspend on its conversation.exchange interrupt (status ${String(s)})`);
107
+ return { runId };
108
+ }
109
+
110
+ const resolve = (runId: string, speakerId: string) =>
111
+ driver.post(`${P.runsPath}/${P.idSegment(runId)}/interrupts/${encodeURIComponent(GATE_NODE)}`, { resumeValue: { role: 'agent', speakerId, content: 'openwop-conformance turn' } });
112
+ const exchangedCount = async (runId: string): Promise<number> => (await readEvents(P, runId)).filter((e) => e.type === 'conversation.exchanged').length;
113
+
114
+ /** §A.2, `openwop.requirement.0239.council.roster-carried`. */
115
+ export async function rosterLeg(): Promise<LegOutcome> {
116
+ const o = await openCouncil();
117
+ if ('kind' in o) return o;
118
+ return { kind: 'observed', findings: judgeRoster(await readEvents(P, o.runId)) };
119
+ }
120
+
121
+ /** §B, `openwop.requirement.0239.council.speaker-refused`. */
122
+ export async function speakerLeg(): Promise<LegOutcome> {
123
+ const o = await openCouncil();
124
+ if ('kind' in o) return o;
125
+ const refused = await resolve(o.runId, INTRUDER);
126
+ const after = await readRun(P, o.runId);
127
+ const statusAfter = typeof after.snapshot['status'] === 'string' ? (after.snapshot['status'] as string) : null;
128
+ const findings = judgeRefusal(refused.status, refused.json, statusAfter, await exchangedCount(o.runId));
129
+ const member = await resolve(o.runId, ROSTER[0]);
130
+ await waitRun(o.runId, 5_000);
131
+ findings.push(...judgeMemberAccepted(member.status, await exchangedCount(o.runId)));
132
+ return { kind: 'observed', findings };
133
+ }
134
+
135
+ /** §C, `openwop.requirement.0239.council.roster-exceeded`. */
136
+ export async function oversizeLeg(): Promise<LegOutcome> {
137
+ const g = await gate(OVERSIZE);
138
+ if ('kind' in g) return g;
139
+ const max = g.family['maxParticipants'];
140
+ if (typeof max !== 'number') return skip('inapplicable', 'the host advertises no multiPartyConversation.maxParticipants, so no roster is oversized');
141
+ if (max >= OVERSIZE_COUNT) return skip('inapplicable', `maxParticipants is ${max}; the ${OVERSIZE_COUNT}-member fixture cannot exceed it (RFC 0239 §Decisions 4, gap G2)`);
142
+ const created = await driver.post(P.runsPath, { workflowId: OVERSIZE });
143
+ return { kind: 'observed', findings: judgeOversize(created.status, created.json, max) };
144
+ }
@@ -137,7 +137,18 @@ export async function startEffectReceiver(opts: EffectReceiverOptions = {}): Pro
137
137
  const pinned = Number(process.env['OPENWOP_WEBHOOK_RECEIVER_PORT'] ?? '');
138
138
  const bindPort = Number.isInteger(pinned) && pinned > 0 && pinned < 65536 ? pinned : 0;
139
139
  const binding = receiverBinding();
140
- await new Promise<void>((resolve) => server.listen(bindPort, binding.bind, () => resolve()));
140
+ // A pinned port already held (another process, or another listener in this
141
+ // one) emits `error`, not the listen callback. Without this handler the promise
142
+ // never settled and a duplicate-delivery leg hung to its 180 s timeout instead
143
+ // of failing with the cause (MyndHyve's 2.45.18 cut, port 8787; scoped-receiver
144
+ // has had the same handler since 2.39.3).
145
+ await new Promise<void>((resolve, reject) => {
146
+ const onError = (err: Error): void => {
147
+ reject(new Error(`effect receiver could not bind ${binding.bind}:${bindPort} — ${err.message}. A pinned OPENWOP_WEBHOOK_RECEIVER_PORT must be free for this process (another process holds it?)`));
148
+ };
149
+ server.once('error', onError);
150
+ server.listen(bindPort, binding.bind, () => { server.off('error', onError); resolve(); });
151
+ });
141
152
  const addr = server.address();
142
153
  const port = typeof addr === 'object' && addr ? addr.port : 0;
143
154
  const localUrl = `http://${binding.advertise}:${port}${path}`;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * The front-end plugin boundary (RFC 0238; `spec/v2/core/packs.md` §Front-end
3
+ * plugin packs), observed through the two `uiPlugins.served` operations against
4
+ * the operator-installed `ui-plugin-pack-narrow` fixture.
5
+ *
6
+ * Two halves, as in the other shared witnesses: `observe*` assert nothing and
7
+ * return what the host answered (or why it could not be read); the `judge*`
8
+ * functions are pure and return findings. The scenario maps findings to
9
+ * requirement ids.
10
+ */
11
+
12
+ import { findCanaryLeaks } from './canaries.js';
13
+ import { driver, type OpenWOPResponse } from './driver.js';
14
+ import { v2Discovery } from './v2.js';
15
+
16
+ export const FIXTURE_PACK = 'core.openwop.conformance.ui-plugin-narrow';
17
+ export const FIXTURE_PLUGIN = 'narrow';
18
+ export const FIXTURE_ARTIFACT = 'ui-plugin-narrow-artifact';
19
+
20
+ export const framePath = (pack = FIXTURE_PACK, plugin = FIXTURE_PLUGIN): string =>
21
+ `/host/ui-plugins/${encodeURIComponent(pack)}/${encodeURIComponent(plugin)}/frame`;
22
+ export const rpcPath = (pack = FIXTURE_PACK, plugin = FIXTURE_PLUGIN): string =>
23
+ `/host/ui-plugins/${encodeURIComponent(pack)}/${encodeURIComponent(plugin)}/rpc`;
24
+
25
+ /** A Content-Security-Policy header as directive → source/token list (names lower-cased). */
26
+ export function parseCsp(header: string | null): Map<string, string[]> {
27
+ const out = new Map<string, string[]>();
28
+ for (const part of (header ?? '').split(';')) {
29
+ const [name, ...tokens] = part.trim().split(/\s+/).filter(Boolean);
30
+ if (name && !out.has(name.toLowerCase())) out.set(name.toLowerCase(), tokens);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ /** §B.3 — the `sandbox` directive admits `allow-scripts` and never `allow-same-origin`. */
36
+ export function judgeIsolation(csp: string | null): string[] {
37
+ const p = parseCsp(csp);
38
+ const sandbox = p.get('sandbox');
39
+ if (sandbox === undefined) return [`the frame's Content-Security-Policy carries no sandbox directive (got ${JSON.stringify(csp)})`];
40
+ const out: string[] = [];
41
+ const tokens = sandbox.map((t) => t.toLowerCase());
42
+ if (tokens.includes('allow-same-origin')) out.push('the sandbox directive admits allow-same-origin, so the plugin shares the host origin');
43
+ if (!tokens.includes('allow-scripts')) out.push('the sandbox directive does not admit allow-scripts, so the plugin cannot run');
44
+ return out;
45
+ }
46
+
47
+ /** §B.4 — `default-src 'none'`, and no `connect-src` source beyond the declared `connectSrc`. */
48
+ export function judgeEgress(csp: string | null, declared: readonly string[] = []): string[] {
49
+ const p = parseCsp(csp);
50
+ const out: string[] = [];
51
+ const def = p.get('default-src');
52
+ if (def === undefined || def.length !== 1 || def[0] !== "'none'") out.push(`default-src MUST be 'none' (got ${def === undefined ? 'no default-src' : def.join(' ')})`);
53
+ const connect = (p.get('connect-src') ?? []).filter((s) => s !== "'none'");
54
+ const extra = connect.filter((s) => !declared.includes(s));
55
+ if (extra.length) out.push(`connect-src admits ${extra.join(' ')}, which the plugin did not declare`);
56
+ return out;
57
+ }
58
+
59
+ export interface RpcReply { readonly status: number; readonly json: unknown; readonly text: string }
60
+
61
+ /** One `ui-plugin/1` request envelope. */
62
+ export const request = (id: number, method: string, params?: Record<string, unknown>): Record<string, unknown> =>
63
+ ({ openwop: 'ui-plugin/1', type: 'request', id, method, ...(params ? { params } : {}) });
64
+
65
+ const field = (o: unknown, k: string): unknown => (o && typeof o === 'object' ? (o as Record<string, unknown>)[k] : undefined);
66
+
67
+ /**
68
+ * §C.4 — a method the plugin did not declare (or the host does not allow) is
69
+ * refused inside a `200` with `method_not_allowed`, never executed.
70
+ */
71
+ export function judgeRefused(method: string, r: RpcReply): string[] {
72
+ const code = field(field(r.json, 'error'), 'code');
73
+ if (r.status === 200 && field(r.json, 'ok') === false && code === 'method_not_allowed') return [];
74
+ return [`${method}: a method outside the allowlists MUST return 200 with ok:false and method_not_allowed; got ${r.status} ${JSON.stringify(r.json)?.slice(0, 200)}`];
75
+ }
76
+
77
+ /** §C.2 — a method outside the closed `ui-plugin/1` enum fails validation: `400`, never executed. */
78
+ export function judgeInvalid(method: string, r: RpcReply): string[] {
79
+ if (r.status === 400) return [];
80
+ if (r.status === 200 && field(r.json, 'ok') === false) return [];
81
+ return [`${method}: a request outside the ui-plugin/1 method enum MUST NOT execute; got ${r.status} ${JSON.stringify(r.json)?.slice(0, 200)}`];
82
+ }
83
+
84
+ /** The control: the one declared method succeeds on the fixture artifact. */
85
+ export function judgeControl(r: RpcReply): string[] {
86
+ if (r.status === 200 && field(r.json, 'ok') === true) return [];
87
+ return [`artifact.read of ${FIXTURE_ARTIFACT}, the plugin's one declared method, MUST succeed; got ${r.status} ${JSON.stringify(r.json)?.slice(0, 200)}`];
88
+ }
89
+
90
+ /** §C.6 — no response carries the BYOK canary. */
91
+ export function judgeNoCanary(replies: readonly RpcReply[]): string[] {
92
+ const out: string[] = [];
93
+ for (const r of replies) for (const leak of findCanaryLeaks(r.text)) out.push(`a dispatch response carries canary material (${leak.label})`);
94
+ return out;
95
+ }
96
+
97
+ // ------------------------------------------------------------------ the legs
98
+ // Each leg observes through the suite driver and returns findings or the reason it
99
+ // could not run, so the scenario and the self-test double run the same code.
100
+
101
+
102
+ export type Skip = { readonly kind: 'skip'; readonly skip: 'blocked' | 'inapplicable'; readonly reason: string };
103
+ export type LegOutcome = { readonly kind: 'observed'; readonly findings: string[] } | Skip;
104
+
105
+ const skip = (s: 'blocked' | 'inapplicable', reason: string): Skip => ({ kind: 'skip', skip: s, reason });
106
+ const NOT_INSTALLED = "the ui-plugin-pack-narrow fixture is not installed (404); installing it is the operator's choice (RFC 0238 §D)";
107
+
108
+ async function http(fn: () => Promise<OpenWOPResponse>): Promise<OpenWOPResponse | null> { try { return await fn(); } catch { return null; } }
109
+
110
+ async function gate(): Promise<{ isolation: string } | Skip> {
111
+ let doc: Record<string, unknown> | null = null;
112
+ try { doc = await v2Discovery(); } catch { doc = null; }
113
+ if (!doc) return skip('blocked', 'v2 discovery unreachable');
114
+ const fam = doc['uiPlugins'] as { served?: unknown; isolation?: unknown } | undefined;
115
+ if (!fam || fam.served !== true) return skip('inapplicable', 'the host does not advertise uiPlugins.served');
116
+ return { isolation: typeof fam.isolation === 'string' ? fam.isolation : 'cross-origin-iframe' };
117
+ }
118
+
119
+ async function frameCsp(): Promise<{ csp: string | null } | Skip> {
120
+ const r = await http(() => driver.get(framePath()));
121
+ if (r === null) return skip('blocked', 'GET …/frame could not be made');
122
+ if (r.status === 404) return skip('inapplicable', NOT_INSTALLED);
123
+ if (r.status !== 200) return skip('blocked', `GET …/frame answered ${r.status}`);
124
+ return { csp: r.headers.get('content-security-policy') };
125
+ }
126
+
127
+ async function rpc(id: number, method: string, params?: Record<string, unknown>): Promise<RpcReply | null> {
128
+ const r = await http(() => driver.post(rpcPath(), { message: request(id, method, params) }));
129
+ return r === null ? null : { status: r.status, json: r.json, text: r.text };
130
+ }
131
+
132
+ /** §B.3, `openwop.requirement.0238.ui-plugin.frame-isolated`. */
133
+ export async function isolationLeg(): Promise<LegOutcome> {
134
+ const g = await gate();
135
+ if ('kind' in g) return g;
136
+ if (g.isolation !== 'cross-origin-iframe') return skip('inapplicable', `uiPlugins.isolation is ${g.isolation}: only a cross-origin-iframe boundary is visible over HTTP (RFC 0238 §Decisions 3)`);
137
+ const f = await frameCsp();
138
+ return 'kind' in f ? f : { kind: 'observed', findings: judgeIsolation(f.csp) };
139
+ }
140
+
141
+ /** §B.4, `openwop.requirement.0238.ui-plugin.frame-deny-egress`. */
142
+ export async function egressLeg(): Promise<LegOutcome> {
143
+ const g = await gate();
144
+ if ('kind' in g) return g;
145
+ const f = await frameCsp();
146
+ return 'kind' in f ? f : { kind: 'observed', findings: judgeEgress(f.csp) };
147
+ }
148
+
149
+ /** §C.2/§C.4, `openwop.requirement.0238.ui-plugin.rpc-allowlist`. */
150
+ export async function allowlistLeg(): Promise<LegOutcome> {
151
+ const g = await gate();
152
+ if ('kind' in g) return g;
153
+ const control = await rpc(1, 'artifact.read', { artifactId: FIXTURE_ARTIFACT });
154
+ if (control === null) return skip('blocked', 'POST …/rpc could not be made');
155
+ if (control.status === 404) return skip('inapplicable', NOT_INSTALLED);
156
+ const findings = [...judgeControl(control)];
157
+ for (const [id, method, params] of [[2, 'artifact.write', { artifactId: FIXTURE_ARTIFACT, content: 'openwop-conformance' }], [3, 'host.navigate', { to: '/' }]] as const) {
158
+ const r = await rpc(id, method, params);
159
+ if (r === null) return skip('blocked', `the ${method} dispatch could not be made`);
160
+ findings.push(...judgeRefused(method, r));
161
+ }
162
+ const exec = await rpc(4, 'host.exec');
163
+ if (exec === null) return skip('blocked', 'the host.exec dispatch could not be made');
164
+ findings.push(...judgeInvalid('host.exec', exec));
165
+ return { kind: 'observed', findings };
166
+ }
167
+
168
+ /** §C.6, `openwop.requirement.0238.ui-plugin.no-byok`. */
169
+ export async function noByokLeg(): Promise<LegOutcome> {
170
+ const g = await gate();
171
+ if ('kind' in g) return g;
172
+ const replies: RpcReply[] = [];
173
+ for (const [id, method] of [[1, 'artifact.read'], [2, 'artifact.write']] as const) {
174
+ const r = await rpc(id, method, { artifactId: FIXTURE_ARTIFACT });
175
+ if (r === null) return skip('blocked', `the ${method} dispatch could not be made`);
176
+ if (r.status === 404) return skip('inapplicable', NOT_INSTALLED);
177
+ replies.push(r);
178
+ }
179
+ return { kind: 'observed', findings: judgeNoCanary(replies) };
180
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The WASM memory ceiling (`spec/v2/core/node-pack-runtimes.md` §WASM; RFC 0008
3
+ * §K), observed through run creation and the event poll against the
4
+ * operator-installed `vendor.openwop.misbehaving` fixture pack
5
+ * (`conformance/fixtures/wasm-packs/misbehaving-memory/`), whose one node grows
6
+ * linear memory until the host refuses.
7
+ *
8
+ * `memoryCapLeg` observes and returns findings or the reason it could not run;
9
+ * `judgeMemoryCap` is pure. The scenario and the self-test double run the same code.
10
+ */
11
+
12
+ import { v2Discovery } from './v2.js';
13
+ import { majorProfile } from './major-profile.js';
14
+ import { observeRun, type ObservedRun } from './fixture-run-observer.js';
15
+
16
+ export const MEMORY_CAP_FIXTURE = 'conformance-wasm-pack-memory-cap-breach';
17
+ export const BOMB_TYPE_ID = 'vendor.openwop.misbehaving.memory-bomb';
18
+
19
+ export type Skip = { readonly kind: 'skip'; readonly skip: 'blocked' | 'inapplicable'; readonly reason: string };
20
+ export type LegOutcome = { readonly kind: 'observed'; readonly findings: string[] } | Skip;
21
+ const skip = (s: 'blocked' | 'inapplicable', reason: string): Skip => ({ kind: 'skip', skip: s, reason });
22
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
23
+
24
+ /** A breach emits `cap.breached` `kind: wasm-memory` and the run ends `failed`. */
25
+ export function judgeMemoryCap(o: ObservedRun): string[] {
26
+ const out: string[] = [];
27
+ if (o.createStatus !== 201 && o.createStatus !== 202) return [`createRun of ${MEMORY_CAP_FIXTURE} MUST be accepted (got ${o.createStatus})`];
28
+ const breaches = o.events.filter((e) => e.type === 'cap.breached' && e.payload['kind'] === 'wasm-memory');
29
+ if (breaches.length === 0) out.push(`a module exceeding maxMemoryBytes MUST produce cap.breached with kind wasm-memory; the log has ${o.events.filter((e) => e.type === 'cap.breached').map((e) => String(e.payload['kind'])).join(', ') || 'no cap.breached'}`);
30
+ if (o.terminalStatus !== 'failed') out.push(`the run MUST end failed after the breach (got ${String(o.terminalStatus)})`);
31
+ return out;
32
+ }
33
+
34
+ /** node-pack-runtimes.md §WASM bullet 2 — `openwop.requirement.node-pack-runtimes.wasm-memory-cap`. */
35
+ export async function memoryCapLeg(): Promise<LegOutcome> {
36
+ let doc: Record<string, unknown> | null = null;
37
+ try { doc = await v2Discovery(); } catch { doc = null; }
38
+ if (!doc) return skip('blocked', 'v2 discovery unreachable');
39
+ const fam = doc['nodePackRuntimes'];
40
+ const wasm = isRecord(fam) ? fam['wasm'] : undefined;
41
+ if (!isRecord(wasm)) return skip('inapplicable', 'the host does not advertise nodePackRuntimes.wasm');
42
+ if (typeof wasm['maxMemoryBytes'] !== 'number') return skip('inapplicable', 'the host advertises no nodePackRuntimes.wasm.maxMemoryBytes; the enforcement MUST binds only when it does');
43
+ const fixtures = Array.isArray(doc['fixtures']) ? (doc['fixtures'] as unknown[]) : [];
44
+ if (!fixtures.includes(MEMORY_CAP_FIXTURE)) return skip('inapplicable', `the host does not advertise ${MEMORY_CAP_FIXTURE}; the operator installs conformance/fixtures/wasm-packs/misbehaving-memory/ to run it`);
45
+ return { kind: 'observed', findings: judgeMemoryCap(await observeRun(majorProfile(2), MEMORY_CAP_FIXTURE, { timeoutMs: 30_000 })) };
46
+ }