@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 +15 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +26 -26
- package/schemas/CORPUS-STAMP.json +9 -9
- package/src/scenarios/v2-a2a-operation-map.test.ts +2 -1
- package/src/scenarios/v2-approval-reject-disposition.test.ts +8 -2
- package/src/scenarios/v2-mcp-mount-map.test.ts +2 -1
- package/src/scenarios/v2-minimum-version-refused.test.ts +1 -1
- package/src/scenarios/v2-negotiation-authenticated.test.ts +1 -1
- package/src/scenarios/v2-pack-isolation.test.ts +20 -13
- package/src/scenarios/v2-webhook-durable-delivery.test.ts +13 -8
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`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.45.
|
|
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.
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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.
|
|
5
|
-
"corpusTag": "v2.45.
|
|
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": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
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": "
|
|
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": "
|
|
316
|
+
"spec/v2/release.json": "1001b6d3376418eda319c7bbd6a1f77f45f39d3574df0b977ca474c1f4f3622c",
|
|
317
317
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
318
|
-
"spec/v2/surface-baseline.json": "
|
|
318
|
+
"spec/v2/surface-baseline.json": "9553399d274a2d244ba5ac4de7ccc97b6d7ade51ebb92303938fe684d4cb4d2f"
|
|
319
319
|
},
|
|
320
|
-
"corpusCommit": "
|
|
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
|
|
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('
|
|
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('
|
|
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
|
-
/**
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
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
|
|
417
|
-
).toBeGreaterThanOrEqual(
|
|
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 {
|