@openwop/openwop-conformance 2.44.0 → 2.44.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 +9 -0
- package/README.md +1 -1
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +5 -5
- package/schemas/CORPUS-STAMP.json +17 -16
- package/src/lib/webhook-retry-window.ts +22 -2
- package/src/scenarios/v2-webhook-durable-delivery.test.ts +8 -3
- package/src/scenarios/v2-webhook-message-id-stable.test.ts +2 -2
- package/src/scenarios/v2-webhook-unregister-stops-delivery.test.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.44.1] — 2026-09-28 — the webhook retry legs outlast an advertised maxElapsedMs, and four families get v2 normative homes
|
|
4
|
+
|
|
5
|
+
- **The 2.44.1 cycle opens.** `workspace`, `content`, `triggerBridge` and `uiPlugins` get v2 normative homes (RFC 0189), which changes the packed `@openwop/spec-artifacts` tree (`spec/v2/core/host-services.md`, `i18n.md`, `webhooks.md`, `packs.md`, `spec/v2/declaration.json`) after `v2.44.0` was tagged. No scenario changes: the rules are restated from the v1 text, none added or strengthened.
|
|
6
|
+
- New `spec/v2/core/storage.md` also changes the packed tree (`spec/v2/core/storage.md`, `spec/v2/core/overview.md`, `spec/v2/README.md`, `spec/v2/declaration.json`). It restates the v1 rules for `fs`, `kvStorage`, `tableStorage`, `sql`, `nosql`, `vectorStore`, `searchIndex`, `blobStorage` and `cache` (RFC 0189 §D). No rule and no wire change, and no scenario change.
|
|
7
|
+
- **A webhook retry leg is no longer killed before its own wait elapses** (RFC 0225 follow-up). 2.44.0 made `retryWaitFor` follow an advertised `retryPolicy.maxElapsedMs` plus the 30 s grace (up to 1 h), but the test timeouts were still fixed at registration from the operator cap (90 s by default). A host advertising `maxElapsedMs: 600000` waits 630 s, and its dead-letter leg died at 210 s ("Test timed out in 210000ms"). MyndHyve `00821-qec` recorded `0173.webhook-durable-delivery.dead-letter` and `0188.dead-letter-content-free` `executed-fail` after both passed on 2.43.1. It is the 2.0.2 defect again: a wait longer than the timeout governing it. vitest fixes a timeout before discovery is read, so every test that awaits a retry window now takes its timeout from `retryTestTimeoutMs(waits, slack)`, derived from the largest wait `retryWaitFor` can return (`MAX_RETRY_WAIT_MS`, the 1 h clamp). The timeout is a backstop: each wait is still bounded by its own window, so a host finishes no later than before. Fixed in `v2-webhook-durable-delivery` (retry leg; dead-letter legs), `v2-webhook-unregister-stops-delivery` and `v2-webhook-message-id-stable`. Workaround on 2.44.0: `OPENWOP_WEBHOOK_RETRY_WAIT_MS=600000`.
|
|
8
|
+
- **A self-test holds every call site to it.** `lib/webhook-retry-window.test.ts` scans the scenarios: each `it` that awaits a retry window must register a `retryTestTimeoutMs(N, …)` timeout with N at least its number of waits, and every scenario that computes a window must be seen by the scan. Sabotage-checked: restoring any of the four cap-derived timeouts fails it.
|
|
9
|
+
- **`0215.no-head-of-line` is not this bug.** `v2-webhook-delivery-isolation` waits the fixed 20 s floor under a timeout that covers it, and never calls `retryWaitFor`.
|
|
10
|
+
- **Suite `2.44.1`**. `@openwop/spec-artifacts` moves in lockstep at the same exact pin.
|
|
11
|
+
|
|
3
12
|
## [2.44.0] — 2026-09-28 — three run-failure codes registered, a webhook retry bound hosts can advertise, and RFC 0219 Accepted
|
|
4
13
|
|
|
5
14
|
- **RFC 0219 is `Accepted`** (corpus only; no scenario change). The flip changes the packed `@openwop/spec-artifacts` tree through `spec/v1/gaps.json`: 0219 gap G6 is closed (SDK 2.5.0 sends the header), G7 is declared (`no-floor-no-refusal` needs a host with no `minClientVersion`), and G8 is transferred to #1763 (CORS).
|
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.44.
|
|
14
|
+
npm install --legacy-peer-deps @openwop/openwop-conformance@2.44.1 @openwop/spec-artifacts@2.44.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
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.44.
|
|
3
|
+
"version": "2.44.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.44.
|
|
59
|
+
"@openwop/spec-artifacts": "2.44.1"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -34360,7 +34360,7 @@
|
|
|
34360
34360
|
{
|
|
34361
34361
|
"id": "openwop.it.v2-webhook-durable-delivery.a-failed-attempt-is-retried-and-the-event-is-delivered-at-least-once",
|
|
34362
34362
|
"file": "v2-webhook-durable-delivery.test.ts",
|
|
34363
|
-
"line":
|
|
34363
|
+
"line": 263,
|
|
34364
34364
|
"title": "a failed attempt is retried and the event is delivered at least once",
|
|
34365
34365
|
"explicitId": "openwop.requirement.0173.webhook-durable-delivery",
|
|
34366
34366
|
"citations": [
|
|
@@ -34397,7 +34397,7 @@
|
|
|
34397
34397
|
{
|
|
34398
34398
|
"id": "openwop.it.v2-webhook-durable-delivery.an-exhausted-delivery-is-dead-lettered-never-dropped",
|
|
34399
34399
|
"file": "v2-webhook-durable-delivery.test.ts",
|
|
34400
|
-
"line":
|
|
34400
|
+
"line": 383,
|
|
34401
34401
|
"title": "an exhausted delivery is dead-lettered, never dropped",
|
|
34402
34402
|
"explicitId": "openwop.requirement.0173.webhook-durable-delivery.dead-letter",
|
|
34403
34403
|
"citations": [
|
|
@@ -34436,7 +34436,7 @@
|
|
|
34436
34436
|
{
|
|
34437
34437
|
"id": "openwop.it.v2-webhook-durable-delivery.the-dead-letter-read-is-served-and-its-records-carry-no-payload",
|
|
34438
34438
|
"file": "v2-webhook-durable-delivery.test.ts",
|
|
34439
|
-
"line":
|
|
34439
|
+
"line": 491,
|
|
34440
34440
|
"title": "the dead-letter read is served and its records carry no payload",
|
|
34441
34441
|
"explicitId": "openwop.requirement.0188.dead-letter-read",
|
|
34442
34442
|
"citations": [
|
|
@@ -34449,7 +34449,7 @@
|
|
|
34449
34449
|
{
|
|
34450
34450
|
"id": "openwop.it.v2-webhook-durable-delivery.after-unregister-the-dead-letter-read-answers-404-not-found-as-for-a-subscriptio",
|
|
34451
34451
|
"file": "v2-webhook-durable-delivery.test.ts",
|
|
34452
|
-
"line":
|
|
34452
|
+
"line": 512,
|
|
34453
34453
|
"title": "after unregister, the dead-letter read answers 404 not_found, as for a subscription that never existed",
|
|
34454
34454
|
"explicitId": "openwop.requirement.0217.dead-letter-read-after-unregister",
|
|
34455
34455
|
"citations": [
|
|
@@ -34476,7 +34476,7 @@
|
|
|
34476
34476
|
{
|
|
34477
34477
|
"id": "openwop.it.v2-webhook-durable-delivery.a-dead-letter-record-carries-no-delivered-payload",
|
|
34478
34478
|
"file": "v2-webhook-durable-delivery.test.ts",
|
|
34479
|
-
"line":
|
|
34479
|
+
"line": 545,
|
|
34480
34480
|
"title": "a dead-letter record carries no delivered payload",
|
|
34481
34481
|
"explicitId": "openwop.requirement.0188.dead-letter-content-free",
|
|
34482
34482
|
"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.44.
|
|
5
|
-
"corpusTag": "v2.44.
|
|
4
|
+
"version": "2.44.1",
|
|
5
|
+
"corpusTag": "v2.44.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": "69464bc0e1e71ef3b543a73d4fa4f67202343d2603c80af8033f6c3619ef1b30",
|
|
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": "6f9780f7751804b6dd9f57cb5474728326de805fcca667979f6b4f39c9c97336",
|
|
13
|
+
"api/v2/asyncapi.yaml": "ec6520792f7c4661403053003de7669430178597b5e0250ba5264b65dc82dbef",
|
|
14
|
+
"api/v2/openapi.yaml": "46314e02c9aeb4cb7a4ca0cfd5997dc24bd11c3971341e505cd0b7bef3150cd8",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
205
205
|
"spec/v1/alias-detectors.json": "29f21e0fb2c7a232a92a5c19b4cba3a86bc3b5e16f06780452f0421c66c6ec38",
|
|
206
206
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
207
|
-
"spec/v1/core-standard-manifest.json": "
|
|
207
|
+
"spec/v1/core-standard-manifest.json": "b6efb5d51e1432088bfeff049f310ae0095f3852a59e877e9d628a30782f5580",
|
|
208
208
|
"spec/v1/deprecations.json": "46df807fb4adf0aae24671c20f69975cf3496e3bc3a521cb7e6ea556bdce563c",
|
|
209
209
|
"spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
|
|
210
210
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
"spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
|
|
217
217
|
"spec/v1/operation-path-manifest.json": "fada0342815fb129c1ae61f1829bd6e34417717408ce86fcc54280f373c19e74",
|
|
218
218
|
"spec/v1/spec-gaps.json": "ea9612af55bea9dae3e82223d4f714243f8f590b0193f1188257b13491a38afc",
|
|
219
|
-
"spec/v2/README.md": "
|
|
219
|
+
"spec/v2/README.md": "377639de1dad1ed71aba6dae8d34fdb6ee954fb23106e89454fe8d29cdde6349",
|
|
220
220
|
"spec/v2/core/capabilities.md": "ad728e8a761153b55d036ece55043ff62d0982121e2e629e5e7cdf560fca3c03",
|
|
221
221
|
"spec/v2/core/conformance.md": "0cfceb926eed87055550a5c23e640210c795fc8cf9dd1b2544db0e2b3842c564",
|
|
222
222
|
"spec/v2/core/connection-packs.md": "1ee129d792c537bfe82393ccb92555d8e6ddc342b1aa781a13f101b27ec38171",
|
|
@@ -225,28 +225,29 @@
|
|
|
225
225
|
"spec/v2/core/events.md": "604abb6f9e2259a04103e1cb76d728712c3701c0d84dd915d4dc2949c912ee5e",
|
|
226
226
|
"spec/v2/core/form-content-packs.md": "02931227899676fd4581a27017be2a6c4953904717be9b81c29d12f87ab3917d",
|
|
227
227
|
"spec/v2/core/headers.md": "5cb1624fa63acc81992b6a6d842ec392cf7271aaf2f65d1f83c8c4fa286e6730",
|
|
228
|
-
"spec/v2/core/host-services.md": "
|
|
229
|
-
"spec/v2/core/i18n.md": "
|
|
228
|
+
"spec/v2/core/host-services.md": "8b920bf2e9335ab04452068429b0c37d6eb8872b380a4d53368f2600d4afa241",
|
|
229
|
+
"spec/v2/core/i18n.md": "2d5a67bd3b9b956d1144acab2fbc3646cdb50b3621f97d772087eaa953798286",
|
|
230
230
|
"spec/v2/core/idempotency.md": "2084d756789c1faf5121593fb39e67ca36a4c227178e175004742e403178ea49",
|
|
231
231
|
"spec/v2/core/identity.md": "2dceb1c2697015e4a4fa8a9b28f4fbd25fa44a76ca5a919c752e2a8163c819db",
|
|
232
232
|
"spec/v2/core/interop.md": "e72e25dff864c83b2980d7366b34054cffa072af0225821199f260f6228d260e",
|
|
233
233
|
"spec/v2/core/interrupt.md": "4c825634acac72900ebd3c5ac5941f55648fa33d2df92913ef16371dcb700a34",
|
|
234
234
|
"spec/v2/core/node-pack-runtimes.md": "e1088cb69b8c3c67256c494063328916c2980ab1aa97a7063de25bab82fdb1e5",
|
|
235
235
|
"spec/v2/core/oauth.md": "c96483a44b62978c596945fbb7d7c863cf6056cd980fdb4cf4e97555e3e10b72",
|
|
236
|
-
"spec/v2/core/overview.md": "
|
|
237
|
-
"spec/v2/core/packs.md": "
|
|
236
|
+
"spec/v2/core/overview.md": "c8c3bedb8e02dd8ce244b8a7a75d23af1c30dc92a343af912489424a89674d70",
|
|
237
|
+
"spec/v2/core/packs.md": "5929e708bd4e79f6e1beea570af583c80f3b63bb21dec78a5df03c235e73364a",
|
|
238
238
|
"spec/v2/core/persistence.md": "711874f74cb08b03cd118d683056db10a063517762fcea73ec70feb5d983e6a4",
|
|
239
239
|
"spec/v2/core/portability.md": "87c4327f674f714fda6217fd79b99d2ba219ad47dc02f9751bb56c200fb2c17a",
|
|
240
240
|
"spec/v2/core/replay.md": "4d379494057746c6883f6047972e93e621863b998ed291b9a691d3aa57c1ff43",
|
|
241
241
|
"spec/v2/core/runs.md": "55e21292ae4b41d0d104199a6c2634ecb3f787c4a671f9ebc37084be38ceef03",
|
|
242
242
|
"spec/v2/core/security-defaults.md": "153884da7a2d54c30c81b5e649cfdef31e151b3a2a19dee1bc5a16c35d60a3aa",
|
|
243
|
+
"spec/v2/core/storage.md": "598ad4f3775c297c698a20daf72a7d15a6836c1231ddbca5961865c704f765b5",
|
|
243
244
|
"spec/v2/core/tool-catalog.md": "d16a9004418719c48301804aefa4d7ab6f41112a4ed83727f552aa1b92b418b0",
|
|
244
245
|
"spec/v2/core/versioning.md": "e3b3ab6d76e51035039a32173e9b412628a52c8fe7b16687ff58a2d0d983faa0",
|
|
245
|
-
"spec/v2/core/webhooks.md": "
|
|
246
|
+
"spec/v2/core/webhooks.md": "d2d33c6be777f217ade3e15dd369849e5c6ce67e1a4417216f0aa6ec5bdecc56",
|
|
246
247
|
"spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
|
|
247
248
|
"spec/v2/corrections.json": "59f647f4d22477f6c2b06dcf913b682dde5d51dd5e607a5d0cf648ab60864d6e",
|
|
248
249
|
"spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
|
|
249
|
-
"spec/v2/declaration.json": "
|
|
250
|
+
"spec/v2/declaration.json": "56c85c6d49f93d08d3bd48ccf1c049191cfbc4d4c22a1cd5202c1b6252759aca",
|
|
250
251
|
"spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
|
|
251
252
|
"spec/v2/errors.json": "fa650cbbdf25a140fdaf82ec5269f95b4585ac4bdde1fa6b0b3ffe48a3f7943c",
|
|
252
253
|
"spec/v2/errors.schema.json": "47404ad53e6147cf9017b813483712466573e419fb33e6c4b6c12d3e7deba1bc",
|
|
@@ -298,9 +299,9 @@
|
|
|
298
299
|
"spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
|
|
299
300
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
300
301
|
"spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
|
|
301
|
-
"spec/v2/release.json": "
|
|
302
|
+
"spec/v2/release.json": "af448fd044ab5b990a21945905f109dbbb9b0df9ab7f1105e79aa7d1e0f35a33",
|
|
302
303
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
303
|
-
"spec/v2/surface-baseline.json": "
|
|
304
|
+
"spec/v2/surface-baseline.json": "2f79e3283946dd8a401db039109d403b9c386e6b91a4780cbcb1440e8d3a6ffe"
|
|
304
305
|
},
|
|
305
|
-
"corpusCommit": "
|
|
306
|
+
"corpusCommit": "bf414c875dba212893f0230d44f2b4a2e226311d"
|
|
306
307
|
}
|
|
@@ -65,9 +65,29 @@ export function retryWaitCapMs(env: Record<string, string | undefined> = process
|
|
|
65
65
|
export function retryWaitFor(policy: AdvertisedRetryPolicy | null, capMs: number): number {
|
|
66
66
|
if (policy === null) return RETRY_WAIT_FLOOR_MS;
|
|
67
67
|
const bound = advertisedMaxElapsedMs(policy);
|
|
68
|
-
if (bound !== null) return Math.min(
|
|
68
|
+
if (bound !== null) return Math.min(MAX_RETRY_WAIT_MS, Math.max(bound + ADVERTISED_BOUND_GRACE_MS, capMs));
|
|
69
69
|
const backoff = String(policy.backoff ?? '');
|
|
70
|
-
return backoff === 'exponential' || backoff === 'fixed' ? capMs : RETRY_WAIT_FLOOR_MS;
|
|
70
|
+
return backoff === 'exponential' || backoff === 'fixed' ? Math.min(MAX_RETRY_WAIT_MS, capMs) : RETRY_WAIT_FLOOR_MS;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The longest `retryWaitFor` can return, whatever a host advertises or an
|
|
75
|
+
* operator sets (suite 2.44.1).
|
|
76
|
+
*
|
|
77
|
+
* vitest fixes a test's timeout when the test is REGISTERED, before any
|
|
78
|
+
* discovery document is read, so a timeout cannot follow the host's advertised
|
|
79
|
+
* `maxElapsedMs`. 2.44.0 derived them from the cap alone (90 s by default):
|
|
80
|
+
* a host advertising 600000 had its dead-letter leg killed at 210 s ("Test
|
|
81
|
+
* timed out in 210000ms"), before its own bound plus the grace elapsed. That is
|
|
82
|
+
* the 2.0.2 defect again: a wait longer than the timeout that governs it. The
|
|
83
|
+
* timeout is only a backstop — every wait is bounded by its own window — so it
|
|
84
|
+
* is derived from the largest window instead.
|
|
85
|
+
*/
|
|
86
|
+
export const MAX_RETRY_WAIT_MS = MAX_RETRY_WAIT_CAP_MS;
|
|
87
|
+
|
|
88
|
+
/** The timeout for a test that runs `waits` sequential retry waits, plus `slackMs` for the HTTP round trips around them. */
|
|
89
|
+
export function retryTestTimeoutMs(waits: number, slackMs: number): number {
|
|
90
|
+
return waits * MAX_RETRY_WAIT_MS + slackMs;
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
/** What a row says when its window closed before the host's schedule did. Computed, so the numbers a host reads are the ones the run used. */
|
|
@@ -35,7 +35,7 @@ import { absenceIsUnmeasured, noDeliveryCause, startScopedReceiver, type ScopedR
|
|
|
35
35
|
import { readErrorCode } from '../lib/error-envelope.js';
|
|
36
36
|
import { blockedDespiteAssertions, softSkip } from '../lib/soft-skip.js';
|
|
37
37
|
import { req } from '../lib/requirement-ids.js';
|
|
38
|
-
import { pastAdvertisedBound, retryWaitCapMs, retryWaitFor, windowClosedNote } from '../lib/webhook-retry-window.js';
|
|
38
|
+
import { pastAdvertisedBound, retryTestTimeoutMs, retryWaitCapMs, retryWaitFor, windowClosedNote } from '../lib/webhook-retry-window.js';
|
|
39
39
|
|
|
40
40
|
export const REQUIRES_HOST_CALLBACK = 'the host POSTs and retries webhook deliveries to the suite-owned scoped receiver behind OPENWOP_WEBHOOK_RECEIVER_URL';
|
|
41
41
|
|
|
@@ -198,6 +198,11 @@ function retryWaitMs(doc: Record<string, unknown>): number {
|
|
|
198
198
|
* to the wait carries its own timeout, the way the advert is sourced from the
|
|
199
199
|
* constant the delivery loop reads. `WAIT_SLACK_MS` covers `waitTerminal`,
|
|
200
200
|
* registration and the HTTP round trips around the waits.
|
|
201
|
+
*
|
|
202
|
+
* 2.44.1: and the cap was the wrong wait to derive from once RFC 0225 let a
|
|
203
|
+
* host advertise `maxElapsedMs` — the wait follows the advert, the timeout is
|
|
204
|
+
* fixed at registration. So the budget is derived from the largest wait
|
|
205
|
+
* `retryWaitFor` can return (`retryTestTimeoutMs`).
|
|
201
206
|
*/
|
|
202
207
|
/**
|
|
203
208
|
* How many attempts the receiver refuses before answering 204 (suite 2.0.3).
|
|
@@ -230,9 +235,9 @@ function failFirstFor(policy: { maxAttempts?: number } | null): number {
|
|
|
230
235
|
|
|
231
236
|
const WAIT_SLACK_MS = 30_000;
|
|
232
237
|
/** One `retryWaitMs` wait (the retry leg). */
|
|
233
|
-
const RETRY_TEST_TIMEOUT_MS =
|
|
238
|
+
const RETRY_TEST_TIMEOUT_MS = retryTestTimeoutMs(1, WAIT_SLACK_MS);
|
|
234
239
|
/** Two sequential `retryWaitMs` waits (the dead-letter leg: observe a retry, then exhaust). */
|
|
235
|
-
const DEAD_LETTER_TEST_TIMEOUT_MS =
|
|
240
|
+
const DEAD_LETTER_TEST_TIMEOUT_MS = retryTestTimeoutMs(2, WAIT_SLACK_MS);
|
|
236
241
|
|
|
237
242
|
/** Register the suite receiver; null (with a note) when the host's SSRF guard refuses a loopback URL. */
|
|
238
243
|
async function register(rx: ScopedReceiver): Promise<{ webhookId: string } | null> {
|
|
@@ -33,7 +33,7 @@ import { req } from '../lib/requirement-ids.js';
|
|
|
33
33
|
import { readErrorCode } from '../lib/error-envelope.js';
|
|
34
34
|
import { blockedDespiteAssertions, softSkip } from '../lib/soft-skip.js';
|
|
35
35
|
import { hitHeader, mintWhsec, startModalReceiver, STANDARD_WEBHOOKS_ID, type ModalHit } from '../lib/webhook-receiver.js';
|
|
36
|
-
import { retryWaitCapMs, retryWaitFor } from '../lib/webhook-retry-window.js';
|
|
36
|
+
import { retryTestTimeoutMs, retryWaitCapMs, retryWaitFor } from '../lib/webhook-retry-window.js';
|
|
37
37
|
import {
|
|
38
38
|
STANDARD_WEBHOOKS_ALG,
|
|
39
39
|
deliveriesFor,
|
|
@@ -128,5 +128,5 @@ describe('RFC 0201 §C.10 — webhook-id is stable across retries, distinct acro
|
|
|
128
128
|
for (const { id } of perKey) {
|
|
129
129
|
expect(subs.includes(id), req(ID, 'RFC 0201 §C.11', 'webhook-id MUST NOT be the subscription id')).toBe(false);
|
|
130
130
|
}
|
|
131
|
-
},
|
|
131
|
+
}, retryTestTimeoutMs(1, 45_000));
|
|
132
132
|
});
|
|
@@ -45,7 +45,7 @@ import { absenceIsUnmeasured, noDeliveryCause, startScopedReceiver, type ScopedR
|
|
|
45
45
|
import { readErrorCode } from '../lib/error-envelope.js';
|
|
46
46
|
import { blockedDespiteAssertions, softSkip } from '../lib/soft-skip.js';
|
|
47
47
|
import { req } from '../lib/requirement-ids.js';
|
|
48
|
-
import { retryWaitCapMs, retryWaitFor } from '../lib/webhook-retry-window.js';
|
|
48
|
+
import { retryTestTimeoutMs, retryWaitCapMs, retryWaitFor } from '../lib/webhook-retry-window.js';
|
|
49
49
|
|
|
50
50
|
export const REQUIRES_HOST_CALLBACK = 'the host POSTs and retries webhook deliveries to the suite-owned scoped receiver behind OPENWOP_WEBHOOK_RECEIVER_URL';
|
|
51
51
|
|
|
@@ -180,5 +180,5 @@ describe('RFC 0215 §B — unregistering stops the attempts (gated on webhooks)'
|
|
|
180
180
|
if (controlLate.length === 0) {
|
|
181
181
|
return blockedDespiteAssertions(`the control subscription was not retried after the 204 + ${GRACE_MS}ms within ${windowMs}ms either, so the target's silence shows nothing — the schedule finished inside the grace or runs past the window (raise OPENWOP_WEBHOOK_RETRY_WAIT_MS above the host's backoff sum) — ${detail}`);
|
|
182
182
|
}
|
|
183
|
-
},
|
|
183
|
+
}, retryTestTimeoutMs(2, 90_000));
|
|
184
184
|
});
|