@openwop/openwop-conformance 2.45.24 → 2.45.25

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.45.25] — 2026-10-07 — `v2-wasm-abi-reject`; the frontend-plugin schema legs leave host bundles
4
+
5
+ - **New `v2-wasm-abi-reject` (`openwop.requirement.node-pack-runtimes.wasm-abi-reject`).** Gated on `nodePackRuntimes.wasm` and the new operator-installed fixture `conformance-wasm-pack-abi-mismatch`, which invokes `vendor.openwop.misbehaving.abi-bomb` (the `misbehaving-abi` pack, ABI version 999). Loading is not a protocol operation, so the leg reads what a rejection at load prevents: the pack in an advertised `loadedPacks`, and the node completing. The run must be refused at creation (4xx) or end without that node completing; a 401/403/429/5xx is `blocked`. Proof: a 9-case self-test double (`lib/wasm-abi-reject-witness.test.ts`). No RFC was needed: both observations already exist.
6
+ - **No scenario change.** RFC 0238 goes `Accepted` on openwop-app's certified 2.45.23 cut; its gaps G1 and G3 close, which changes the packed `spec/v1/gaps.json`.
7
+ - **No scenario change.** RFC 0239 goes `Accepted` on the v2 reference host's certified 2.45.23 cut; its gap G1 closes, which changes the packed `spec/v1/gaps.json`.
8
+ - **No scenario change (RFC 0238 G3).** The 13 always-on schema legs of `frontend-plugin-packs` read only the corpus, so they move to `src/coherence/frontend-plugin-schemas.test.ts` and leave host bundles; their ids become `openwop.it.frontend-plugin-schemas.*`, which changes the packed `requirements.json`. The host-reading legs stay.
9
+
3
10
  ## [2.45.24] — 2026-10-07 — the effect receiver fails fast on a held pinned port; the README index check reads `spec/v2/core`
4
11
 
5
12
  - **The effect receiver fails fast on a held pinned port.** With `OPENWOP_WEBHOOK_RECEIVER_PORT` pinned to a port another process holds, `startEffectReceiver` attached no `error` handler, so `EADDRINUSE` never settled its `listen` promise and the `0158`/`0194` duplicate-delivery rows hung to their 180 s timeout instead of failing with the cause. It now rejects at once with the bind error, as `scoped-receiver` has since 2.39.3. Self-test: a held port rejects in under 2 s; without the handler the test times out. Reported by MyndHyve (2.45.18 cut, port 8787).
package/README.md CHANGED
@@ -137,7 +137,7 @@ Each scenario file records its disposition and assertion count in `<report-dir>/
137
137
 
138
138
  ## What's Covered
139
139
 
140
- The current suite has 649 scenario files under `src/scenarios/`. By target major ([`scenario-majors.json`](./scenario-majors.json)): 209 files run against a v2 host and 455 against a v1 host; 15 of them run at both.
140
+ The current suite has 650 scenario files under `src/scenarios/`. By target major ([`scenario-majors.json`](./scenario-majors.json)): 210 files run against a v2 host and 455 against a v1 host; 15 of them run at both.
141
141
 
142
142
  The v2 scenarios (`src/scenarios/v2-*.test.ts`) cover, by area:
143
143
 
@@ -154,7 +154,7 @@ The v2 scenarios (`src/scenarios/v2-*.test.ts`) cover, by area:
154
154
  | Security defaults | SSRF refusal, filesystem sandbox, cross-tenant isolation for storage, memory and queues, secret canaries | `v2-safefetch-ssrf-refused`, `v2-fs-sandbox-escape-refused`, `v2-storage-cross-tenant-isolation`, `v2-secret-canary-absent` |
155
155
  | Webhooks | Delivery shape, Standard Webhooks signing, durable retries, secret rotation, egress refusal, endpoint verification | `v2-webhook-delivery-shape`, `v2-webhook-durable-delivery`, `v2-webhook-secret-rotation`, `v2-webhook-egress-refusal` |
156
156
  | Durability and execution | Recovery after a kill, effect seams that never re-fire, execution bounds, budgets | `v2-durability-recovery`, `v2-effect-seam-no-refire`, `v2-run-execution-bounds`, `v2-budget-enforcement` |
157
- | Packs and composition | Pack isolation, peer dependencies, sub-workflow dispatch and input mapping, chain pins, WASM memory caps, registry lifecycle | `v2-pack-isolation`, `v2-subworkflow-linkage`, `v2-chain-pin-exact`, `v2-wasm-memory-cap` |
157
+ | Packs and composition | Pack isolation, peer dependencies, sub-workflow dispatch and input mapping, chain pins, WASM memory caps and ABI rejection, registry lifecycle | `v2-pack-isolation`, `v2-subworkflow-linkage`, `v2-chain-pin-exact`, `v2-wasm-memory-cap`, `v2-wasm-abi-reject` |
158
158
  | AI, prompts and agents | AI envelopes, providers and usage, prompt libraries and rendering, model capabilities, conversations, multi-party councils | `v2-prompt-render-deterministic`, `v2-provider-usage-emission`, `v2-conversation-turn-parts`, `v2-multi-party-council` |
159
159
  | Interop | A2A agent cards and operation map, MCP mounts and tasks, A2UI v0.9 surfaces, trace context | `v2-a2a-operation-map`, `v2-mcp-mount-map`, `v2-a2ui-v09-surface`, `v2-interop-trace-context` |
160
160
  | Family advertisements | Each optional capability family's discovery record validates against its schema seat | `v2-*-advertisement` |
@@ -166,7 +166,7 @@ Three kinds of scenario share the tree:
166
166
  - **Host** checks drive a live host over HTTP.
167
167
  - **Gated** checks run only when the host advertises the family, profile or fixture they need, and otherwise record `inapplicable` or `blocked` with a reason. `--require-behavior` turns an advertised-but-unwitnessable behaviour into a failure.
168
168
 
169
- Current source tree: 649 scenario files. [`coverage.md`](./coverage.md) maps each spec document and every OpenAPI operation to its scenarios. [`CHANGELOG.md`](./CHANGELOG.md) records when each scenario landed.
169
+ Current source tree: 650 scenario files. [`coverage.md`](./coverage.md) maps each spec document and every OpenAPI operation to its scenarios. [`CHANGELOG.md`](./CHANGELOG.md) records when each scenario landed.
170
170
 
171
171
  ## How the 2.x suite is built
172
172
 
package/coverage.md CHANGED
@@ -32,7 +32,7 @@ Each scenario that targets major 2 is listed once, under the [`spec/v2/core/`](.
32
32
  | [`identity.md`](../spec/v2/core/identity.md) | `auth-challenge-no-oracle.test.ts`, `v2-assurance-downgrade-audited.test.ts`, `v2-auth-challenge.test.ts`, `v2-authorization-advertisement.test.ts`, `v2-bound-id-kinds.test.ts`, `v2-bound-id-path-projection.test.ts`, `v2-id-grammar.test.ts`, `v2-interrupt-token-scheme.test.ts`, `v2-lane-exp-only-bound.test.ts`, `v2-lane-issuer-advertised.test.ts`, `v2-oidc-id-token-audience.test.ts`, `v2-owner-subject-required.test.ts`, `v2-protected-resource-metadata.test.ts`, `v2-revocation-honored.test.ts`, `v2-subject-link-record.test.ts` |
33
33
  | [`interop.md`](../spec/v2/core/interop.md) | `otel-mcp-semconv-projection.test.ts`, `v2-a2a-agent-cards.test.ts`, `v2-a2a-client-error-details.test.ts`, `v2-a2a-operation-map.test.ts`, `v2-a2a-push-delivery.test.ts`, `v2-interop-trace-context.test.ts`, `v2-legacy-profiles-absent.test.ts`, `v2-mcp-mount-map.test.ts`, `v2-mcp-tasks.test.ts`, `v2-minimum-version-refused.test.ts`, `v2-mrtr-rounds-ceiling.test.ts`, `v2-negotiation-authenticated.test.ts`, `v2-negotiation-decided-emitted.test.ts`, `v2-no-transport-advertisement.test.ts`, `v2-refresh-sla.test.ts` |
34
34
  | [`interrupt.md`](../spec/v2/core/interrupt.md) | `v2-approval-reject-disposition.test.ts`, `v2-approver-enforced.test.ts`, `v2-callback-url-guarded.test.ts` |
35
- | [`node-pack-runtimes.md`](../spec/v2/core/node-pack-runtimes.md) | `v2-node-pack-runtimes-advertisement.test.ts`, `v2-wasm-memory-cap.test.ts` |
35
+ | [`node-pack-runtimes.md`](../spec/v2/core/node-pack-runtimes.md) | `v2-node-pack-runtimes-advertisement.test.ts`, `v2-wasm-memory-cap.test.ts`, `v2-wasm-abi-reject.test.ts` |
36
36
  | [`oauth.md`](../spec/v2/core/oauth.md) | `v2-credential-interrupt.test.ts`, `v2-credentials-advertisement.test.ts`, `v2-oauth-client-pkce-state-iss.test.ts`, `v2-oauth-mcp-reach-discovery.test.ts` |
37
37
  | [`packs.md`](../spec/v2/core/packs.md) | `v2-manifest-ceiling-refused.test.ts`, `v2-manifest-hatch-carried.test.ts`, `v2-peer-dependency-declared.test.ts`, `v2-registry-lifecycle.test.ts`, `v2-ui-plugin-boundary.test.ts`, `v2-ui-plugins-advertisement.test.ts` |
38
38
  | [`persistence.md`](../spec/v2/core/persistence.md) | `v2-era-2-append-vocabulary.test.ts`, `v2-era-key.test.ts`, `v2-era-stamp-universal.test.ts`, `v2-pinned-run-disposition.test.ts`, `v2-unmapped-type-refused.test.ts`, `v2-v1-events-translated.test.ts` |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.45.24",
4
- "stampSha256": "eddfc3a323cff5ccd6a745bd97319f63e89d8982678d824bfe5af5fafebbd7de"
3
+ "version": "2.45.25",
4
+ "stampSha256": "071daf4502be07fdcaa748880b10f91bcbddaee45417cfdcd43d8c53b15e1e88"
5
5
  }
@@ -0,0 +1,41 @@
1
+ {
2
+ "id": "conformance-wasm-pack-abi-mismatch",
3
+ "name": "Conformance: WASM Pack ABI Mismatch (Refused at Load)",
4
+ "version": "1.0",
5
+ "description": "node-pack-runtimes.md §WASM fixture. Invokes `vendor.openwop.misbehaving.abi-bomb`, whose module reports `openwop_abi_version()` 999, which no host lists. A host advertising this fixture says its operator offered `conformance/fixtures/wasm-packs/misbehaving-abi/` to its loader; a conforming host rejected the pack at load, so `nodePackRuntimes.wasm.loadedPacks` (when advertised) omits it, and the run is refused at creation or ends without the node completing. Fixture-only; not signed for the registry.",
6
+ "nodes": [
7
+ {
8
+ "id": "abi",
9
+ "typeId": "vendor.openwop.misbehaving.abi-bomb",
10
+ "name": "ABI bomb",
11
+ "position": {
12
+ "x": 0,
13
+ "y": 0
14
+ },
15
+ "config": {},
16
+ "inputs": {}
17
+ }
18
+ ],
19
+ "edges": [],
20
+ "triggers": [
21
+ {
22
+ "id": "manual",
23
+ "type": "manual",
24
+ "enabled": true
25
+ }
26
+ ],
27
+ "variables": [],
28
+ "metadata": {
29
+ "tags": [
30
+ "conformance",
31
+ "wasm",
32
+ "node-pack",
33
+ "rfc-0008",
34
+ "abi-version",
35
+ "misbehaving"
36
+ ]
37
+ },
38
+ "settings": {
39
+ "timeout": 10000
40
+ }
41
+ }
package/fixtures.md CHANGED
@@ -126,6 +126,7 @@ All fixtures MUST advertise:
126
126
  | A2A Task Roundtrip | `conformance-a2a-task-roundtrip` | Track 6 — host consumes the conformance suite's synthetic A2A peer; covers drift points #3 (`AUTH_REQUIRED`) and #4 (`REJECTED`). Node `core.conformance.a2a-invoke` is a **conformance-RESERVED** typeId (renamed 2026-08-16 from `core.a2a.invoke`, a multi-segment `core.*` id no host shipped and the corpus never defined): a host that consumes A2A peers MUST map it to its A2A bridge node (a host-extension node — `a2a-integration.md` §"State projection"); a host that does not consume A2A MUST NOT advertise this fixture | `failed` or `waiting-input` (per `driftScenario` input) | ≤ 30s |
127
127
  | WASM Pack Roundtrip | `conformance-wasm-pack-roundtrip` | RFC 0008 — invokes `vendor.openwop.rust-hello.greet` (loaded WASM pack); exercises required exports + at least one import | `completed` | ≤ 10s |
128
128
  | WASM Pack Memory-Cap Breach | `conformance-wasm-pack-memory-cap-breach` | RFC 0008 §K — invokes the deliberately-misbehaving `vendor.openwop.misbehaving.memory-bomb` pack (allocates 1 GiB beyond the host's `memoryPagesMax`). Host MUST emit `cap.breached` with `kind: "wasm-memory"` and drive the run to terminal `failed`. Misbehaving pack lives at `openwop-examples:examples/packs/rust-misbehaving-memory/` (repo-qualified per the 2026-06 monorepo split — the `openwop-examples` sibling repo) and is fixture-only (NOT signed for registry publication). | `failed` (with `cap.breached`) | ≤ 10s |
129
+ | WASM Pack ABI Mismatch | `conformance-wasm-pack-abi-mismatch` | node-pack-runtimes.md §WASM — invokes `vendor.openwop.misbehaving.abi-bomb`, whose module reports `openwop_abi_version()` 999. Advertising the fixture says the operator offered `fixtures/wasm-packs/misbehaving-abi/` to the loader; a conforming host rejected it at load, so `loadedPacks` (when advertised) omits it and the node never runs. Fixture-only (NOT signed for registry publication). | refused at creation (4xx), or `failed` / `cancelled` without `node.completed` for `abi` | ≤ 10s |
129
130
  | Configurable Schema | `conformance-configurable-schema` | Track 13 — workflow declares `configurableSchema` (`additionalProperties: false`, `recursionLimit: integer ≥ 1`). Suite verifies `GET /workflows/{id}` surfaces the schema AND `POST /runs` with a mismatched `configurable` returns `validation_error`. | `completed` (with accepted overlay) | ≤ 5s |
130
131
  | Smoke — BYOK Roundtrip | `openwop-smoke-byok-roundtrip` | End-to-end BYOK secret-resolution smoke. Single `conformance.secret.echo` node fetches the host-provisioned canary secret `openwop-conformance-canary-secret`, emits SHA-256 hex + byte length to variables — never the raw value. Spec: `run-options.md` §"Credential references" + `auth.md` §"Secret resolution" + `observability.md` §"Redaction". | `completed` | ≤ 10s |
131
132
  | Secrets — Run Witness | `openwop-secrets-run-witness` | RFC 0229 §C — one `core.secret.witness` node. The client supplies a value with the run (`runSecrets`, a `run:` ref) and its SHA-256 as an input; the node outputs only `{ matched }`. It can confirm only a value the caller already holds, so it is safe to advertise in production. Advertised by every host that advertises `secrets.runSecrets`. | `completed` (`matched: true` or `false`); `failed` (`credential_forbidden` / `credential_not_found`) on the scope legs | ≤ 10s |
@@ -841,7 +842,7 @@ The `fixtures/wasm-packs/` sub-directory holds two node packs (`language: "wasm"
841
842
  | Pack | Node | Purpose |
842
843
  | --- | --- | --- |
843
844
  | `misbehaving-memory` (`vendor.openwop.misbehaving`) | `vendor.openwop.misbehaving.memory-bomb` | Grows linear memory 64 MiB at a time until the host refuses, then traps. A host advertising `nodePackRuntimes.wasm.maxMemoryBytes` MUST emit `cap.breached` `kind: "wasm-memory"`. Drives `conformance-wasm-pack-memory-cap-breach` and `v2-wasm-memory-cap`. |
844
- | `misbehaving-abi` (`vendor.openwop.misbehaving-abi`) | `vendor.openwop.misbehaving.abi-bomb` | Reports `openwop_abi_version` 999, which no host lists, so a conforming host refuses it at load. Rejection at load is not yet observable over the protocol (TODO §8), so no v2 leg reads it. |
845
+ | `misbehaving-abi` (`vendor.openwop.misbehaving-abi`) | `vendor.openwop.misbehaving.abi-bomb` | Reports `openwop_abi_version` 999, which no host lists, so a conforming host refuses it at load. Drives `conformance-wasm-pack-abi-mismatch` and `v2-wasm-abi-reject`, which read the refusal from what it prevents: the pack in `nodePackRuntimes.wasm.loadedPacks`, and its node completing. |
845
846
 
846
847
  ---
847
848
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.45.24",
3
+ "version": "2.45.25",
4
4
  "description": "Black-box conformance suite for OpenWOP hosts (v2 current; v1 scenarios retained for the frozen v1 tree).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,6 +56,6 @@
56
56
  "@openwop/spec-artifacts": "file:../spec-artifacts"
57
57
  },
58
58
  "peerDependencies": {
59
- "@openwop/spec-artifacts": "2.45.24"
59
+ "@openwop/spec-artifacts": "2.45.25"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "$comment": "GENERATED by conformance/scripts/generate-requirement-registry.mjs — do not edit. One record per it()/test() in src/scenarios. Ids: openwop.it.<file-stem>.<title-slug>[~n] (src/lib/requirement-ids.ts). A record with id null has an interpolated title; its run-time row is keyed by the rendered title and maps here by file+line only. Renamed ids need a row in requirement-aliases.json.",
3
3
  "generatedFrom": "src/scenarios/*.test.ts",
4
4
  "counts": {
5
- "files": 735,
6
- "tests": 2780,
7
- "withStableId": 2780,
5
+ "files": 737,
6
+ "tests": 2781,
7
+ "withStableId": 2781,
8
8
  "interpolatedTitles": 0,
9
- "explicitIds": 2643
9
+ "explicitIds": 2644
10
10
  },
11
11
  "records": [
12
12
  {
@@ -13928,184 +13928,10 @@
13928
13928
  }
13929
13929
  ]
13930
13930
  },
13931
- {
13932
- "id": "openwop.it.frontend-plugin-packs.a-well-formed-frontend-plugin-manifest-validates",
13933
- "file": "frontend-plugin-packs.test.ts",
13934
- "line": 70,
13935
- "title": "a well-formed frontend-plugin manifest validates",
13936
- "explicitId": "openwop.it.frontend-plugin-packs.a-well-formed-frontend-plugin-manifest-validates",
13937
- "citations": [
13938
- {
13939
- "section": "frontend-plugin-packs.md",
13940
- "requirement": null,
13941
- "interpolated": true
13942
- }
13943
- ]
13944
- },
13945
- {
13946
- "id": "openwop.it.frontend-plugin-packs.a-backend-runtime-member-is-rejected-a-plugin-is-sandboxed-ui-not-a-node-entry",
13947
- "file": "frontend-plugin-packs.test.ts",
13948
- "line": 77,
13949
- "title": "a backend `runtime` member is rejected (a plugin is sandboxed UI, not a node entry)",
13950
- "explicitId": "openwop.it.frontend-plugin-packs.a-backend-runtime-member-is-rejected-a-plugin-is-sandboxed-ui-not-a-node-entry",
13951
- "citations": [
13952
- {
13953
- "section": "frontend-plugin-packs.md",
13954
- "requirement": "node-packs.md §Pack kinds — a kind:\"frontend-plugin\" manifest carrying `runtime` MUST be rejected (pack_kind_invalid)"
13955
- }
13956
- ]
13957
- },
13958
- {
13959
- "id": "openwop.it.frontend-plugin-packs.a-uiplugins-entry-missing-entry-is-rejected",
13960
- "file": "frontend-plugin-packs.test.ts",
13961
- "line": 85,
13962
- "title": "a uiPlugins[] entry missing `entry` is rejected",
13963
- "explicitId": "openwop.it.frontend-plugin-packs.a-uiplugins-entry-missing-entry-is-rejected",
13964
- "citations": [
13965
- {
13966
- "section": "frontend-plugin-packs.md",
13967
- "requirement": "a uiPlugins[] entry missing `entry` MUST NOT validate"
13968
- }
13969
- ]
13970
- },
13971
- {
13972
- "id": "openwop.it.frontend-plugin-packs.an-entry-path-with-traversal-is-rejected",
13973
- "file": "frontend-plugin-packs.test.ts",
13974
- "line": 91,
13975
- "title": "an `entry` path with `..` traversal is rejected",
13976
- "explicitId": "openwop.it.frontend-plugin-packs.an-entry-path-with-traversal-is-rejected",
13977
- "citations": [
13978
- {
13979
- "section": "frontend-plugin-packs.md",
13980
- "requirement": "an `entry` path MUST NOT contain `..` (path-traversal)"
13981
- }
13982
- ]
13983
- },
13984
- {
13985
- "id": "openwop.it.frontend-plugin-packs.a-hostapi-method-outside-the-closed-allowlist-is-rejected-frontend-plugin-rpc-al",
13986
- "file": "frontend-plugin-packs.test.ts",
13987
- "line": 97,
13988
- "title": "a hostApi method outside the closed allowlist is rejected (frontend-plugin-rpc-allowlist)",
13989
- "explicitId": "openwop.it.frontend-plugin-packs.a-hostapi-method-outside-the-closed-allowlist-is-rejected-frontend-plugin-rpc-al",
13990
- "citations": [
13991
- {
13992
- "section": "frontend-plugin-packs.md",
13993
- "requirement": "frontend-plugin-packs.md §Host-RPC — only the closed allowlist methods are permitted; `host.exec` MUST NOT validate"
13994
- }
13995
- ]
13996
- },
13997
- {
13998
- "id": "openwop.it.frontend-plugin-packs.an-empty-uiplugins-is-rejected-a-pack-must-declare-at-least-one-plugin",
13999
- "file": "frontend-plugin-packs.test.ts",
14000
- "line": 106,
14001
- "title": "an empty uiPlugins[] is rejected (a pack MUST declare at least one plugin)",
14002
- "explicitId": "openwop.it.frontend-plugin-packs.an-empty-uiplugins-is-rejected-a-pack-must-declare-at-least-one-plugin",
14003
- "citations": [
14004
- {
14005
- "section": "frontend-plugin-packs.md",
14006
- "requirement": "a frontend-plugin pack MUST declare at least one uiPlugins[] entry"
14007
- }
14008
- ]
14009
- },
14010
- {
14011
- "id": "openwop.it.frontend-plugin-packs.a-canvas-preview-entry-with-canvastypes-host-announce-validates-rfc-0130",
14012
- "file": "frontend-plugin-packs.test.ts",
14013
- "line": 111,
14014
- "title": "a canvas-preview entry with canvasTypes + host.announce validates (RFC 0130)",
14015
- "explicitId": "openwop.it.frontend-plugin-packs.a-canvas-preview-entry-with-canvastypes-host-announce-validates-rfc-0130",
14016
- "citations": [
14017
- {
14018
- "section": "RFC 0130",
14019
- "requirement": null,
14020
- "interpolated": true
14021
- }
14022
- ]
14023
- },
14024
- {
14025
- "id": "openwop.it.frontend-plugin-packs.a-surface-outside-the-closed-set-is-still-rejected-rfc-0130-keeps-the-enum-close",
14026
- "file": "frontend-plugin-packs.test.ts",
14027
- "line": 126,
14028
- "title": "a surface outside the closed set is still rejected (RFC 0130 keeps the enum closed)",
14029
- "explicitId": "openwop.it.frontend-plugin-packs.a-surface-outside-the-closed-set-is-still-rejected-rfc-0130-keeps-the-enum-close",
14030
- "citations": [
14031
- {
14032
- "section": "RFC 0130",
14033
- "requirement": "frontend-plugin-packs.md §The pack — the surface enum stays closed; an unknown surface MUST NOT validate"
14034
- }
14035
- ]
14036
- },
14037
- {
14038
- "id": "openwop.it.frontend-plugin-packs.a-valid-artifact-write-request-carrying-a-version-token-validates",
14039
- "file": "frontend-plugin-packs.test.ts",
14040
- "line": 141,
14041
- "title": "a valid artifact.write request carrying a version token validates",
14042
- "explicitId": "openwop.it.frontend-plugin-packs.a-valid-artifact-write-request-carrying-a-version-token-validates",
14043
- "citations": [
14044
- {
14045
- "section": "frontend-plugin-packs.md",
14046
- "requirement": null,
14047
- "interpolated": true
14048
- }
14049
- ]
14050
- },
14051
- {
14052
- "id": "openwop.it.frontend-plugin-packs.an-artifact-conflict-response-carries-currentversion-version-token-concurrency",
14053
- "file": "frontend-plugin-packs.test.ts",
14054
- "line": 152,
14055
- "title": "an artifact_conflict response carries currentVersion (version-token concurrency)",
14056
- "explicitId": "openwop.it.frontend-plugin-packs.an-artifact-conflict-response-carries-currentversion-version-token-concurrency",
14057
- "citations": [
14058
- {
14059
- "section": "frontend-plugin-packs.md",
14060
- "requirement": null,
14061
- "interpolated": true
14062
- }
14063
- ]
14064
- },
14065
- {
14066
- "id": "openwop.it.frontend-plugin-packs.a-request-with-a-method-outside-the-allowlist-is-schema-rejected",
14067
- "file": "frontend-plugin-packs.test.ts",
14068
- "line": 166,
14069
- "title": "a request with a method outside the allowlist is schema-rejected",
14070
- "explicitId": "openwop.it.frontend-plugin-packs.a-request-with-a-method-outside-the-allowlist-is-schema-rejected",
14071
- "citations": [
14072
- {
14073
- "section": "frontend-plugin-packs.md",
14074
- "requirement": "a method outside the ui-plugin/1 allowlist MUST NOT validate"
14075
- }
14076
- ]
14077
- },
14078
- {
14079
- "id": "openwop.it.frontend-plugin-packs.a-message-without-the-ui-plugin-1-protocol-tag-is-rejected",
14080
- "file": "frontend-plugin-packs.test.ts",
14081
- "line": 171,
14082
- "title": "a message without the ui-plugin/1 protocol tag is rejected",
14083
- "explicitId": "openwop.it.frontend-plugin-packs.a-message-without-the-ui-plugin-1-protocol-tag-is-rejected",
14084
- "citations": [
14085
- {
14086
- "section": "frontend-plugin-packs.md",
14087
- "requirement": "a host MUST ignore messages whose ui-plugin tag it does not recognize"
14088
- }
14089
- ]
14090
- },
14091
- {
14092
- "id": "openwop.it.frontend-plugin-packs.no-credential-bearing-field-is-admitted-on-the-envelope-frontend-plugin-no-byok",
14093
- "file": "frontend-plugin-packs.test.ts",
14094
- "line": 176,
14095
- "title": "no credential-bearing field is admitted on the envelope (frontend-plugin-no-byok)",
14096
- "explicitId": "openwop.it.frontend-plugin-packs.no-credential-bearing-field-is-admitted-on-the-envelope-frontend-plugin-no-byok",
14097
- "citations": [
14098
- {
14099
- "section": "frontend-plugin-packs.md",
14100
- "requirement": null,
14101
- "interpolated": true
14102
- }
14103
- ]
14104
- },
14105
13931
  {
14106
13932
  "id": "openwop.it.frontend-plugin-packs.a-host-advertising-uiplugins-must-advertise-a-conformant-isolation-model",
14107
13933
  "file": "frontend-plugin-packs.test.ts",
14108
- "line": 197,
13934
+ "line": 40,
14109
13935
  "title": "a host advertising uiPlugins MUST advertise a conformant isolation model",
14110
13936
  "explicitId": "openwop.it.frontend-plugin-packs.a-host-advertising-uiplugins-must-advertise-a-conformant-isolation-model",
14111
13937
  "citations": [
@@ -14118,7 +13944,7 @@
14118
13944
  {
14119
13945
  "id": "openwop.it.frontend-plugin-packs.an-undeclared-host-rpc-method-is-refused-with-method-not-allowed",
14120
13946
  "file": "frontend-plugin-packs.test.ts",
14121
- "line": 212,
13947
+ "line": 55,
14122
13948
  "title": "an undeclared host-RPC method is refused with method_not_allowed",
14123
13949
  "explicitId": "openwop.it.frontend-plugin-packs.an-undeclared-host-rpc-method-is-refused-with-method-not-allowed",
14124
13950
  "citations": [
@@ -14135,7 +13961,7 @@
14135
13961
  {
14136
13962
  "id": "openwop.it.frontend-plugin-packs.a-stale-artifact-write-is-refused-with-artifact-conflict-currentversion-no-persi",
14137
13963
  "file": "frontend-plugin-packs.test.ts",
14138
- "line": 235,
13964
+ "line": 78,
14139
13965
  "title": "a stale artifact.write is refused with artifact_conflict + currentVersion (no persist)",
14140
13966
  "explicitId": "openwop.it.frontend-plugin-packs.a-stale-artifact-write-is-refused-with-artifact-conflict-currentversion-no-persi",
14141
13967
  "citations": [
@@ -38101,6 +37927,19 @@
38101
37927
  }
38102
37928
  ]
38103
37929
  },
37930
+ {
37931
+ "id": "openwop.it.v2-wasm-abi-reject.a-pack-whose-openwop-abi-version-is-not-listed-is-not-loaded-and-its-node-never",
37932
+ "file": "v2-wasm-abi-reject.test.ts",
37933
+ "line": 30,
37934
+ "title": "a pack whose openwop_abi_version() is not listed is not loaded and its node never runs",
37935
+ "explicitId": "openwop.requirement.node-pack-runtimes.wasm-abi-reject",
37936
+ "citations": [
37937
+ {
37938
+ "section": "spec/v2/core/node-pack-runtimes.md §WASM",
37939
+ "requirement": "a host MUST reject at load a pack whose openwop_abi_version() is not listed"
37940
+ }
37941
+ ]
37942
+ },
38104
37943
  {
38105
37944
  "id": "openwop.it.v2-wasm-memory-cap.a-module-exceeding-maxmemorybytes-produces-cap-breached-wasm-memory-and-fails-th",
38106
37945
  "file": "v2-wasm-memory-cap.test.ts",
@@ -43444,6 +43283,180 @@
43444
43283
  }
43445
43284
  ]
43446
43285
  },
43286
+ {
43287
+ "id": "openwop.it.frontend-plugin-schemas.a-well-formed-frontend-plugin-manifest-validates",
43288
+ "file": "frontend-plugin-schemas.test.ts",
43289
+ "line": 63,
43290
+ "title": "a well-formed frontend-plugin manifest validates",
43291
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-well-formed-frontend-plugin-manifest-validates",
43292
+ "citations": [
43293
+ {
43294
+ "section": "frontend-plugin-packs.md",
43295
+ "requirement": null,
43296
+ "interpolated": true
43297
+ }
43298
+ ]
43299
+ },
43300
+ {
43301
+ "id": "openwop.it.frontend-plugin-schemas.a-backend-runtime-member-is-rejected-a-plugin-is-sandboxed-ui-not-a-node-entry",
43302
+ "file": "frontend-plugin-schemas.test.ts",
43303
+ "line": 71,
43304
+ "title": "a backend `runtime` member is rejected (a plugin is sandboxed UI, not a node entry)",
43305
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-backend-runtime-member-is-rejected-a-plugin-is-sandboxed-ui-not-a-node-entry",
43306
+ "citations": [
43307
+ {
43308
+ "section": "frontend-plugin-packs.md",
43309
+ "requirement": "node-packs.md §Pack kinds — a kind:\"frontend-plugin\" manifest carrying `runtime` MUST be rejected (pack_kind_invalid)"
43310
+ }
43311
+ ]
43312
+ },
43313
+ {
43314
+ "id": "openwop.it.frontend-plugin-schemas.a-uiplugins-entry-missing-entry-is-rejected",
43315
+ "file": "frontend-plugin-schemas.test.ts",
43316
+ "line": 80,
43317
+ "title": "a uiPlugins[] entry missing `entry` is rejected",
43318
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-uiplugins-entry-missing-entry-is-rejected",
43319
+ "citations": [
43320
+ {
43321
+ "section": "frontend-plugin-packs.md",
43322
+ "requirement": "a uiPlugins[] entry missing `entry` MUST NOT validate"
43323
+ }
43324
+ ]
43325
+ },
43326
+ {
43327
+ "id": "openwop.it.frontend-plugin-schemas.an-entry-path-with-traversal-is-rejected",
43328
+ "file": "frontend-plugin-schemas.test.ts",
43329
+ "line": 87,
43330
+ "title": "an `entry` path with `..` traversal is rejected",
43331
+ "explicitId": "openwop.it.frontend-plugin-schemas.an-entry-path-with-traversal-is-rejected",
43332
+ "citations": [
43333
+ {
43334
+ "section": "frontend-plugin-packs.md",
43335
+ "requirement": "an `entry` path MUST NOT contain `..` (path-traversal)"
43336
+ }
43337
+ ]
43338
+ },
43339
+ {
43340
+ "id": "openwop.it.frontend-plugin-schemas.a-hostapi-method-outside-the-closed-allowlist-is-rejected-frontend-plugin-rpc-al",
43341
+ "file": "frontend-plugin-schemas.test.ts",
43342
+ "line": 94,
43343
+ "title": "a hostApi method outside the closed allowlist is rejected (frontend-plugin-rpc-allowlist)",
43344
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-hostapi-method-outside-the-closed-allowlist-is-rejected-frontend-plugin-rpc-al",
43345
+ "citations": [
43346
+ {
43347
+ "section": "frontend-plugin-packs.md",
43348
+ "requirement": "frontend-plugin-packs.md §Host-RPC — only the closed allowlist methods are permitted; `host.exec` MUST NOT validate"
43349
+ }
43350
+ ]
43351
+ },
43352
+ {
43353
+ "id": "openwop.it.frontend-plugin-schemas.an-empty-uiplugins-is-rejected-a-pack-must-declare-at-least-one-plugin",
43354
+ "file": "frontend-plugin-schemas.test.ts",
43355
+ "line": 104,
43356
+ "title": "an empty uiPlugins[] is rejected (a pack MUST declare at least one plugin)",
43357
+ "explicitId": "openwop.it.frontend-plugin-schemas.an-empty-uiplugins-is-rejected-a-pack-must-declare-at-least-one-plugin",
43358
+ "citations": [
43359
+ {
43360
+ "section": "frontend-plugin-packs.md",
43361
+ "requirement": "a frontend-plugin pack MUST declare at least one uiPlugins[] entry"
43362
+ }
43363
+ ]
43364
+ },
43365
+ {
43366
+ "id": "openwop.it.frontend-plugin-schemas.a-canvas-preview-entry-with-canvastypes-host-announce-validates-rfc-0130",
43367
+ "file": "frontend-plugin-schemas.test.ts",
43368
+ "line": 110,
43369
+ "title": "a canvas-preview entry with canvasTypes + host.announce validates (RFC 0130)",
43370
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-canvas-preview-entry-with-canvastypes-host-announce-validates-rfc-0130",
43371
+ "citations": [
43372
+ {
43373
+ "section": "RFC 0130",
43374
+ "requirement": null,
43375
+ "interpolated": true
43376
+ }
43377
+ ]
43378
+ },
43379
+ {
43380
+ "id": "openwop.it.frontend-plugin-schemas.a-surface-outside-the-closed-set-is-still-rejected-rfc-0130-keeps-the-enum-close",
43381
+ "file": "frontend-plugin-schemas.test.ts",
43382
+ "line": 126,
43383
+ "title": "a surface outside the closed set is still rejected (RFC 0130 keeps the enum closed)",
43384
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-surface-outside-the-closed-set-is-still-rejected-rfc-0130-keeps-the-enum-close",
43385
+ "citations": [
43386
+ {
43387
+ "section": "RFC 0130",
43388
+ "requirement": "frontend-plugin-packs.md §The pack — the surface enum stays closed; an unknown surface MUST NOT validate"
43389
+ }
43390
+ ]
43391
+ },
43392
+ {
43393
+ "id": "openwop.it.frontend-plugin-schemas.a-valid-artifact-write-request-carrying-a-version-token-validates",
43394
+ "file": "frontend-plugin-schemas.test.ts",
43395
+ "line": 140,
43396
+ "title": "a valid artifact.write request carrying a version token validates",
43397
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-valid-artifact-write-request-carrying-a-version-token-validates",
43398
+ "citations": [
43399
+ {
43400
+ "section": "frontend-plugin-packs.md",
43401
+ "requirement": null,
43402
+ "interpolated": true
43403
+ }
43404
+ ]
43405
+ },
43406
+ {
43407
+ "id": "openwop.it.frontend-plugin-schemas.an-artifact-conflict-response-carries-currentversion-version-token-concurrency",
43408
+ "file": "frontend-plugin-schemas.test.ts",
43409
+ "line": 152,
43410
+ "title": "an artifact_conflict response carries currentVersion (version-token concurrency)",
43411
+ "explicitId": "openwop.it.frontend-plugin-schemas.an-artifact-conflict-response-carries-currentversion-version-token-concurrency",
43412
+ "citations": [
43413
+ {
43414
+ "section": "frontend-plugin-packs.md",
43415
+ "requirement": null,
43416
+ "interpolated": true
43417
+ }
43418
+ ]
43419
+ },
43420
+ {
43421
+ "id": "openwop.it.frontend-plugin-schemas.a-request-with-a-method-outside-the-allowlist-is-schema-rejected",
43422
+ "file": "frontend-plugin-schemas.test.ts",
43423
+ "line": 167,
43424
+ "title": "a request with a method outside the allowlist is schema-rejected",
43425
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-request-with-a-method-outside-the-allowlist-is-schema-rejected",
43426
+ "citations": [
43427
+ {
43428
+ "section": "frontend-plugin-packs.md",
43429
+ "requirement": "a method outside the ui-plugin/1 allowlist MUST NOT validate"
43430
+ }
43431
+ ]
43432
+ },
43433
+ {
43434
+ "id": "openwop.it.frontend-plugin-schemas.a-message-without-the-ui-plugin-1-protocol-tag-is-rejected",
43435
+ "file": "frontend-plugin-schemas.test.ts",
43436
+ "line": 173,
43437
+ "title": "a message without the ui-plugin/1 protocol tag is rejected",
43438
+ "explicitId": "openwop.it.frontend-plugin-schemas.a-message-without-the-ui-plugin-1-protocol-tag-is-rejected",
43439
+ "citations": [
43440
+ {
43441
+ "section": "frontend-plugin-packs.md",
43442
+ "requirement": "a host MUST ignore messages whose ui-plugin tag it does not recognize"
43443
+ }
43444
+ ]
43445
+ },
43446
+ {
43447
+ "id": "openwop.it.frontend-plugin-schemas.no-credential-bearing-field-is-admitted-on-the-envelope-frontend-plugin-no-byok",
43448
+ "file": "frontend-plugin-schemas.test.ts",
43449
+ "line": 179,
43450
+ "title": "no credential-bearing field is admitted on the envelope (frontend-plugin-no-byok)",
43451
+ "explicitId": "openwop.it.frontend-plugin-schemas.no-credential-bearing-field-is-admitted-on-the-envelope-frontend-plugin-no-byok",
43452
+ "citations": [
43453
+ {
43454
+ "section": "frontend-plugin-packs.md",
43455
+ "requirement": null,
43456
+ "interpolated": true
43457
+ }
43458
+ ]
43459
+ },
43447
43460
  {
43448
43461
  "id": "openwop.it.locale-key-grammar.extended-bcp-47-keys-validate-at-every-locale-site-v1-and-v2",
43449
43462
  "file": "locale-key-grammar.test.ts",
@@ -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": 649,
4
+ "files": 650,
5
5
  "v1": 455,
6
- "v2": 209
6
+ "v2": 210
7
7
  },
8
8
  "majors": {
9
9
  "a2a-1-0-agent-card.test.ts": [
@@ -1818,6 +1818,9 @@
1818
1818
  "v2-version-header-honored.test.ts": [
1819
1819
  2
1820
1820
  ],
1821
+ "v2-wasm-abi-reject.test.ts": [
1822
+ 2
1823
+ ],
1821
1824
  "v2-wasm-memory-cap.test.ts": [
1822
1825
  2
1823
1826
  ],
@@ -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.24",
5
- "corpusTag": "v2.45.24",
4
+ "version": "2.45.25",
5
+ "corpusTag": "v2.45.25",
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": "139cd5f7411c714ef1f207ee3284dc75633be3817d9946c1bdd998220fa79eec",
13
- "api/v2/asyncapi.yaml": "e9cc564d30c8ff03da39e2c1b64fa33f2f2e627dc6afebf2fddc4262e3b0a7c5",
14
- "api/v2/openapi.yaml": "15fb4ae5e02a5366b9bb60a3c9eb05f79695d1ae3669c0746bcae34cc04c77a2",
12
+ "api/seams-v2.yaml": "0692397ec226e29431dbb2f0d7e645d42ac76a3e4f851f1342136186e55ede78",
13
+ "api/v2/asyncapi.yaml": "416a78c622b4b2bad33cc73d4f2676e53685d1ee99ab6576338dd426a5a0e0fb",
14
+ "api/v2/openapi.yaml": "907a0bbf13669b0db55effd35eae67bafc2df0a496f11de37d876f69cc7fbe98",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "276fc1d51eb08e14e9127b9fd27761d7046ee33c12000f3a64a801322db8bf58",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -118,7 +118,7 @@
118
118
  "schemas/v2/audit-verify-result.schema.json": "14af8e9283510f0a40e48a2059c929ee44e0fc9fa3cc66a95bfbe90b17c48297",
119
119
  "schemas/v2/budget-policy.schema.json": "1de68095db0beb5c7a4be629de26e79f62ebbe748a88d46f36e768edfd1f0d68",
120
120
  "schemas/v2/capabilities.schema.json": "d859a413ff463ec39acc32a9cb76f2e8c7419e45683d6b7f886861765a9c36c7",
121
- "schemas/v2/certification-bundle.schema.json": "ee3b10966458a10568a32b8a7eb4ab7bdb2f2d00da3ef6b698cd06462d52de40",
121
+ "schemas/v2/certification-bundle.schema.json": "93fcdfe54b1db168b702bfaf632b60c7a8a602be92c3fe48c81cf25408c2332b",
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",
@@ -208,14 +208,14 @@
208
208
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
209
209
  "spec/v1/alias-detectors.json": "22444c22bb88c568339ef512553f9fc38146e22b566d11ab8608f655d688d19c",
210
210
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
211
- "spec/v1/core-standard-manifest.json": "b1d32d221b43e1897cd65fc22bdac4384376fa720733be1a736dea31141eeee9",
211
+ "spec/v1/core-standard-manifest.json": "53a46c4dc0224f13f06c4aa6f07ac6aaa0db84407c4ef2127a9d9d5652255b9e",
212
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": "ab22f506255eef90fba09585776dce09faa803207807eccbb3ea1c1a6cc80480",
218
+ "spec/v1/gaps.json": "a97d03cce8f59be6f84c66eeeacc56e01e500c7a32845afc95f4d4c4d91b4bfd",
219
219
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
220
220
  "spec/v1/migrations.json": "4c50760be70cb644d02427bcaf5749606700edf4513f423903cf3b1d12f5c300",
221
221
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
@@ -313,9 +313,9 @@
313
313
  "spec/v2/path-manifest.json": "50fc251f296b06ec5fad35724f5c63b1a32e0e8fc222a85cd29b785cebdb9c98",
314
314
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
315
315
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
316
- "spec/v2/release.json": "013b562cc396f4c6f000ed977be781f6f3ace49f609ee5fae045ff57aacb53a1",
316
+ "spec/v2/release.json": "0fdc846e4aacfad6b0fe7a255002c4ad167bc41f14174ba6f2dcf2498cb540f3",
317
317
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
318
- "spec/v2/surface-baseline.json": "1bc7b1cbf33591b9e9f406d2ce2de08b306dc6986e365a29d20a78331bce7344"
318
+ "spec/v2/surface-baseline.json": "91233acd0e69f99afbe4319260298ede7bddc2bd665d30305ecd443bef65ab58"
319
319
  },
320
- "corpusCommit": "2941c09ee028be32e129b96dde8b269e2a098854"
320
+ "corpusCommit": "f204da96cb286ba47bed89d4f66d70bbfdcda5a4"
321
321
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The WASM ABI check (`spec/v2/core/node-pack-runtimes.md` §WASM: a host "MUST
3
+ * reject at load a pack whose `openwop_abi_version()` is not listed"; RFC 0008
4
+ * §H), observed through discovery and run creation against the
5
+ * operator-installed `vendor.openwop.misbehaving-abi` fixture pack
6
+ * (`conformance/fixtures/wasm-packs/misbehaving-abi/`), whose module reports
7
+ * ABI version 999.
8
+ *
9
+ * Loading is not a protocol operation, so the refusal is read from what it
10
+ * makes impossible. A host advertising `conformance-wasm-pack-abi-mismatch`
11
+ * says its operator offered the pack to its loader. Rejected at load, the pack
12
+ * is absent from `nodePackRuntimes.wasm.loadedPacks` (when advertised), and its
13
+ * one node never runs: the fixture's run is refused at creation or ends
14
+ * without that node completing.
15
+ *
16
+ * `abiRejectLeg` observes and returns findings or the reason it could not run;
17
+ * `judgeAbiReject` is pure. The scenario and the self-test double run the same code.
18
+ */
19
+
20
+ import { v2Discovery } from './v2.js';
21
+ import { majorProfile } from './major-profile.js';
22
+ import { completionsOf, observeRun, type ObservedRun } from './fixture-run-observer.js';
23
+
24
+ export const ABI_FIXTURE = 'conformance-wasm-pack-abi-mismatch';
25
+ export const ABI_PACK = 'vendor.openwop.misbehaving-abi';
26
+ export const ABI_NODE = 'abi';
27
+
28
+ export type Skip = { readonly kind: 'skip'; readonly skip: 'blocked' | 'inapplicable'; readonly reason: string };
29
+ export type LegOutcome = { readonly kind: 'observed'; readonly findings: string[] } | Skip;
30
+ const skip = (s: 'blocked' | 'inapplicable', reason: string): Skip => ({ kind: 'skip', skip: s, reason });
31
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
32
+ const TERMINAL = new Set(['completed', 'failed', 'cancelled']);
33
+
34
+ /** A create the host refused on its merits, not for want of access or by its own fault. */
35
+ const refusedAtCreate = (status: number): boolean => status >= 400 && status < 500 && status !== 401 && status !== 403 && status !== 429;
36
+
37
+ /** The pack is not loaded, and its node never completes. */
38
+ export function judgeAbiReject(loadedPacks: unknown, o: ObservedRun): string[] {
39
+ const out: string[] = [];
40
+ if (Array.isArray(loadedPacks) && loadedPacks.includes(ABI_PACK)) out.push(`nodePackRuntimes.wasm.loadedPacks MUST NOT list ${ABI_PACK}, whose openwop_abi_version() 999 no host lists; it does`);
41
+ if (o.createStatus === 201 || o.createStatus === 202) {
42
+ if (completionsOf(o.events, ABI_NODE).length > 0 || o.terminalStatus === 'completed') {
43
+ out.push(`the ${ABI_PACK} node ran (node.completed, run ${String(o.terminalStatus)}): a pack whose ABI version is not listed MUST be rejected at load, so its node cannot execute`);
44
+ } else if (o.terminalStatus === undefined || !TERMINAL.has(o.terminalStatus)) {
45
+ out.push(`the ${ABI_FIXTURE} run MUST be refused at creation or end without the node completing; it is still ${String(o.terminalStatus)}`);
46
+ }
47
+ } else if (!refusedAtCreate(o.createStatus)) {
48
+ out.push(`createRun of ${ABI_FIXTURE} answered ${o.createStatus}; a refusal of an unloaded node is a 4xx`);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** node-pack-runtimes.md §WASM bullet 1 — `openwop.requirement.node-pack-runtimes.wasm-abi-reject`. */
54
+ export async function abiRejectLeg(): Promise<LegOutcome> {
55
+ let doc: Record<string, unknown> | null = null;
56
+ try { doc = await v2Discovery(); } catch { doc = null; }
57
+ if (!doc) return skip('blocked', 'v2 discovery unreachable');
58
+ const fam = doc['nodePackRuntimes'];
59
+ const wasm = isRecord(fam) ? fam['wasm'] : undefined;
60
+ if (!isRecord(wasm)) return skip('inapplicable', 'the host does not advertise nodePackRuntimes.wasm');
61
+ const fixtures = Array.isArray(doc['fixtures']) ? (doc['fixtures'] as unknown[]) : [];
62
+ if (!fixtures.includes(ABI_FIXTURE)) return skip('inapplicable', `the host does not advertise ${ABI_FIXTURE}; the operator offers conformance/fixtures/wasm-packs/misbehaving-abi/ to the loader to run it`);
63
+ const o = await observeRun(majorProfile(2), ABI_FIXTURE, { timeoutMs: 30_000 });
64
+ if (o.createStatus === 401 || o.createStatus === 403 || o.createStatus === 429 || o.createStatus >= 500) return skip('blocked', `createRun of ${ABI_FIXTURE} answered ${o.createStatus}`);
65
+ return { kind: 'observed', findings: judgeAbiReject(wasm['loadedPacks'], o) };
66
+ }
@@ -5,25 +5,17 @@
5
5
  * plus the manifest shape and the `ui-plugin/1` host-RPC + version-token concurrency
6
6
  * contract.
7
7
  *
8
- * Two layers:
8
+ * The always-on schema layer (manifest and `ui-plugin/1` message shapes) reads
9
+ * only the corpus, so it lives in `src/coherence/frontend-plugin-schemas.test.ts`
10
+ * (RFC 0238 G3). What stays here reads a host:
9
11
  *
10
- * A. Always-on, server-free schema probe — `frontend-plugin-manifest.schema.json`
11
- * and `ui-plugin-message.schema.json` enforce the wire shape: a valid manifest /
12
- * message validates; a backend `runtime` member, a `uiPlugins[]` entry missing
13
- * `entry`, an out-of-allowlist `method`, and any envelope `additionalProperties`
14
- * are rejected. The `version`-token concurrency contract (the `artifact_conflict`
15
- * error code + `currentVersion`) is schema-pinned. No credential-bearing field is
16
- * admitted on the envelope (`frontend-plugin-no-byok`).
17
- *
18
- * B. Capability-gated behavioral leg — on a host advertising
19
- * `capabilities.uiPlugins.supported: true` that exposes the
20
- * `POST /v1/host/sample/ui-plugin/rpc` test seam, an undeclared-method request
21
- * MUST be refused with `method_not_allowed` (`frontend-plugin-rpc-allowlist`), and
22
- * a stale `artifact.write` MUST be refused with `artifact_conflict` + `currentVersion`
23
- * and MUST NOT persist (§Concurrency). Hosts without the seam soft-skip (404);
24
- * unadvertised hosts skip via the behavior gate. (No conformant host advertises
25
- * `uiPlugins` yet — these legs soft-skip until the openwop-app reference host (ADR
26
- * 0153) lands, the first witness that graduates the invariants to protocol.)
12
+ * - the `uiPlugins` isolation advertisement, on a host that advertises it; and
13
+ * - the capability-gated host-RPC legs over the v1 test seam
14
+ * `POST /v1/host/sample/ui-plugin/rpc`: an undeclared method is refused with
15
+ * `method_not_allowed` (`frontend-plugin-rpc-allowlist`), and a stale
16
+ * `artifact.write` is refused with `artifact_conflict` + `currentVersion` and
17
+ * does not persist. Hosts without the seam soft-skip (404). The v2 witness is
18
+ * `v2-ui-plugin-boundary` (RFC 0238).
27
19
  *
28
20
  * @see spec/v1/frontend-plugin-packs.md
29
21
  * @see SECURITY/invariants.yaml ids: frontend-plugin-{isolation,egress,rpc-allowlist,no-byok}
@@ -31,161 +23,12 @@
31
23
  */
32
24
 
33
25
  import { describe, it, expect } from 'vitest';
34
- import { readFileSync } from 'node:fs';
35
- import { join } from 'node:path';
36
- import Ajv2020 from 'ajv/dist/2020.js';
37
- import addFormats from 'ajv-formats';
38
- import { SCHEMAS_DIR } from '../lib/paths.js';
39
26
  import { driver } from '../lib/driver.js';
40
27
  import { behaviorGate } from '../lib/behavior-gate.js';
41
28
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
42
29
  import { req } from '../lib/requirement-ids.js';
43
30
  import { softSkip } from '../lib/soft-skip.js';
44
31
 
45
- const MANIFEST_SCHEMA = join(SCHEMAS_DIR, 'frontend-plugin-manifest.schema.json');
46
- const MESSAGE_SCHEMA = join(SCHEMAS_DIR, 'ui-plugin-message.schema.json');
47
-
48
- function validManifest(): Record<string, unknown> {
49
- return {
50
- name: 'vendor.acme.canvas-editor',
51
- version: '1.0.0',
52
- kind: 'frontend-plugin',
53
- engines: { openwop: '>=1.2.0' },
54
- uiPlugins: [
55
- {
56
- pluginId: 'app-builder',
57
- surface: 'artifact-viewer',
58
- entry: 'ui/app-builder.mjs',
59
- hostApi: ['artifact.read', 'artifact.write'],
60
- },
61
- ],
62
- };
63
- }
64
-
65
- describe('frontend-plugin manifest: schema layer (always-on, server-free)', () => {
66
- const ajv = new Ajv2020({ allErrors: true, strict: false });
67
- addFormats(ajv);
68
- const validate = ajv.compile(JSON.parse(readFileSync(MANIFEST_SCHEMA, 'utf8')));
69
-
70
- it('a well-formed frontend-plugin manifest validates', () => {
71
- expect(
72
- validate(validManifest()),
73
- req('openwop.it.frontend-plugin-packs.a-well-formed-frontend-plugin-manifest-validates', 'frontend-plugin-packs.md', `frontend-plugin-packs.md §The pack — a valid manifest MUST validate. Errors: ${JSON.stringify(validate.errors)}`),
74
- ).toBe(true);
75
- });
76
-
77
- it('a backend `runtime` member is rejected (a plugin is sandboxed UI, not a node entry)', () => {
78
- const m = { ...validManifest(), runtime: { language: 'javascript', entry: 'index.mjs' } };
79
- expect(
80
- validate(m),
81
- req('openwop.it.frontend-plugin-packs.a-backend-runtime-member-is-rejected-a-plugin-is-sandboxed-ui-not-a-node-entry', 'frontend-plugin-packs.md', 'node-packs.md §Pack kinds — a kind:"frontend-plugin" manifest carrying `runtime` MUST be rejected (pack_kind_invalid)'),
82
- ).toBe(false);
83
- });
84
-
85
- it('a uiPlugins[] entry missing `entry` is rejected', () => {
86
- const m = validManifest();
87
- delete (m.uiPlugins as Array<Record<string, unknown>>)[0].entry;
88
- expect(validate(m), req('openwop.it.frontend-plugin-packs.a-uiplugins-entry-missing-entry-is-rejected', 'frontend-plugin-packs.md', 'a uiPlugins[] entry missing `entry` MUST NOT validate')).toBe(false);
89
- });
90
-
91
- it('an `entry` path with `..` traversal is rejected', () => {
92
- const m = validManifest();
93
- (m.uiPlugins as Array<Record<string, unknown>>)[0].entry = '../escape.mjs';
94
- expect(validate(m), req('openwop.it.frontend-plugin-packs.an-entry-path-with-traversal-is-rejected', 'frontend-plugin-packs.md', 'an `entry` path MUST NOT contain `..` (path-traversal)')).toBe(false);
95
- });
96
-
97
- it('a hostApi method outside the closed allowlist is rejected (frontend-plugin-rpc-allowlist)', () => {
98
- const m = validManifest();
99
- (m.uiPlugins as Array<Record<string, unknown>>)[0].hostApi = ['artifact.read', 'host.exec'];
100
- expect(
101
- validate(m),
102
- req('openwop.it.frontend-plugin-packs.a-hostapi-method-outside-the-closed-allowlist-is-rejected-frontend-plugin-rpc-al', 'frontend-plugin-packs.md', 'frontend-plugin-packs.md §Host-RPC — only the closed allowlist methods are permitted; `host.exec` MUST NOT validate'),
103
- ).toBe(false);
104
- });
105
-
106
- it('an empty uiPlugins[] is rejected (a pack MUST declare at least one plugin)', () => {
107
- const m = { ...validManifest(), uiPlugins: [] };
108
- expect(validate(m), req('openwop.it.frontend-plugin-packs.an-empty-uiplugins-is-rejected-a-pack-must-declare-at-least-one-plugin', 'frontend-plugin-packs.md', 'a frontend-plugin pack MUST declare at least one uiPlugins[] entry')).toBe(false);
109
- });
110
-
111
- it('a canvas-preview entry with canvasTypes + host.announce validates (RFC 0130)', () => {
112
- const m = validManifest();
113
- (m.uiPlugins as Array<Record<string, unknown>>)[0] = {
114
- pluginId: 'gantt-preview',
115
- surface: 'canvas-preview',
116
- canvasTypes: ['canvas.gantt'],
117
- entry: 'ui/preview.html',
118
- hostApi: ['artifact.read', 'host.announce'],
119
- };
120
- expect(
121
- validate(m),
122
- req('openwop.it.frontend-plugin-packs.a-canvas-preview-entry-with-canvastypes-host-announce-validates-rfc-0130', 'RFC 0130', `frontend-plugin-packs.md §The pack (RFC 0130) — a canvas-preview entry MUST validate. Errors: ${JSON.stringify(validate.errors)}`),
123
- ).toBe(true);
124
- });
125
-
126
- it('a surface outside the closed set is still rejected (RFC 0130 keeps the enum closed)', () => {
127
- const m = validManifest();
128
- (m.uiPlugins as Array<Record<string, unknown>>)[0].surface = 'omni-panel';
129
- expect(
130
- validate(m),
131
- req('openwop.it.frontend-plugin-packs.a-surface-outside-the-closed-set-is-still-rejected-rfc-0130-keeps-the-enum-close', 'RFC 0130', 'frontend-plugin-packs.md §The pack — the surface enum stays closed; an unknown surface MUST NOT validate'),
132
- ).toBe(false);
133
- });
134
- });
135
-
136
- describe('ui-plugin/1 message: schema layer (always-on, server-free)', () => {
137
- const ajv = new Ajv2020({ allErrors: true, strict: false });
138
- addFormats(ajv);
139
- const validate = ajv.compile(JSON.parse(readFileSync(MESSAGE_SCHEMA, 'utf8')));
140
-
141
- it('a valid artifact.write request carrying a version token validates', () => {
142
- const reqBody = {
143
- openwop: 'ui-plugin/1',
144
- type: 'request',
145
- id: 7,
146
- method: 'artifact.write',
147
- params: { artifactId: 'a-1', version: 'opaque-v1', payload: {} },
148
- };
149
- expect(validate(reqBody), req('openwop.it.frontend-plugin-packs.a-valid-artifact-write-request-carrying-a-version-token-validates', 'frontend-plugin-packs.md', `a valid artifact.write request MUST validate. Errors: ${JSON.stringify(validate.errors)}`)).toBe(true);
150
- });
151
-
152
- it('an artifact_conflict response carries currentVersion (version-token concurrency)', () => {
153
- const res = {
154
- openwop: 'ui-plugin/1',
155
- type: 'response',
156
- id: 7,
157
- ok: false,
158
- error: { code: 'artifact_conflict', currentVersion: 'opaque-v2' },
159
- };
160
- expect(
161
- validate(res),
162
- req('openwop.it.frontend-plugin-packs.an-artifact-conflict-response-carries-currentversion-version-token-concurrency', 'frontend-plugin-packs.md', `frontend-plugin-packs.md §Concurrency — a stale write surfaces artifact_conflict + currentVersion. Errors: ${JSON.stringify(validate.errors)}`),
163
- ).toBe(true);
164
- });
165
-
166
- it('a request with a method outside the allowlist is schema-rejected', () => {
167
- const reqBody = { openwop: 'ui-plugin/1', type: 'request', id: 1, method: 'host.exec' };
168
- expect(validate(reqBody), req('openwop.it.frontend-plugin-packs.a-request-with-a-method-outside-the-allowlist-is-schema-rejected', 'frontend-plugin-packs.md', 'a method outside the ui-plugin/1 allowlist MUST NOT validate')).toBe(false);
169
- });
170
-
171
- it('a message without the ui-plugin/1 protocol tag is rejected', () => {
172
- const reqBody = { openwop: 'ui-plugin/2', type: 'request', id: 1, method: 'artifact.read' };
173
- expect(validate(reqBody), req('openwop.it.frontend-plugin-packs.a-message-without-the-ui-plugin-1-protocol-tag-is-rejected', 'frontend-plugin-packs.md', 'a host MUST ignore messages whose ui-plugin tag it does not recognize')).toBe(false);
174
- });
175
-
176
- it('no credential-bearing field is admitted on the envelope (frontend-plugin-no-byok)', () => {
177
- // additionalProperties:false on every envelope variant — a stray apiKey/token at the
178
- // envelope root cannot ride the boundary.
179
- for (const leak of ['apiKey', 'token', 'clientSecret', 'authorization']) {
180
- const reqBody = { openwop: 'ui-plugin/1', type: 'request', id: 1, method: 'artifact.read', [leak]: 'xxx' };
181
- expect(
182
- validate(reqBody),
183
- req('openwop.it.frontend-plugin-packs.no-credential-bearing-field-is-admitted-on-the-envelope-frontend-plugin-no-byok', 'frontend-plugin-packs.md', `frontend-plugin-no-byok — a credential-named envelope field ("${leak}") MUST NOT validate (additionalProperties:false)`),
184
- ).toBe(false);
185
- }
186
- });
187
- });
188
-
189
32
  describe('frontend-plugin: isolation advertisement (always-on, capability shape)', () => {
190
33
  // RFC 0119: `isolation` is a categorical model, not a single browser const. A conformant host
191
34
  // advertises a member of the enum (cross-origin-iframe default) or an x-host-* vendor model —
@@ -0,0 +1,35 @@
1
+ /**
2
+ * v2 — a host rejecting at load a WASM pack whose ABI version it does not list
3
+ * (`spec/v2/core/node-pack-runtimes.md` §WASM: "MUST reject at load a pack
4
+ * whose `openwop_abi_version()` is not listed").
5
+ *
6
+ * The v1 twin, `wasm-pack-abi-version-rejection`, runs at major 1 only. Here the
7
+ * operator offers the hand-written fixture pack at
8
+ * `conformance/fixtures/wasm-packs/misbehaving-abi/` (`vendor.openwop.misbehaving-abi`,
9
+ * ABI version 999) to the host's loader and advertises
10
+ * `conformance-wasm-pack-abi-mismatch`. Loading is not a protocol operation, so
11
+ * the leg reads what a rejection makes impossible: the pack in
12
+ * `nodePackRuntimes.wasm.loadedPacks`, and its node completing.
13
+ *
14
+ * The leg lives in `lib/wasm-abi-reject-witness.ts`, proven against a double in
15
+ * `lib/wasm-abi-reject-witness.test.ts`.
16
+ *
17
+ * Dispositions: no `nodePackRuntimes.wasm`, or the fixture not advertised ⇒
18
+ * `inapplicable`; discovery unreachable or a 401/403/429/5xx at creation ⇒ `blocked`.
19
+ *
20
+ * @see spec/v2/core/node-pack-runtimes.md §WASM
21
+ * @see RFCS/0008-wasm-abi.md §H
22
+ */
23
+
24
+ import { describe, it, expect } from 'vitest';
25
+ import { softSkip } from '../lib/soft-skip.js';
26
+ import { req } from '../lib/requirement-ids.js';
27
+ import { abiRejectLeg } from '../lib/wasm-abi-reject-witness.js';
28
+
29
+ describe('v2 WASM ABI rejection (node-pack-runtimes.md §WASM)', () => {
30
+ it('a pack whose openwop_abi_version() is not listed is not loaded and its node never runs', async () => {
31
+ const out = await abiRejectLeg();
32
+ if (out.kind === 'skip') return softSkip(out.skip, out.reason);
33
+ expect(out.findings.join('; '), req('openwop.requirement.node-pack-runtimes.wasm-abi-reject', 'spec/v2/core/node-pack-runtimes.md §WASM', 'a host MUST reject at load a pack whose openwop_abi_version() is not listed')).toBe('');
34
+ });
35
+ });