@openwop/openwop-conformance 2.41.1 → 2.42.1

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,16 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.42.1] — 2026-09-26 — RFC 0111's live witness scenarios outlast a real host, and the MCP run-transport leg identifies its own run
4
+
5
+ - **RFC 0111's live scenarios no longer die at vitest's 30 s default.** `context-budget-transcript-bound` and `context-summarization-replay` drive the live-model fixture `conformance-context-budget-live` (six real model turns and six child runs; 30–40 s per run on MyndHyve production, 2026-09-26), and the global `testTimeout: 30_000` killed both before any assertion — which `OPENWOP_POLL_TIMEOUT_SCALE` could not reach. Each now carries a per-test timeout from `liveScenarioTimeoutMs(runs)` (`src/lib/polling.ts`: the scaled sum of `LIVE_RUN_POLL_MS` = 180 s per run plus 60 s for seam reads — 240 s / 420 s at scale 1) and polls with `{ timeoutMs: LIVE_RUN_POLL_MS }`, so the named poll deadline always fires before the test deadline, at every scale. The global timeout is unchanged. Self-test: `src/lib/polling.test.ts` (3 new); an unscaled helper fails 2 of them and a 30 s helper 3.
6
+ - **`v2-mcp-mount-map`'s run-transport leg identifies its own run.** It diffed `listRuns` around one `tools/call` and required exactly one new `conformance-noop` run, but the suite runs files concurrently and that fixture is every file's smallest run: the v2 reference host's CI (4 workers) failed "got 2 new run(s)" on a host whose `tools/call` started exactly one, and `fresh[0]` could have been a sibling's run read for the wrong transport. The leg now requires at least one new run and reads the transport of the run the result names (when `listRuns` shows it), else the only new run, else passes if any new run in the window started `mcp`.
7
+ - **`spec/v2/core/webhooks.md`'s `Stable` banner cites RFC 0217**, now `Accepted` on the v2 reference host's certified 2.42.0 cut. No scenario change.
8
+
9
+ ## [2.42.0] — 2026-09-26 — `replay_context_summary_unavailable` is a registered code, and an unregistered subscription has no sink
10
+
11
+ - **RFC 0217: after unregister, the dead-letter read answers `404 not_found`, as for a subscription that never existed.** `v2-webhook-durable-delivery` gains `openwop.requirement.0217.dead-letter-read-after-unregister`, gated on `webhooks.deadLetter`: read the sink (`200`, the control), unregister (`204`), read again, and read a never-minted same-tenant id; both later reads must be `404 not_found`. Closes RFC 0215's gap G5. Sabotage: a v2 reference host whose read answers `200 { deliveries: [] }` for an unknown id fails the row.
12
+ - **Version moved ahead of publication.** `@openwop/openwop-conformance` and its exact-pinned peer `@openwop/spec-artifacts` move to `2.42.0` because `2.41.1` is published and this cycle changes shipped spec files (`spec/v2/errors.json`, the generated `schemas/v2/error-envelope.schema.json`). A minor: a new registered error code is an additive wire member. No scenario changes — no conformance leg asserts the code (a black-box suite cannot make a host's summary artifact unavailable at `fromSeq`; RFC 0111 amendment).
13
+
3
14
  ## [2.41.1] — 2026-09-26 — an SSE projection row no longer depends on a host's front forwarding headers early
4
15
 
5
16
  - **`v2-stream-sse-projection` reads a terminal run's stream.** It streamed the noop run the moment it was created, so on a loaded certification cut the run was still queued and the stream stayed open. A public front that buffers SSE (openwop-app's Firebase Hosting) withholds the headers of an open stream, `streamEvents`' 8 s budget ran out before any header arrived, and `0171.stream-sse-projection` recorded `blocked` ("answered 0") on both of openwop-app's 2.40.3 production cuts. Quiet-machine time to first byte for the same request was 0.19 s. No rule bounds time to headers. The row tests how every frame names its run, and a terminal run's backlog carries every frame, so the leg now waits up to 30 s for the run to finish before it reads the stream (the host closes a terminal run's stream, events.md §SSE), and a missing response now says which of the two it was.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  # --legacy-peer-deps is REQUIRED, not optional: the exact peer pin is what npm's
12
12
  # default resolver refuses. npm 10.9 fails outright with
13
13
  # "Cannot read properties of null (reading 'edgesOut')" — use npm >= 11.
14
- npm install --legacy-peer-deps @openwop/openwop-conformance@2.41.1 @openwop/spec-artifacts@2.41.1
14
+ npm install --legacy-peer-deps @openwop/openwop-conformance@2.42.1 @openwop/spec-artifacts@2.42.1
15
15
  # or run without install:
16
16
  npx @openwop/openwop-conformance --base-url https://api.example.com --api-key hk_test_...
17
17
  ```
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.41.1",
4
- "stampSha256": "65a8826755d00694cf357104040996db9220790ddc8579ec4eaca262f75a444d"
3
+ "version": "2.42.1",
4
+ "stampSha256": "f71d4bdee95ce4bb80f65ffb6ace46bc5fb62db3456131531ad31caa1040c944"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.41.1",
3
+ "version": "2.42.1",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
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.41.1"
59
+ "@openwop/spec-artifacts": "2.42.1"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -3,10 +3,10 @@
3
3
  "generatedFrom": "src/scenarios/*.test.ts",
4
4
  "counts": {
5
5
  "files": 636,
6
- "tests": 2438,
7
- "withStableId": 2438,
6
+ "tests": 2439,
7
+ "withStableId": 2439,
8
8
  "interpolatedTitles": 0,
9
- "explicitIds": 2332
9
+ "explicitIds": 2333
10
10
  },
11
11
  "records": [
12
12
  {
@@ -30273,14 +30273,15 @@
30273
30273
  },
30274
30274
  {
30275
30275
  "section": "interop-map.json mcp.methods tools/call; runs.md run.started",
30276
- "requirement": "the run starts with run.started.transport mcp"
30276
+ "requirement": null,
30277
+ "interpolated": true
30277
30278
  }
30278
30279
  ]
30279
30280
  },
30280
30281
  {
30281
30282
  "id": "openwop.it.v2-mcp-mount-map.a-suspending-tool-answers-inputrequiredresult-the-retry-resolves-it-requeststate",
30282
30283
  "file": "v2-mcp-mount-map.test.ts",
30283
- "line": 181,
30284
+ "line": 197,
30284
30285
  "title": "a suspending tool answers InputRequiredResult; the retry resolves it; requestState is single use and forgery-proof",
30285
30286
  "explicitId": null,
30286
30287
  "citations": [
@@ -30322,7 +30323,7 @@
30322
30323
  {
30323
30324
  "id": "openwop.it.v2-mcp-mount-map.the-mrtr-input-request-key-is-the-open-interrupt-s-interruptid-never-the-node-it",
30324
30325
  "file": "v2-mcp-mount-map.test.ts",
30325
- "line": 206,
30326
+ "line": 222,
30326
30327
  "title": "the MRTR input-request key is the open interrupt’s interruptId, never the node it suspended on",
30327
30328
  "explicitId": null,
30328
30329
  "citations": [
@@ -30361,7 +30362,7 @@
30361
30362
  {
30362
30363
  "id": "openwop.it.v2-mcp-mount-map.a-list-that-differs-per-caller-is-cachescope-private",
30363
30364
  "file": "v2-mcp-mount-map.test.ts",
30364
- "line": 244,
30365
+ "line": 260,
30365
30366
  "title": "a list that differs per caller is cacheScope private",
30366
30367
  "explicitId": null,
30367
30368
  "citations": [
@@ -30379,7 +30380,7 @@
30379
30380
  {
30380
30381
  "id": "openwop.it.v2-mcp-mount-map.unknown-meta-extension-keys-and-capabilities-extensions-are-opaque-processed-nor",
30381
30382
  "file": "v2-mcp-mount-map.test.ts",
30382
- "line": 261,
30383
+ "line": 277,
30383
30384
  "title": "unknown _meta extension keys and capabilities.extensions are opaque: processed normally, granting nothing",
30384
30385
  "explicitId": null,
30385
30386
  "citations": [
@@ -30397,7 +30398,7 @@
30397
30398
  {
30398
30399
  "id": "openwop.it.v2-mcp-mount-map.an-unauthenticated-request-is-refused-at-the-boundary-unless-anonymousactor-is-a",
30399
30400
  "file": "v2-mcp-mount-map.test.ts",
30400
- "line": 270,
30401
+ "line": 286,
30401
30402
  "title": "an unauthenticated request is refused at the boundary unless anonymousActor is advertised",
30402
30403
  "explicitId": null,
30403
30404
  "citations": [
@@ -30420,7 +30421,7 @@
30420
30421
  {
30421
30422
  "id": "openwop.it.v2-mcp-mount-map.a-credential-interrupt-is-answered-in-url-mode-url-connecturl-iserror-without-ur",
30422
30423
  "file": "v2-mcp-mount-map.test.ts",
30423
- "line": 287,
30424
+ "line": 303,
30424
30425
  "title": "a credential interrupt is answered in URL mode (url = connectUrl), isError without URL support, and input_required again on an accept retry with no credential",
30425
30426
  "explicitId": "openwop.requirement.0199.mcp-url-mode",
30426
30427
  "citations": [
@@ -30458,7 +30459,7 @@
30458
30459
  {
30459
30460
  "id": "openwop.it.v2-mcp-mount-map.form-mode-is-never-emitted-for-a-nested-schema-or-a-sensitive-format-password-fi",
30460
30461
  "file": "v2-mcp-mount-map.test.ts",
30461
- "line": 328,
30462
+ "line": 344,
30462
30463
  "title": "form mode is never emitted for a nested schema or a sensitive (format password) field",
30463
30464
  "explicitId": "openwop.requirement.0199.form-mode-no-secret",
30464
30465
  "citations": [
@@ -33406,9 +33407,36 @@
33406
33407
  ]
33407
33408
  },
33408
33409
  {
33409
- "id": "openwop.it.v2-webhook-durable-delivery.a-dead-letter-record-carries-no-delivered-payload",
33410
+ "id": "openwop.it.v2-webhook-durable-delivery.after-unregister-the-dead-letter-read-answers-404-not-found-as-for-a-subscriptio",
33410
33411
  "file": "v2-webhook-durable-delivery.test.ts",
33411
33412
  "line": 475,
33413
+ "title": "after unregister, the dead-letter read answers 404 not_found, as for a subscription that never existed",
33414
+ "explicitId": "openwop.requirement.0217.dead-letter-read-after-unregister",
33415
+ "citations": [
33416
+ {
33417
+ "section": "RFC 0188 §A.1",
33418
+ "requirement": "while the subscription exists, its dead-letter read MUST answer 200 (the control for the comparison below)"
33419
+ },
33420
+ {
33421
+ "section": "webhooks.md §Surfaces",
33422
+ "requirement": "unregisterWebhook MUST answer 204 for the caller's own subscription"
33423
+ },
33424
+ {
33425
+ "section": "webhooks.md §Durability (RFC 0217 §A)",
33426
+ "requirement": null,
33427
+ "interpolated": true
33428
+ },
33429
+ {
33430
+ "section": "webhooks.md §Durability (RFC 0217 §A)",
33431
+ "requirement": null,
33432
+ "interpolated": true
33433
+ }
33434
+ ]
33435
+ },
33436
+ {
33437
+ "id": "openwop.it.v2-webhook-durable-delivery.a-dead-letter-record-carries-no-delivered-payload",
33438
+ "file": "v2-webhook-durable-delivery.test.ts",
33439
+ "line": 508,
33412
33440
  "title": "a dead-letter record carries no delivered payload",
33413
33441
  "explicitId": "openwop.requirement.0188.dead-letter-content-free",
33414
33442
  "citations": [
@@ -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.41.1",
5
- "corpusTag": "v2.41.1",
4
+ "version": "2.42.1",
5
+ "corpusTag": "v2.42.1",
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": "6366b5514be71098e9ea41fc81ff52bc39db1a547b353c0b344cc2a2bf5370aa",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "ce8ae43688e1b7d2a7906e320b99237f12adeebc7c854938679e91133cf76f37",
13
- "api/v2/asyncapi.yaml": "6a8e4010cb2af7c0ef46ee1239a1da4742648667a97e121ab0bfa3023d0c414c",
14
- "api/v2/openapi.yaml": "93b08e6581269e5cdeb39506a74aa41062666e29fc6fb9f4fb038977ab7453fd",
12
+ "api/seams-v2.yaml": "849b6c3d37336132343468e553b413a61a133fbc943d21ffc8e961948284db9f",
13
+ "api/v2/asyncapi.yaml": "96bac052de733a0987c35b4834341d91633381ff2ae1e6087ddb2956ac1edad9",
14
+ "api/v2/openapi.yaml": "eb39b2957737fc0f2e3187c33aac2f1475a3d2aa382a5c57686a1a15a5b643d4",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -142,7 +142,7 @@
142
142
  "schemas/v2/envelopes/schema.request.schema.json": "be2c5f7aa881cdd252ccff47ac02193659e1393633a1541f4297383205d384a2",
143
143
  "schemas/v2/envelopes/schema.response.schema.json": "d049a081267c06c28879c331b147f3ed821b14ccfd93005c67aff64db0a8c68e",
144
144
  "schemas/v2/envelopes/ui.a2ui-surface.schema.json": "e92a2548ba65454c9e0a34f72ea6768d18f59e61fa5da7668d0b2d5baddaddc8",
145
- "schemas/v2/error-envelope.schema.json": "217fa41cfe5a664e49d0b4dd97d3185b9eaaec9585bc4392aaf93175812ffbee",
145
+ "schemas/v2/error-envelope.schema.json": "b812a33626efd68d492269ff4a713e5a91a59d7071eb2470db99757f260e5d47",
146
146
  "schemas/v2/eval-summary.schema.json": "2f384daf05b8f3b4f604688cae504d6d9971ba41b8c771e55fe9a17e09e86b7a",
147
147
  "schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
148
148
  "schemas/v2/form-content-pack-manifest.schema.json": "a1942a124e250a6e9fbdb6b811c5a125e78fbcbcaa46dfc71d0636f24710a339",
@@ -204,13 +204,13 @@
204
204
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
205
205
  "spec/v1/alias-detectors.json": "2401fcb1c18cdd688c018b3d716ae6bca85c5e356220ee2b793bd9d81872412d",
206
206
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
207
- "spec/v1/core-standard-manifest.json": "809b6f0c90f84309e75c0f8425988baf0d65607d9f24a3453457080ac931792c",
207
+ "spec/v1/core-standard-manifest.json": "bbcefda69bffa9fb67803c48ddc7e786866c655d8af36f73fc0e3b27e69a1917",
208
208
  "spec/v1/deprecations.json": "2d03f4729810280147ea08c630ea65434f2dc5370567d0aac41c3595f8337d40",
209
209
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
210
210
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
211
211
  "spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
212
212
  "spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
213
- "spec/v1/gaps.json": "392a3d7f4b869a67d5861fcd47bc7eca83b6805b95b6520a91d9beb91df51c52",
213
+ "spec/v1/gaps.json": "d91c400b704b83e65dd40dbed1c0331a6d005fdb77d3facce0eacea89c4c7f14",
214
214
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
215
215
  "spec/v1/migrations.json": "8839497d6a4830b53fa7867014a8336f0d725ef2346352e54fb580b7947897af",
216
216
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
@@ -221,7 +221,7 @@
221
221
  "spec/v2/core/conformance.md": "e4d3b34ef5b7c6a34675ed59a086143e8bfd0e00d0d04ce0f2a2ec6245275084",
222
222
  "spec/v2/core/connection-packs.md": "7257339a4fc125011bf89961588c949cb058a0c0f7d9808ad08a5288deb3f8fc",
223
223
  "spec/v2/core/conversation.md": "c9a937e410fdb4e092cec2f05a7cab8b5232745090cfb20041c03c70ccbb5cdc",
224
- "spec/v2/core/errors.md": "619cd74381c41acf33d012eb1afd834e148ac292663a1a6fa6744dbaff9661d9",
224
+ "spec/v2/core/errors.md": "870f1a9c9481fb49c8bd39170f0656c2ae46124afd4472fb7a3c18c67be0e561",
225
225
  "spec/v2/core/events.md": "3df1866a89fb765c78adf79b8d51f96c59584dee9aa88d59179b88a681d827da",
226
226
  "spec/v2/core/form-content-packs.md": "55ea021b5f676f5851f420b3597262fbf6642108bddeb726922f8c5dd96c968b",
227
227
  "spec/v2/core/headers.md": "38c03524e3aeea30862432d203faffb8b0b05ec65136d223e96ab06941c3717b",
@@ -230,23 +230,23 @@
230
230
  "spec/v2/core/identity.md": "d15667552019f64316be9b65c6e5696c10766581405f05da3907fb47f36e6459",
231
231
  "spec/v2/core/interop.md": "9a6b37e07c0235856199ace6b3a84f592fa5903fcf6c442f8a18f4d6b6aa5525",
232
232
  "spec/v2/core/interrupt.md": "1dc94f7c791dd5a547b1abafde6a8a76acd3d2993d288057f302f57911dc83bf",
233
- "spec/v2/core/node-pack-runtimes.md": "c435ad506f618831b6c0a9fd9150d83a5dc6caa1e3d755b949af2ff8949f1140",
233
+ "spec/v2/core/node-pack-runtimes.md": "b6de99daf90a72f08ce0e311e63988198964f99346591e466529d9e95dc4b6d9",
234
234
  "spec/v2/core/oauth.md": "38ec0e70d46f328430138559095f261ae4fcd3d32c2d994a2d3e630a062c0e6c",
235
235
  "spec/v2/core/overview.md": "8f9d4a68b6185664e40d226cb82d9a9270a6c6630e3db59bddff300be781947d",
236
236
  "spec/v2/core/packs.md": "91cd577b4a636c1587ef78742e583a203244b8508b289b01a18a8ef2ef0a9b3e",
237
237
  "spec/v2/core/persistence.md": "d32736bd74e51df7c552565b4105a8ec801ece6d2292c8676421368ee9bffec1",
238
- "spec/v2/core/replay.md": "9050256a417171b184122877aa395d264545bb67c20e045315f42baa539f13cd",
238
+ "spec/v2/core/replay.md": "7c11e0dad22d367b94630e00b435ed8f33d0ab76d36adc65e150212d770ad685",
239
239
  "spec/v2/core/runs.md": "c7dcc5116648f42cadecdcd4c949b194272908cbc4b1fbc7321956d5b3a713af",
240
240
  "spec/v2/core/security-defaults.md": "500471a8db7af9b776ac40b4a9a278c9f3d45e1c1dead880114e7fadc7bee37d",
241
241
  "spec/v2/core/tool-catalog.md": "35f1a3fd509db0fc0490ddf400686fe94dc4b20c98064d5ebe776da516153e60",
242
242
  "spec/v2/core/versioning.md": "0aded71d090c8358c3ce17763c120cfc1cc42531a5eaa1b8a2a7016ed9594069",
243
- "spec/v2/core/webhooks.md": "2a44b100c42dd841fe2b8f87179a981be6c0dd26608ce1b14e077318c4ac8201",
243
+ "spec/v2/core/webhooks.md": "78d7e237866bf1ee95bbfa9d5b62b619ca2f8ec677d1b752ba96ed5efcfc77ef",
244
244
  "spec/v2/core/workflow-chain-packs.md": "ee45d3fede3bb6f0cc6abcf0d6c929c7cd213a0fbe862b738a998f929c4d1102",
245
245
  "spec/v2/corrections.json": "cc74ffde74384b0f261a4f625871d922e8661d654d647a8534e21036595cb280",
246
246
  "spec/v2/corrections.schema.json": "4ac595b9a6d7f66d53de03fe0edbe0dc58de46426932387bfdc0ceb3cf949a3e",
247
247
  "spec/v2/declaration.json": "84bd247804e625b0a5d66f2011726626e8a9b21e55c479cc9ba239e6b5c358a6",
248
248
  "spec/v2/declaration.schema.json": "881c08b455aa12c41a0a0ec43a960ab78b75ff1302188f7eaaa43074ed4cff7a",
249
- "spec/v2/errors.json": "287a8bb487be96a2938b2101dd737ee87336440b3777a37dd44954726ed891b1",
249
+ "spec/v2/errors.json": "1064999c8a00f44ab99ce24995fe9a93e6d2d5778e5eacb6621de0d0af1eb37f",
250
250
  "spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
251
251
  "spec/v2/event-codemap.schema.json": "b173f2a9bcc0bf9b62a474ce3b91e1431d597e64fd1560fc58662cd8606eaf9c",
252
252
  "spec/v2/ext/README.md": "ff91a503e0795e1b898e37d9e445add2d5975529723f7b32b2599b60a24c1ae3",
@@ -294,9 +294,9 @@
294
294
  "spec/v2/path-manifest.json": "ae9b56d03a701061fd48ad0b73cb0373493f6a4547da92763b422295b5e19535",
295
295
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
296
296
  "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
297
- "spec/v2/release.json": "7ea3c5b5441f86fc7e22eb13407e7554eba5f3c138fe30aaf76dc2a83a75de25",
297
+ "spec/v2/release.json": "c226854124edd701082c57b55c908cdf09926837611a59d00e306696a06a9b8a",
298
298
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
299
- "spec/v2/surface-baseline.json": "b0c9d00354358f46f032b26d889d7fc4a71765833d2f1f21a6d822a5868780ad"
299
+ "spec/v2/surface-baseline.json": "8411f06cd9888524b6d234e8d111896d8c90d06991453c9a192e7550d0b3cb9e"
300
300
  },
301
- "corpusCommit": "55ec9e964bd782903c06fc687016d6e98a6ae5f2"
301
+ "corpusCommit": "c92bac3ebafda0fab5a6113df67422efe428d70d"
302
302
  }
@@ -71,6 +71,27 @@ export function scaledTimeoutMs(timeoutMs: number): number {
71
71
  return scale === 1 ? timeoutMs : Math.ceil(timeoutMs * scale);
72
72
  }
73
73
 
74
+ /**
75
+ * Live-model scenarios (RFC 0111's `conformance-context-budget-live`) drive real
76
+ * model turns and child runs; one run takes 30–40 s on a production host
77
+ * (MyndHyve, 2026-09-26), so vitest's global 30 s `testTimeout` killed them
78
+ * before any assertion — a suite defect that looks like a host failure.
79
+ *
80
+ * `LIVE_RUN_POLL_MS` is the base bound for ONE live run to reach a terminal
81
+ * status (below the fixture's own `settings.timeout` of 300 s); pass it as
82
+ * `pollUntilTerminal(runId, { timeoutMs: LIVE_RUN_POLL_MS })` — `pollUntil`
83
+ * scales it. `liveScenarioTimeoutMs(runs)` is the per-test vitest timeout: the
84
+ * scaled sum of the scenario's poll bounds plus 60 s for its seam reads, so the
85
+ * poll deadline always fires first and a hung host fails with a named poll
86
+ * message, never a bare vitest timeout. Both scale with
87
+ * `OPENWOP_POLL_TIMEOUT_SCALE`, so their order holds at every scale.
88
+ */
89
+ export const LIVE_RUN_POLL_MS = 180_000;
90
+
91
+ export function liveScenarioTimeoutMs(runs: number): number {
92
+ return scaledTimeoutMs(runs * LIVE_RUN_POLL_MS + 60_000);
93
+ }
94
+
74
95
  const TERMINAL = new Set(['completed', 'failed', 'cancelled']);
75
96
 
76
97
  export async function getRun(runId: string): Promise<RunSnapshot> {
@@ -55,7 +55,7 @@
55
55
 
56
56
  import { describe, it, expect } from 'vitest';
57
57
  import { driver } from '../lib/driver.js';
58
- import { pollUntilTerminal } from '../lib/polling.js';
58
+ import { LIVE_RUN_POLL_MS, liveScenarioTimeoutMs, pollUntilTerminal } from '../lib/polling.js';
59
59
  import { behaviorGate } from '../lib/behavior-gate.js';
60
60
  import { isFixtureAdvertised } from '../lib/fixtures.js';
61
61
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
@@ -111,7 +111,7 @@ describe('context-budget-transcript-bound (RFC 0111 §"Context economy")', () =>
111
111
  const runId = runIdOf(create.json);
112
112
  expect(runId, req(ID, 'RFC 0111', 'the create response MUST carry a runId')).toBeDefined();
113
113
  if (runId === undefined) return softSkip('blocked', 'no runId');
114
- await pollUntilTerminal(runId);
114
+ await pollUntilTerminal(runId, { timeoutMs: LIVE_RUN_POLL_MS });
115
115
 
116
116
  const windows: Array<{ iteration: number; window: TranscriptWindow }> = [];
117
117
  for (let iteration = 1; iteration <= MAX_ITERATIONS_PROBED; iteration += 1) {
@@ -153,5 +153,5 @@ describe('context-budget-transcript-bound (RFC 0111 §"Context economy")', () =>
153
153
 
154
154
  if (log === null) return softSkip('blocked', 'the run event-log seam is unavailable, so the real-event, recent-tail and pressure rules were not measured');
155
155
  if (!pressure) softSkip('inapplicable', `no iteration shows budget pressure — every eligible event fit under transcriptTokenBudget ${budget}, so the bound was never exercised (a budget the run never reaches is not a witness)`);
156
- });
156
+ }, liveScenarioTimeoutMs(1));
157
157
  });
@@ -38,7 +38,7 @@
38
38
 
39
39
  import { describe, it, expect } from 'vitest';
40
40
  import { driver } from '../lib/driver.js';
41
- import { pollUntilTerminal } from '../lib/polling.js';
41
+ import { LIVE_RUN_POLL_MS, liveScenarioTimeoutMs, pollUntilTerminal } from '../lib/polling.js';
42
42
  import { behaviorGate } from '../lib/behavior-gate.js';
43
43
  import { isFixtureAdvertised } from '../lib/fixtures.js';
44
44
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
@@ -125,7 +125,7 @@ describe('context-summarization-replay (RFC 0111 §"Replay determinism")', () =>
125
125
  const sourceRunId = runIdOf(create.json);
126
126
  expect(sourceRunId, req(ID, 'rest-endpoints.md POST /v1/runs', 'the create response MUST carry a runId')).toBeDefined();
127
127
  if (sourceRunId === undefined) return softSkip('blocked', 'no runId');
128
- await pollUntilTerminal(sourceRunId);
128
+ await pollUntilTerminal(sourceRunId, { timeoutMs: LIVE_RUN_POLL_MS });
129
129
 
130
130
  const sourceQ = await queryTestEvents(sourceRunId);
131
131
  if (!sourceQ.ok) return softSkip('blocked', 'the run event-log seam is unavailable');
@@ -141,7 +141,7 @@ describe('context-summarization-replay (RFC 0111 §"Replay determinism")', () =>
141
141
  const forkRunId = runIdOf(fork.json);
142
142
  expect(forkRunId, req(ID, 'rest-endpoints.md POST /v1/runs/{runId}:fork', 'replay fork MUST return a runId')).toBeDefined();
143
143
  if (forkRunId === undefined) return softSkip('blocked', 'no fork runId');
144
- await pollUntilTerminal(forkRunId);
144
+ await pollUntilTerminal(forkRunId, { timeoutMs: LIVE_RUN_POLL_MS });
145
145
 
146
146
  const forkQ = await queryTestEvents(forkRunId);
147
147
  if (!forkQ.ok) return softSkip('blocked', 'the event-log seam is unavailable for the fork');
@@ -153,5 +153,5 @@ describe('context-summarization-replay (RFC 0111 §"Replay determinism")', () =>
153
153
  if (sourceTexts === null || forkTexts === null) return softSkip('inapplicable', 'the transcript-window seam serves no entries[] for these runs, so the model-facing summary text was not compared (summaryRef reuse was)');
154
154
  expect(sourceTexts.length, req(ID, 'RFC 0111 §"Replay determinism"', 'the source run summarized, so its transcript windows MUST carry the summary text it fed')).toBeGreaterThan(0);
155
155
  expect(forkTexts, req(ID, 'RFC 0111 §"Replay determinism"', 'the replay MUST feed the model the recorded summary text, byte for byte — never a re-summarization')).toEqual(sourceTexts);
156
- });
156
+ }, liveScenarioTimeoutMs(2));
157
157
  });
@@ -172,10 +172,26 @@ describe('RFC 0208 — v2-mcp-mount-map (host as MCP 2026-07-28 server, gated on
172
172
  const before = new Set(await list());
173
173
  const ok = await toolCall(m.url, 'conformance-noop');
174
174
  const fresh = (await list()).filter((x) => !before.has(x));
175
- expect(fresh.length, req(R('mcp-run-transport'), 'interop-map.json mcp.methods tools/call', `tools/call MUST start a run (v2Operation createRun) that listRuns shows the same Subject (got ${fresh.length} new run(s); result ${JSON.stringify(ok.error ?? ok.result)})`)).toBe(1);
176
- const poll = await driver.get(`/runs/${encodeURIComponent(fresh[0]!)}/events/poll?timeout=1`);
177
- const started = ((poll.json as { events?: Array<{ type?: string; payload?: { transport?: string } }> } | undefined)?.events ?? []).find((e) => e.type === 'run.started');
178
- expect(started?.payload?.transport, req(R('mcp-run-transport'), 'interop-map.json mcp.methods tools/call; runs.md run.started', 'the run starts with run.started.transport mcp')).toBe('mcp');
175
+ expect(fresh.length, req(R('mcp-run-transport'), 'interop-map.json mcp.methods tools/call', `tools/call MUST start a run (v2Operation createRun) that listRuns shows the same Subject (got ${fresh.length} new run(s); result ${JSON.stringify(ok.error ?? ok.result)})`)).toBeGreaterThanOrEqual(1);
176
+ // WHICH new run is ours (2.42.1). The leg counted `fresh.length === 1`, but
177
+ // the suite runs files concurrently and conformance-noop is every file's
178
+ // smallest run, so a sibling's run landed in the same window: the v2
179
+ // reference host's CI (4 workers) failed "got 2 new run(s)" on a host whose
180
+ // own tools/call started exactly one, and `fresh[0]` could have been the
181
+ // sibling's run, read for the wrong transport. Ours is the runId the result
182
+ // names when it names one listRuns shows, else the only new run; when the
183
+ // window stays ambiguous, the requirement holds if any new run started mcp.
184
+ const named = ((): string | null => {
185
+ const m = /"runId"\s*:\s*"([^"]+)"/.exec(JSON.stringify(ok.result ?? ''));
186
+ return m && fresh.includes(m[1]!) ? m[1]! : null;
187
+ })();
188
+ const transportOf = async (runId: string): Promise<string | undefined> => {
189
+ const poll = await driver.get(`/runs/${encodeURIComponent(runId)}/events/poll?timeout=1`);
190
+ return ((poll.json as { events?: Array<{ type?: string; payload?: { transport?: string } }> } | undefined)?.events ?? []).find((e) => e.type === 'run.started')?.payload?.transport;
191
+ };
192
+ const ours = named ?? (fresh.length === 1 ? fresh[0]! : null);
193
+ const transports = ours !== null ? [await transportOf(ours)] : await Promise.all(fresh.map(transportOf));
194
+ expect(transports.includes('mcp') ? 'mcp' : transports[0], req(R('mcp-run-transport'), 'interop-map.json mcp.methods tools/call; runs.md run.started', `the run starts with run.started.transport mcp (${ours !== null ? `run ${ours}` : `${fresh.length} runs started in the window, none named by the result`}; transports ${JSON.stringify(transports)})`)).toBe('mcp');
179
195
  });
180
196
 
181
197
  it('a suspending tool answers InputRequiredResult; the retry resolves it; requestState is single use and forgery-proof', async () => {
@@ -472,6 +472,39 @@ describe('RFC 0173 §B — webhook-durable-delivery (gated on webhooks)', () =>
472
472
  ).toBe(200);
473
473
  }, DEAD_LETTER_TEST_TIMEOUT_MS);
474
474
 
475
+ it('after unregister, the dead-letter read answers 404 not_found, as for a subscription that never existed', async () => {
476
+ // RFC 0217 (amends RFC 0188 §A; suite 2.42.0; RFC 0215 gap G5). The read
477
+ // is path-scoped to a subscription, and identity.md §5 already says an
478
+ // unknown id is 404. What was unstated is the id that WAS known: after
479
+ // unregisterWebhook's 204 a host MUST answer exactly as for a same-tenant id
480
+ // it never minted, so the subscription's existence is not recoverable
481
+ // through the sink, and a host MAY discard the records at the unregister.
482
+ const ID = 'openwop.requirement.0217.dead-letter-read-after-unregister';
483
+ if (!(await v2Discovery())) return softSkip('blocked', 'v2 discovery unreachable');
484
+ const fam = await gateFamily('webhooks');
485
+ if (!fam) return softSkip('inapplicable', 'webhooks family not advertised');
486
+ if (!fam['deadLetter']) return softSkip('inapplicable', 'host does not advertise the webhooks.deadLetter facet — RFC 0188 §A.5 makes the read a 404 for every subscription, so there is no before/after to compare');
487
+ const reg = await driver.post('/webhooks', { url: 'https://subscriber.invalid/hook', events: ['run.completed'] });
488
+ if (reg.status !== 201) return softSkip('blocked', `POST /webhooks answered ${reg.status} — no subscription to unregister`);
489
+ const webhookId = (reg.json as { webhookId?: unknown } | null)?.webhookId;
490
+ if (typeof webhookId !== 'string' || !webhookId.includes('/')) return softSkip('blocked', `the mint returned ${JSON.stringify(webhookId)}, not a tenant-bound id`);
491
+ const live = await driver.get(`/webhooks/${projectBoundId(webhookId)}/dead-letters`);
492
+ const del = await driver.delete(`/webhooks/${encodeURIComponent(webhookId)}`);
493
+ expect(live.status, req(ID, 'RFC 0188 §A.1', 'while the subscription exists, its dead-letter read MUST answer 200 (the control for the comparison below)')).toBe(200);
494
+ expect(del.status, req(ID, 'webhooks.md §Surfaces', 'unregisterWebhook MUST answer 204 for the caller\'s own subscription')).toBe(204);
495
+ const after = await driver.get(`/webhooks/${projectBoundId(webhookId)}/dead-letters`);
496
+ const tenant = webhookId.slice(0, webhookId.indexOf('/'));
497
+ const never = await driver.get(`/webhooks/${projectBoundId(`${tenant}/never-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`)}/dead-letters`);
498
+ expect(
499
+ [after.status, readErrorCode(after.json)],
500
+ req(ID, 'webhooks.md §Durability (RFC 0217 §A)', `after unregisterWebhook answers 204, GET /webhooks/{webhookId}/dead-letters MUST answer 404 not_found — the subscription no longer exists to own a sink (got ${after.status} ${readErrorCode(after.json) ?? ''})`),
501
+ ).toEqual([404, 'not_found']);
502
+ expect(
503
+ [after.status, readErrorCode(after.json)],
504
+ req(ID, 'webhooks.md §Durability (RFC 0217 §A)', `the answer for an unregistered subscription MUST be indistinguishable from a same-tenant id the host never minted (that one answered ${never.status} ${readErrorCode(never.json) ?? ''})`),
505
+ ).toEqual([never.status, readErrorCode(never.json)]);
506
+ }, DEAD_LETTER_TEST_TIMEOUT_MS);
507
+
475
508
  it('a dead-letter record carries no delivered payload', async () => {
476
509
  // Its own `it`: RFC 0168 §A.1 allows one explicit requirement id per it(),
477
510
  // and `check-req-only` enforces it.