@openwop/openwop-conformance 2.45.26 → 2.45.28

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,20 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.45.28] — 2026-10-08 — two more seam-driven legs, and the backoff ratio, on a public seam-free cut
4
+
5
+ - **Found by the v2 reference host's first public seams-off cut** (2.45.27, RFC 0241's evidence): all seven target rows `executed-pass`, but four other rows kept the bundle from certifying. Neither shows on a loopback cut.
6
+ - **`0199.mcp-url-mode` (`v2-mcp-mount-map`) and `0199.a2a-auth-required` (`v2-a2a-operation-map`):** both mint a fresh Subject through the credential mint seam, without checking for the seams profile, and recorded `blocked` when it was absent. They run only where `oauth.credentialInterrupt` is advertised, which this host does only with an https public base. Now `inapplicable` without the seams profile.
7
+ - **`v2-webhook-durable-delivery`, the exponential-backoff check:** it required the second retry gap to be at least 1.5x the first. Each gap also carries a constant, the receiver round trip a host waits on before scheduling the retry, so the gaps are B + c and 2B + c; through a tunnel (c ≈ 270 ms, base 500 ms) a correct host measured 1.43x. The second gap must now exceed the first by at least a quarter of it.
8
+ - Proven on the v2 reference host: seams off with an https base, both 0199 rows `inapplicable`; the backoff leg passes on the host as is, and fails (510 ms after 604 ms) with the backoff made constant.
9
+
10
+ ## [2.45.27] — 2026-10-08 — a seam-free host's seam-driven legs record `inapplicable`, not `blocked`
11
+
12
+ - **Three scenarios recorded `blocked` on a host without the seams profile**, which denies the bundle certification, where the rest of the suite records a seam-driven leg `inapplicable`. Found by the v2 reference host's first seams-off cut (RFC 0241's evidence): 11 rows `blocked`, all `executed-pass` with the seams profile.
13
+ - **`v2-pack-isolation` (8 rows):** `invoke` recorded `inapplicable`, then each leg added `blocked` (`seam unavailable`), which outranks it. A leg now returns the kind `invoke` recorded; a seams host whose sandbox seam answers 404 is still `blocked`.
14
+ - **`v2-negotiation-authenticated` and `v2-minimum-version-refused`, MCP legs:** `blocked` without the seams profile, where their A2A twins say `inapplicable`. Now `inapplicable`.
15
+ - **`v2-approval-reject-disposition`, the quorum leg:** the resume value's `voter` is a suite convention, not a protocol field, so a seam-free host rightly counts the bearer, and one bearer cannot cause a majority. That is now `inapplicable`; a seams host that ignores `voter` is still `blocked`.
16
+ - Proven on the v2 reference host: seams off, the 11 rows `inapplicable` and none `blocked`; seams on, all 28 rows of the four files `executed-pass`; seams on with `voter` ignored, the quorum row `blocked`.
17
+
3
18
  ## [2.45.26] — 2026-10-08 — RFC 0236's legs run without the seam (RFC 0241 Active)
4
19
 
5
20
  - **No scenario change.** RFC 0237 goes `Accepted` on the v2 reference host's certified 2.45.25 cut; its gaps G1 and G2 close, which changes the packed `spec/v1/gaps.json`.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.45.26",
4
- "stampSha256": "01d0c29c58bd680baa38517e507d8c14b73ca1bf9aa888d79efc7d78a94d9e1b"
3
+ "version": "2.45.28",
4
+ "stampSha256": "0e47a879900333edf1f01645a54edb7935cf263125d4ab3dc90405f81189d8a3"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.45.26",
3
+ "version": "2.45.28",
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.26"
59
+ "@openwop/spec-artifacts": "2.45.28"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -28193,7 +28193,7 @@
28193
28193
  {
28194
28194
  "id": "openwop.it.v2-approval-reject-disposition.a-rejected-gate-with-no-routing-edge-fails-the-run-with-approval-rejected",
28195
28195
  "file": "v2-approval-reject-disposition.test.ts",
28196
- "line": 141,
28196
+ "line": 143,
28197
28197
  "title": "a rejected gate with no routing edge fails the run with approval_rejected",
28198
28198
  "explicitId": null,
28199
28199
  "citations": [
@@ -28216,7 +28216,7 @@
28216
28216
  {
28217
28217
  "id": "openwop.it.v2-approval-reject-disposition.interrupt-resolved-records-action-reject-and-decision-rejected",
28218
28218
  "file": "v2-approval-reject-disposition.test.ts",
28219
- "line": 152,
28219
+ "line": 154,
28220
28220
  "title": "interrupt.resolved records action reject and decision rejected",
28221
28221
  "explicitId": null,
28222
28222
  "citations": [
@@ -28235,7 +28235,7 @@
28235
28235
  {
28236
28236
  "id": "openwop.it.v2-approval-reject-disposition.the-gate-node-fails-with-approval-rejected-not-retryable-and-run-failed-names-it",
28237
28237
  "file": "v2-approval-reject-disposition.test.ts",
28238
- "line": 165,
28238
+ "line": 167,
28239
28239
  "title": "the gate node fails with approval_rejected, not retryable, and run.failed names it",
28240
28240
  "explicitId": null,
28241
28241
  "citations": [
@@ -28256,7 +28256,7 @@
28256
28256
  {
28257
28257
  "id": "openwop.it.v2-approval-reject-disposition.a-majority-reject-decides-the-quorum-gate-on-the-deciding-vote-and-fails-the-run",
28258
28258
  "file": "v2-approval-reject-disposition.test.ts",
28259
- "line": 178,
28259
+ "line": 180,
28260
28260
  "title": "a majority reject decides the quorum gate on the deciding vote and fails the run with approval_rejected",
28261
28261
  "explicitId": null,
28262
28262
  "citations": [
@@ -28282,7 +28282,7 @@
28282
28282
  {
28283
28283
  "id": "openwop.it.v2-approval-reject-disposition.a-rejected-gate-routed-over-an-any-failed-edge-fails-as-a-node-its-all-success-s",
28284
28284
  "file": "v2-approval-reject-disposition.test.ts",
28285
- "line": 201,
28285
+ "line": 207,
28286
28286
  "title": "a rejected gate routed over an any_failed edge fails as a node, its all_success sibling never runs, and the run completes",
28287
28287
  "explicitId": null,
28288
28288
  "citations": [
@@ -28308,7 +28308,7 @@
28308
28308
  {
28309
28309
  "id": "openwop.it.v2-approval-reject-disposition.an-unresolved-gate-past-its-timeoutms-with-no-ontimeout-is-resolved-rejected-by",
28310
28310
  "file": "v2-approval-reject-disposition.test.ts",
28311
- "line": 220,
28311
+ "line": 226,
28312
28312
  "title": "an unresolved gate past its timeoutMs with no onTimeout is resolved rejected by the host and fails the run naming the gate",
28313
28313
  "explicitId": null,
28314
28314
  "citations": [
@@ -28329,7 +28329,7 @@
28329
28329
  {
28330
28330
  "id": "openwop.it.v2-approval-reject-disposition.a-gate-whose-ontimeout-is-approve-still-resolves-rejected-on-timeout-and-fails-t",
28331
28331
  "file": "v2-approval-reject-disposition.test.ts",
28332
- "line": 239,
28332
+ "line": 245,
28333
28333
  "title": "a gate whose onTimeout is approve still resolves rejected on timeout and fails the run: a timeout never grants",
28334
28334
  "explicitId": null,
28335
28335
  "citations": [
@@ -28351,7 +28351,7 @@
28351
28351
  {
28352
28352
  "id": "openwop.it.v2-approval-reject-disposition.a-rejected-gate-looped-back-to-itself-is-asked-again-under-a-new-key-and-the-fir",
28353
28353
  "file": "v2-approval-reject-disposition.test.ts",
28354
- "line": 257,
28354
+ "line": 263,
28355
28355
  "title": "a rejected gate looped back to itself is asked again under a new key, and the first rejection is not replayed",
28356
28356
  "explicitId": null,
28357
28357
  "citations": [
@@ -28377,7 +28377,7 @@
28377
28377
  {
28378
28378
  "id": "openwop.it.v2-approval-reject-disposition.a-replay-fork-of-a-decided-reject-derives-the-rejection-from-the-log-and-never-r",
28379
28379
  "file": "v2-approval-reject-disposition.test.ts",
28380
- "line": 333,
28380
+ "line": 339,
28381
28381
  "title": "a replay fork of a decided reject derives the rejection from the log and never re-decides it (leg A)",
28382
28382
  "explicitId": null,
28383
28383
  "citations": [
@@ -28390,7 +28390,7 @@
28390
28390
  {
28391
28391
  "id": "openwop.it.v2-approval-reject-disposition.a-replay-fork-of-a-timed-out-gate-derives-the-timeout-rejection-from-the-log-and",
28392
28392
  "file": "v2-approval-reject-disposition.test.ts",
28393
- "line": 348,
28393
+ "line": 354,
28394
28394
  "title": "a replay fork of a timed-out gate derives the timeout rejection from the log and never re-arms it (leg B)",
28395
28395
  "explicitId": null,
28396
28396
  "citations": [
@@ -32919,7 +32919,7 @@
32919
32919
  {
32920
32920
  "id": "openwop.it.v2-mcp-mount-map.form-mode-is-never-emitted-for-a-nested-schema-or-a-sensitive-format-password-fi",
32921
32921
  "file": "v2-mcp-mount-map.test.ts",
32922
- "line": 344,
32922
+ "line": 345,
32923
32923
  "title": "form mode is never emitted for a nested schema or a sensitive (format password) field",
32924
32924
  "explicitId": "openwop.requirement.0199.form-mode-no-secret",
32925
32925
  "citations": [
@@ -34062,7 +34062,7 @@
34062
34062
  {
34063
34063
  "id": "openwop.it.v2-pack-isolation.sandbox-isolationmodel-names-a-real-mechanism-never-node-vm",
34064
34064
  "file": "v2-pack-isolation.test.ts",
34065
- "line": 82,
34065
+ "line": 89,
34066
34066
  "title": "sandbox.isolationModel names a real mechanism, never node:vm",
34067
34067
  "explicitId": "openwop.requirement.0173.pack-isolation",
34068
34068
  "citations": [
@@ -34080,7 +34080,7 @@
34080
34080
  {
34081
34081
  "id": "openwop.it.v2-pack-isolation.the-sandbox-invoke-seam-answers-the-seam-contract",
34082
34082
  "file": "v2-pack-isolation.test.ts",
34083
- "line": 96,
34083
+ "line": 103,
34084
34084
  "title": "the sandbox-invoke seam answers the seam contract",
34085
34085
  "explicitId": "openwop.requirement.0173.pack-isolation.seam",
34086
34086
  "citations": [
@@ -34094,7 +34094,7 @@
34094
34094
  {
34095
34095
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-fs-gated-a-host-filesystem-read-is-refused",
34096
34096
  "file": "v2-pack-isolation.test.ts",
34097
- "line": 108,
34097
+ "line": 115,
34098
34098
  "title": "node-pack-sandbox-fs-gated: a host filesystem read is refused",
34099
34099
  "explicitId": null,
34100
34100
  "citations": []
@@ -34102,7 +34102,7 @@
34102
34102
  {
34103
34103
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-fs-gated-a-host-filesystem-write-is-refused",
34104
34104
  "file": "v2-pack-isolation.test.ts",
34105
- "line": 115,
34105
+ "line": 122,
34106
34106
  "title": "node-pack-sandbox-fs-gated: a host filesystem write is refused",
34107
34107
  "explicitId": null,
34108
34108
  "citations": []
@@ -34110,7 +34110,7 @@
34110
34110
  {
34111
34111
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-no-env-the-host-environment-does-not-leak",
34112
34112
  "file": "v2-pack-isolation.test.ts",
34113
- "line": 122,
34113
+ "line": 129,
34114
34114
  "title": "node-pack-sandbox-no-env: the host environment does not leak",
34115
34115
  "explicitId": null,
34116
34116
  "citations": []
@@ -34118,7 +34118,7 @@
34118
34118
  {
34119
34119
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-network-gated-ungated-network-egress-is-refused",
34120
34120
  "file": "v2-pack-isolation.test.ts",
34121
- "line": 129,
34121
+ "line": 136,
34122
34122
  "title": "node-pack-sandbox-network-gated: ungated network egress is refused",
34123
34123
  "explicitId": null,
34124
34124
  "citations": []
@@ -34126,7 +34126,7 @@
34126
34126
  {
34127
34127
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-no-process-host-process-access-is-refused",
34128
34128
  "file": "v2-pack-isolation.test.ts",
34129
- "line": 136,
34129
+ "line": 143,
34130
34130
  "title": "node-pack-sandbox-no-process: host process access is refused",
34131
34131
  "explicitId": null,
34132
34132
  "citations": []
@@ -34134,7 +34134,7 @@
34134
34134
  {
34135
34135
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-timeout-exceeding-wallclocklimitms-fails-with-sandbox-timeout",
34136
34136
  "file": "v2-pack-isolation.test.ts",
34137
- "line": 143,
34137
+ "line": 150,
34138
34138
  "title": "node-pack-sandbox-timeout: exceeding wallClockLimitMs fails with sandbox_timeout",
34139
34139
  "explicitId": "openwop.requirement.0173.pack-isolation.timeout",
34140
34140
  "citations": [
@@ -34147,7 +34147,7 @@
34147
34147
  {
34148
34148
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-memory-cap-exceeding-memorylimitbytes-fails-with-sandbox-memor",
34149
34149
  "file": "v2-pack-isolation.test.ts",
34150
- "line": 150,
34150
+ "line": 157,
34151
34151
  "title": "node-pack-sandbox-memory-cap: exceeding memoryLimitBytes fails with sandbox_memory_exceeded",
34152
34152
  "explicitId": "openwop.requirement.0173.pack-isolation.memory-cap",
34153
34153
  "citations": [
@@ -34160,7 +34160,7 @@
34160
34160
  {
34161
34161
  "id": "openwop.it.v2-pack-isolation.node-pack-sandbox-isolated-context-a-pack-cannot-mutate-state-another-invocation",
34162
34162
  "file": "v2-pack-isolation.test.ts",
34163
- "line": 157,
34163
+ "line": 164,
34164
34164
  "title": "node-pack-sandbox-isolated-context: a pack cannot mutate state another invocation sees",
34165
34165
  "explicitId": "openwop.requirement.0173.pack-isolation.isolated-context",
34166
34166
  "citations": [
@@ -38110,7 +38110,7 @@
38110
38110
  {
38111
38111
  "id": "openwop.it.v2-webhook-durable-delivery.an-exhausted-delivery-is-dead-lettered-never-dropped",
38112
38112
  "file": "v2-webhook-durable-delivery.test.ts",
38113
- "line": 430,
38113
+ "line": 435,
38114
38114
  "title": "an exhausted delivery is dead-lettered, never dropped",
38115
38115
  "explicitId": "openwop.requirement.0173.webhook-durable-delivery.dead-letter",
38116
38116
  "citations": [
@@ -38149,7 +38149,7 @@
38149
38149
  {
38150
38150
  "id": "openwop.it.v2-webhook-durable-delivery.the-dead-letter-read-is-served-and-its-records-carry-no-payload",
38151
38151
  "file": "v2-webhook-durable-delivery.test.ts",
38152
- "line": 539,
38152
+ "line": 544,
38153
38153
  "title": "the dead-letter read is served and its records carry no payload",
38154
38154
  "explicitId": "openwop.requirement.0188.dead-letter-read",
38155
38155
  "citations": [
@@ -38162,7 +38162,7 @@
38162
38162
  {
38163
38163
  "id": "openwop.it.v2-webhook-durable-delivery.after-unregister-the-dead-letter-read-answers-404-not-found-as-for-a-subscriptio",
38164
38164
  "file": "v2-webhook-durable-delivery.test.ts",
38165
- "line": 560,
38165
+ "line": 565,
38166
38166
  "title": "after unregister, the dead-letter read answers 404 not_found, as for a subscription that never existed",
38167
38167
  "explicitId": "openwop.requirement.0217.dead-letter-read-after-unregister",
38168
38168
  "citations": [
@@ -38189,7 +38189,7 @@
38189
38189
  {
38190
38190
  "id": "openwop.it.v2-webhook-durable-delivery.a-dead-letter-record-carries-no-delivered-payload",
38191
38191
  "file": "v2-webhook-durable-delivery.test.ts",
38192
- "line": 593,
38192
+ "line": 598,
38193
38193
  "title": "a dead-letter record carries no delivered payload",
38194
38194
  "explicitId": "openwop.requirement.0188.dead-letter-content-free",
38195
38195
  "citations": [
@@ -38207,7 +38207,7 @@
38207
38207
  {
38208
38208
  "id": "openwop.it.v2-webhook-durable-delivery.every-logged-event-of-a-subscribed-type-reaches-a-healthy-receiver-at-least-once",
38209
38209
  "file": "v2-webhook-durable-delivery.test.ts",
38210
- "line": 688,
38210
+ "line": 693,
38211
38211
  "title": "every logged event of a subscribed type reaches a healthy receiver at least once within the retry bound",
38212
38212
  "explicitId": "openwop.requirement.0173.webhook-delivery-complete",
38213
38213
  "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.45.26",
5
- "corpusTag": "v2.45.26",
4
+ "version": "2.45.28",
5
+ "corpusTag": "v2.45.28",
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": "d3b59eb1f87f42ba118bdbbdfb74e6f499081d8d5ed5fba3b06d5ae561bc90bd",
13
- "api/v2/asyncapi.yaml": "dc2ed7cd47e0ca326e89badc1f0b0c3a64b2030859559786c711230e5e7474f1",
14
- "api/v2/openapi.yaml": "3fbb8672105a847d457e13cde09bf65b02b02163cf1082f08b3cec68088a416a",
12
+ "api/seams-v2.yaml": "1408a344c243a2a54de2f19b839efd2a9ad45ffa94ab72c2b3e352cbce2804f3",
13
+ "api/v2/asyncapi.yaml": "84aa6ad5580f097da601bb257a859c8c4ef745051c77d39b5de222c59734a89b",
14
+ "api/v2/openapi.yaml": "3a85fa3b6d637123554921da4e003a69aabe2c1dc3144b0dd090b68cf0301553",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "276fc1d51eb08e14e9127b9fd27761d7046ee33c12000f3a64a801322db8bf58",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -208,7 +208,7 @@
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": "cd739e168fa36dd811fd9ed1f506e0716c592e7a61c0d12164f82732ca366177",
211
+ "spec/v1/core-standard-manifest.json": "54ae669965b91b6526ce05a6dcc4ce7bbf419dba030e9f23edeb174b1806b3c2",
212
212
  "spec/v1/deprecations.json": "e723bc11d7272c71789ad1303bb7f21809a8a024abf57594f9d5c35dc199ece9",
213
213
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
214
214
  "spec/v1/end-of-support.json": "35f0bf3b70fe78b16e2a2fd81a153fc27e703f6c16376c6fe7cdf1f4f2c4e6d4",
@@ -313,9 +313,9 @@
313
313
  "spec/v2/path-manifest.json": "7b5679a4dad98c80d855361d88a98c8a4e045af15e310862a05b285b815dc9fc",
314
314
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
315
315
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
316
- "spec/v2/release.json": "7ce8ba7234bfe7eff4c767cc0aa4ddea0495fae08212fe98f63ae312cab06c74",
316
+ "spec/v2/release.json": "1001b6d3376418eda319c7bbd6a1f77f45f39d3574df0b977ca474c1f4f3622c",
317
317
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
318
- "spec/v2/surface-baseline.json": "da9da09f5133be09b75820d138c5b4422365b2b8d113597c7fa89c99913069b3"
318
+ "spec/v2/surface-baseline.json": "9553399d274a2d244ba5ac4de7ccc97b6d7ade51ebb92303938fe684d4cb4d2f"
319
319
  },
320
- "corpusCommit": "906642cfb7cea6d4be59f0471866f8ffa3e4c702"
320
+ "corpusCommit": "19b9fd8ea3ae511f8d1b461e99d55564db3857e0"
321
321
  }
@@ -38,7 +38,7 @@ import { isFixtureAdvertised } from '../lib/fixtures.js';
38
38
  import { softSkip } from '../lib/soft-skip.js';
39
39
  import { SCHEMAS_DIR } from '../lib/paths.js';
40
40
  import { req } from '../lib/requirement-ids.js';
41
- import { SEAMS_PREFIX } from '../lib/seams.js';
41
+ import { SEAMS_PREFIX, seamsProfileAdvertised } from '../lib/seams.js';
42
42
  import { unservedDestination } from '../lib/scoped-receiver.js';
43
43
  import { errorInfos, normaliseErrorData, isOpenwopEnvelope, A2A_ERROR_DOMAIN } from '../lib/a2a-error-info.js';
44
44
 
@@ -513,6 +513,7 @@ describe('RFC 0208 — v2-a2a-operation-map (host as A2A 1.0 server, gated on a2
513
513
  const oauth = await familyAdvertised('oauth');
514
514
  if (oauth?.['credentialInterrupt'] !== true) return softSkip('inapplicable', 'oauth.credentialInterrupt is not advertised — the host raises no credential interrupt to project (RFC 0199 §C.1)');
515
515
  if (!isFixtureAdvertised('conformance-credential')) return softSkip('blocked', 'fixture conformance-credential is not in the advertised fixtures[]');
516
+ if (!seamsProfileAdvertised(await v2Discovery().catch(() => null))) return softSkip('inapplicable', 'a fresh Subject is minted through the seams profile (credential mint seam) — conformance.seamsProfile is not openwop-conformance-seams-v2');
516
517
  // A fresh Subject, so no credential another scenario acquired satisfies the node.
517
518
  const minted = await driver.post(`${SEAMS_PREFIX}/sample/auth/credential/mint`, { lane: 'api-key' }).catch(() => null);
518
519
  const bearer = (minted?.json as { credential?: unknown } | undefined)?.credential;
@@ -59,7 +59,8 @@
59
59
  * The quorum leg presents distinct votes on one bearer through the resume
60
60
  * value's `voter` member, as `interrupt-quorum-resolution` does at major 1. A
61
61
  * host that counts both votes as one principal cannot be driven to a majority
62
- * this way; that records `blocked`, not a failure.
62
+ * this way: that records `blocked` on a host advertising the seams profile, and
63
+ * `inapplicable` on one without it, which rightly counts only the bearer.
63
64
  *
64
65
  * @see spec/v2/core/interrupt.md §Rejection
65
66
  * @see spec/v2/errors.json approval_rejected
@@ -68,7 +69,8 @@
68
69
 
69
70
  import { describe, it, expect } from 'vitest';
70
71
  import { driver, type OpenWOPResponse } from '../lib/driver.js';
71
- import { gateFamily } from '../lib/v2.js';
72
+ import { gateFamily, v2Discovery } from '../lib/v2.js';
73
+ import { seamsProfileAdvertised } from '../lib/seams.js';
72
74
  import { isFixtureAdvertised } from '../lib/fixtures.js';
73
75
  import { readErrorCode } from '../lib/error-envelope.js';
74
76
  import { softSkip } from '../lib/soft-skip.js';
@@ -190,6 +192,10 @@ describe('RFC 0223 — v2-approval-reject-disposition (gated on interrupt + conf
190
192
  const terminal = await waitStatus(s.runId, TERMINAL, 10_000);
191
193
  if (between === 'waiting-approval' && terminal?.['status'] === 'waiting-approval') {
192
194
  await http(() => driver.post(`/runs/${enc(s.runId)}/cancel`, {}));
195
+ // `voter` is a suite convention, not a protocol field: a host without the
196
+ // seams profile rightly counts the bearer, and one bearer cannot cause a
197
+ // majority there. Only a seams host that ignores it is `blocked`.
198
+ if (!seamsProfileAdvertised(await v2Discovery().catch(() => null))) return softSkip('inapplicable', 'the host counted both votes as one approver: one bearer, and the resume value\'s voter stand-in is a seams-profile convention this seam-free host rightly ignores; a majority needs distinct principals');
193
199
  return softSkip('blocked', 'the host counted both votes as one approver (one bearer; the resume value\'s voter was not honoured), so a majority could not be caused');
194
200
  }
195
201
  expect(between, req(id, DOC, 'one reject of three under majority does not decide the gate: the run MUST stay waiting-approval')).toBe('waiting-approval');
@@ -34,7 +34,7 @@ import { loadEnv } from '../lib/env.js';
34
34
  import { softSkip } from '../lib/soft-skip.js';
35
35
  import { SCHEMAS_DIR } from '../lib/paths.js';
36
36
  import { req } from '../lib/requirement-ids.js';
37
- import { SEAMS_PREFIX } from '../lib/seams.js';
37
+ import { SEAMS_PREFIX, seamsProfileAdvertised } from '../lib/seams.js';
38
38
 
39
39
  export const HOST_CALLBACK_NOT_REQUIRED = 'the suite is the MCP client: every leg POSTs JSON-RPC to the mount the host advertises in mcp.serverUrls; nothing harness-hosted is handed to the host';
40
40
 
@@ -308,6 +308,7 @@ describe('RFC 0208 — v2-mcp-mount-map (host as MCP 2026-07-28 server, gated on
308
308
  if (oauth?.['credentialInterrupt'] !== true) return softSkip('inapplicable', 'oauth.credentialInterrupt is not advertised — the host raises no credential interrupt to bridge (RFC 0199 §C.1)');
309
309
  const missing = needFixtures(['conformance-credential']);
310
310
  if (missing) return softSkip('blocked', missing);
311
+ if (!seamsProfileAdvertised(await v2Discovery().catch(() => null))) return softSkip('inapplicable', 'a fresh Subject is minted through the seams profile (credential mint seam) — conformance.seamsProfile is not openwop-conformance-seams-v2');
311
312
  const minted = await driver.post(`${SEAMS_PREFIX}/sample/auth/credential/mint`, { lane: 'api-key' }).catch(() => null);
312
313
  const bearer = (minted?.json as { credential?: unknown } | undefined)?.credential;
313
314
  if (typeof bearer !== 'string') return softSkip('blocked', 'the credential mint seam did not mint a fresh Subject — a Subject that may already hold a credential makes the leg vacuous');
@@ -116,7 +116,7 @@ describe('RFC 0175 §D.2 — minimum-version-refused (gated on a2a/mcp + seams)'
116
116
  if (!doc) return softSkip('blocked', 'discovery unreachable');
117
117
  const facet = await familyAdvertised('mcp');
118
118
  if (!facet) return softSkip('inapplicable', 'mcp facet not advertised — no floor to enforce');
119
- if (!seamsProfileAdvertised(doc)) return softSkip('blocked', 'the below-floor exchange is driven through the seams profile — conformance.seamsProfile is not openwop-conformance-seams-v2');
119
+ if (!seamsProfileAdvertised(doc)) return softSkip('inapplicable', 'the below-floor exchange is driven through the seams profile — conformance.seamsProfile is not openwop-conformance-seams-v2');
120
120
  const floor = String(facet['minimumRevision']);
121
121
  if (!(MCP_LOWEST < floor)) {
122
122
  return softSkip('blocked', `the advertised mcp.minimumRevision (${floor}) is at or below the lowest revision the suite server can offer (${MCP_LOWEST}) — nothing below the floor can be offered`);
@@ -119,7 +119,7 @@ describe('RFC 0175 §D.1 — negotiation-authenticated (gated on a2a/mcp + seams
119
119
  if (!doc) return softSkip('blocked', 'discovery unreachable');
120
120
  const facet = await familyAdvertised('mcp');
121
121
  if (!facet) return softSkip('inapplicable', 'mcp facet not advertised — no negotiation to authenticate');
122
- if (!seamsProfileAdvertised(doc)) return softSkip('blocked', 'the unauthenticated exchange is driven through the seams profile — conformance.seamsProfile is not openwop-conformance-seams-v2');
122
+ if (!seamsProfileAdvertised(doc)) return softSkip('inapplicable', 'the unauthenticated exchange is driven through the seams profile — conformance.seamsProfile is not openwop-conformance-seams-v2');
123
123
  const preferred = String(facet['preferredVersion']);
124
124
  if (!(MCP_LOWER < preferred)) return softSkip('blocked', `mcp.preferredVersion is ${preferred}, at or below the lowest revision the suite server can offer (${MCP_LOWER}) — no lower revision can be offered`);
125
125
  const server = new McpFakeServer({ protocolVersions: ['2025-06-18'] });
@@ -33,7 +33,7 @@ import { describe, it, expect } from 'vitest';
33
33
  import { driver } from '../lib/driver.js';
34
34
  import { v2Discovery, gateFamily } from '../lib/v2.js';
35
35
  import { seamsProfileAdvertised, SEAMS_PREFIX } from '../lib/seams.js';
36
- import { softSkip, seamAbsent } from '../lib/soft-skip.js';
36
+ import { softSkip, seamAbsent, type SoftSkipKind } from '../lib/soft-skip.js';
37
37
  import { req } from '../lib/requirement-ids.js';
38
38
 
39
39
  const ISOLATION_MODELS = ['wasm', 'process', 'container', 'vm'];
@@ -55,12 +55,19 @@ async function gated(): Promise<Record<string, unknown> | null> {
55
55
  return sandbox;
56
56
  }
57
57
 
58
- /** Drive one misbehaving typeId through the sandbox seam; null (reason recorded) when the seam is absent. */
58
+ /**
59
+ * The kind `invoke` recorded when it returned null. A leg returns this kind, not
60
+ * a fixed `blocked`: a seam-free host records `inapplicable` here, and a second
61
+ * `blocked` on top of it outranked that and denied the bundle certification.
62
+ */
63
+ let invokeSkip: SoftSkipKind = 'blocked';
64
+
65
+ /** Drive one misbehaving typeId through the sandbox seam; null (reason recorded, kind in `invokeSkip`) when the seam is absent. */
59
66
  async function invoke(typeId: string, extra: Record<string, unknown> = {}): Promise<InvokeResult | null> {
60
67
  const doc = await discovery();
61
- if (!doc || !seamsProfileAdvertised(doc)) { softSkip('inapplicable', `the ${typeId} leg is seam-driven — seams profile (conformance.seamsProfile = openwop-conformance-seams-v2) not advertised`); return null; }
68
+ if (!doc || !seamsProfileAdvertised(doc)) { invokeSkip = 'inapplicable'; softSkip('inapplicable', `the ${typeId} leg is seam-driven — seams profile (conformance.seamsProfile = openwop-conformance-seams-v2) not advertised`); return null; }
62
69
  const res = await driver.post(INVOKE, { typeId, ...extra });
63
- if (res.status === 404 || res.status === 403 || res.status === 405) { seamAbsent(`host advertises sandbox but ${INVOKE} answered ${res.status} — the ${typeId} leg is unobservable (host-sample-test-seams.md §8)`); return null; }
70
+ if (res.status === 404 || res.status === 403 || res.status === 405) { invokeSkip = 'blocked'; seamAbsent(`host advertises sandbox but ${INVOKE} answered ${res.status} — the ${typeId} leg is unobservable (host-sample-test-seams.md §8)`); return null; }
64
71
  // The same id is ALSO asserted by its own `it` above, and that is the copy the
65
72
  // registry can see: `generate-requirement-registry.mjs` harvests `req(…)` only
66
73
  // within an `it`, so this helper-level call alone left the id out of
@@ -108,58 +115,58 @@ describe('RFC 0173 §B — pack-isolation (gated on sandbox)', () => {
108
115
  it('node-pack-sandbox-fs-gated: a host filesystem read is refused', async () => {
109
116
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
110
117
  const r = await invoke('misbehave.fs-escape-read');
111
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
118
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
112
119
  expectEscape('openwop.requirement.0173.pack-isolation.fs-read', r, 'host-fs-escape', 'node-pack-sandbox-fs-gated');
113
120
  });
114
121
 
115
122
  it('node-pack-sandbox-fs-gated: a host filesystem write is refused', async () => {
116
123
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
117
124
  const r = await invoke('misbehave.fs-escape-write');
118
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
125
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
119
126
  expectEscape('openwop.requirement.0173.pack-isolation.fs-write', r, 'host-fs-escape', 'node-pack-sandbox-fs-gated');
120
127
  });
121
128
 
122
129
  it('node-pack-sandbox-no-env: the host environment does not leak', async () => {
123
130
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
124
131
  const r = await invoke('misbehave.env-leak');
125
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
132
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
126
133
  expectEscape('openwop.requirement.0173.pack-isolation.no-env', r, 'host-env-leak', 'node-pack-sandbox-no-env');
127
134
  });
128
135
 
129
136
  it('node-pack-sandbox-network-gated: ungated network egress is refused', async () => {
130
137
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
131
138
  const r = await invoke('misbehave.network-escape');
132
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
139
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
133
140
  expectEscape('openwop.requirement.0173.pack-isolation.network', r, 'network-escape', 'node-pack-sandbox-network-gated');
134
141
  });
135
142
 
136
143
  it('node-pack-sandbox-no-process: host process access is refused', async () => {
137
144
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
138
145
  const r = await invoke('misbehave.process-escape');
139
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
146
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
140
147
  expectEscape('openwop.requirement.0173.pack-isolation.no-process', r, 'host-process-escape', 'node-pack-sandbox-no-process');
141
148
  });
142
149
 
143
150
  it('node-pack-sandbox-timeout: exceeding wallClockLimitMs fails with sandbox_timeout', async () => {
144
151
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
145
152
  const r = await invoke('misbehave.timeout');
146
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
153
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
147
154
  expect(r.error?.code, req('openwop.requirement.0173.pack-isolation.timeout', 'security-defaults.md §Sandbox isolation', 'node-pack-sandbox-timeout: a wall-clock overrun MUST fail with sandbox_timeout')).toBe('sandbox_timeout');
148
155
  });
149
156
 
150
157
  it('node-pack-sandbox-memory-cap: exceeding memoryLimitBytes fails with sandbox_memory_exceeded', async () => {
151
158
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
152
159
  const r = await invoke('misbehave.memory-bomb');
153
- if (!r) return softSkip('blocked', 'seam unavailable (reason recorded above)');
160
+ if (!r) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
154
161
  expect(r.error?.code, req('openwop.requirement.0173.pack-isolation.memory-cap', 'security-defaults.md §Sandbox isolation', 'node-pack-sandbox-memory-cap: a memory overrun MUST fail with sandbox_memory_exceeded')).toBe('sandbox_memory_exceeded');
155
162
  });
156
163
 
157
164
  it('node-pack-sandbox-isolated-context: a pack cannot mutate state another invocation sees', async () => {
158
165
  if (!(await gated())) return softSkip('inapplicable', 'gate not met (reason recorded above)');
159
166
  const first = await invoke('misbehave.cross-pack-mutate');
160
- if (!first) return softSkip('blocked', 'seam unavailable (reason recorded above)');
167
+ if (!first) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
161
168
  const second = await invoke('misbehave.cross-pack-mutate');
162
- if (!second) return softSkip('blocked', 'seam unavailable (reason recorded above)');
169
+ if (!second) return softSkip(invokeSkip, 'seam leg not driven (reason recorded above)');
163
170
  for (const r of [first, second]) {
164
171
  expect(r.error, req('openwop.requirement.0173.pack-isolation.isolated-context', 'host-sample-test-seams.md §8', 'misbehave.cross-pack-mutate is not a failure mode — it MUST return a result')).toBeUndefined();
165
172
  expect(
@@ -403,18 +403,23 @@ describe('RFC 0173 §B — webhook-durable-delivery (gated on webhooks)', () =>
403
403
  // Unfailable-leg audit (2026-09-26): `> 0 ms` alone let a host advertising
404
404
  // `backoff: exponential` that retried in a tight loop (any constant
405
405
  // spacing, even a few ms) pass. An exponential backoff MUST widen, so with
406
- // three or more attempts the second gap is at least 1.5x the first (not
407
- // 2x: jitter tolerance). Only the first two gaps are compared, so a host
408
- // whose schedule hits its cap later is not convicted. `fixed` gets no
409
- // equivalent: its base interval is not on the wire (see the comment above
410
- // the `!retried` branch), so there is no floor to measure against.
406
+ // three or more attempts the second gap is wider than the first by at least
407
+ // a quarter of it. Not 1.5x: each gap also carries a constant c, the
408
+ // receiver round trip a host waits on before scheduling the retry, so the
409
+ // gaps are B + c and 2B + c, and a public cut's tunnel (c ≈ 270 ms
410
+ // against a 500 ms base, 2026-10-08) put a correct host at 1.43x. A
411
+ // constant spacing still fails: its gaps differ only by jitter. Only the
412
+ // first two gaps are compared, so a host whose schedule hits its cap
413
+ // later is not convicted. `fixed` gets no equivalent: its base interval
414
+ // is not on the wire (see the comment above the `!retried` branch), so
415
+ // there is no floor to measure against.
411
416
  if (policy.backoff === 'exponential' && times.length >= 3) {
412
417
  const gap1 = times[1]! - times[0]!;
413
418
  const gap2 = times[2]! - times[1]!;
414
419
  expect(
415
- gap2,
416
- req('openwop.requirement.0173.webhook-durable-delivery', 'webhooks.md §Durability', `retry attempts for ${key} MUST follow the advertised exponential backoff — the second gap (${gap2}ms) MUST be at least 1.5x the first (${gap1}ms); a constant spacing is not exponential`),
417
- ).toBeGreaterThanOrEqual(1.5 * gap1);
420
+ gap2 - gap1,
421
+ req('openwop.requirement.0173.webhook-durable-delivery', 'webhooks.md §Durability', `retry attempts for ${key} MUST follow the advertised exponential backoff — the second gap (${gap2}ms) MUST exceed the first (${gap1}ms) by at least a quarter of it; a constant spacing is not exponential`),
422
+ ).toBeGreaterThanOrEqual(0.25 * gap1);
418
423
  }
419
424
  }
420
425
  } else {