@openwop/openwop-conformance 2.44.1 → 2.44.2
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 +7 -0
- package/README.md +1 -1
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +1 -6
- package/schemas/CORPUS-STAMP.json +9 -9
- package/src/lib/delivery-isolation-verdict.ts +97 -0
- package/src/scenarios/v2-webhook-delivery-isolation.test.ts +21 -29
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.44.2] — 2026-09-28 — RFC 0215's no-head-of-line leg does not convict a late fan-out, and says when it sampled each count
|
|
4
|
+
|
|
5
|
+
- **The 2.44.2 cycle opens.** A patch: the requirement is unchanged, and one verdict branch that convicted without the contention it names becomes unjudged. `@openwop/spec-artifacts` moves in lockstep at the same exact pin.
|
|
6
|
+
- **`0215.no-head-of-line` no longer fails a host whose `run.started` fan-out is late.** The last branch failed the row as "a bounded dispatcher below the floor" whenever fewer than 8 held attempts had arrived by the time the run was terminal. MyndHyve `00821-qec` hit it twice. In the first cut no held attempt had arrived and the healthy one was delivered. In the second, 2 held attempts had arrived at terminal and 7 later, and the healthy attempt started with 7 held open and none closed. §A forbids an attempt waiting for another subscription's attempt to *finish*. None had finished, so the healthy attempt waited for nothing, and the contention §A.2 names was never established. The row is now `blocked` (unjudged) in that case. A bounded pool still fails: its healthy attempt starts only after a held attempt closes, or never while they stay open. So does a serial dispatcher, through the peak assertion.
|
|
7
|
+
- **The detail names each sampling time.** It read "2 held attempt(s) arrived (at most 7 open at once)", with the 2 sampled at terminal and the 7 at the verdict. It now reports what had arrived and was open at terminal, what had arrived by the verdict and the peak, when the healthy attempt arrived, and whether the host had closed any held attempt. It no longer names a cause the data does not show.
|
|
8
|
+
- **The verdict is a pure function with self-tests** (`lib/delivery-isolation-verdict.ts`). The tests cover pass, the contention-present fail, a 7-slot pool with a 15 s timeout (fail), a serial dispatcher (peak assertion), and both MyndHyve cuts (unjudged). Sabotage-checked: removing the new branch fails the two MyndHyve cases.
|
|
9
|
+
|
|
3
10
|
## [2.44.1] — 2026-09-28 — the webhook retry legs outlast an advertised maxElapsedMs, and four families get v2 normative homes
|
|
4
11
|
|
|
5
12
|
- **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.
|
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.2 @openwop/spec-artifacts@2.44.2
|
|
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.2",
|
|
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.2"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -34246,7 +34246,7 @@
|
|
|
34246
34246
|
{
|
|
34247
34247
|
"id": "openwop.requirement.0215.no-head-of-line",
|
|
34248
34248
|
"file": "v2-webhook-delivery-isolation.test.ts",
|
|
34249
|
-
"line":
|
|
34249
|
+
"line": 119,
|
|
34250
34250
|
"title": null,
|
|
34251
34251
|
"explicitId": "openwop.requirement.0215.no-head-of-line",
|
|
34252
34252
|
"citations": [
|
|
@@ -34273,11 +34273,6 @@
|
|
|
34273
34273
|
"requirement": null,
|
|
34274
34274
|
"interpolated": true
|
|
34275
34275
|
},
|
|
34276
|
-
{
|
|
34277
|
-
"section": "webhooks.md §Durability (RFC 0215 §A.1, §A.2)",
|
|
34278
|
-
"requirement": null,
|
|
34279
|
-
"interpolated": true
|
|
34280
|
-
},
|
|
34281
34276
|
{
|
|
34282
34277
|
"section": "webhooks.md §Durability (RFC 0215 §A.1, §A.2)",
|
|
34283
34278
|
"requirement": null,
|
|
@@ -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.2",
|
|
5
|
+
"corpusTag": "v2.44.2",
|
|
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": "f24127f3cd544f1c5a942859ffc492b438d37b1b7d7a660c631fb87f70cd24f6",
|
|
13
|
+
"api/v2/asyncapi.yaml": "3283ff1bbe962dc75e6d66d2b755d4eb7d53770ce8511f2d671b923d6ca0e51a",
|
|
14
|
+
"api/v2/openapi.yaml": "4a455ba22c81718e2842adce60e5d3c5970e6f8a55b301d9154ccc8060f6cf91",
|
|
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": "3d7fd99cd02153094723c2bf383d757c282e653526aa3b199d9bdb4b9cfd7c80",
|
|
208
208
|
"spec/v1/deprecations.json": "46df807fb4adf0aae24671c20f69975cf3496e3bc3a521cb7e6ea556bdce563c",
|
|
209
209
|
"spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
|
|
210
210
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -299,9 +299,9 @@
|
|
|
299
299
|
"spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
|
|
300
300
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
301
301
|
"spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
|
|
302
|
-
"spec/v2/release.json": "
|
|
302
|
+
"spec/v2/release.json": "28b407019929da55701a718110d440f08899646af0b74d62f6adc5542c9c1c91",
|
|
303
303
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
304
|
-
"spec/v2/surface-baseline.json": "
|
|
304
|
+
"spec/v2/surface-baseline.json": "d0540276e74001f5058dd81909100ea6ee3c1925c6ac02918418f5ec941d0661"
|
|
305
305
|
},
|
|
306
|
-
"corpusCommit": "
|
|
306
|
+
"corpusCommit": "c486eafcfa16908a7b57c98b33cfd66fdf8e599d"
|
|
307
307
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verdict of RFC 0215's `no-head-of-line` leg, as a pure function of what
|
|
3
|
+
* the leg observed (suite 2.44.2), so the rule can be tested without a host.
|
|
4
|
+
*
|
|
5
|
+
* 2.44.2 fixes two defects that MyndHyve's 2.44.x cuts surfaced:
|
|
6
|
+
*
|
|
7
|
+
* 1. **A conviction without the contention it names.** The last branch failed
|
|
8
|
+
* the row as "a bounded dispatcher below the floor" whenever fewer than
|
|
9
|
+
* `floor` held attempts had arrived by the time the run was terminal. On a
|
|
10
|
+
* host whose `run.started` fan-out arrived late, that convicted a healthy
|
|
11
|
+
* attempt that started while every held attempt that HAD arrived was still
|
|
12
|
+
* open and none had finished (MyndHyve `00821-qec`: 0 held attempts, then 2
|
|
13
|
+
* at terminal and 7 later, the healthy attempt delivered with 7 open). §A
|
|
14
|
+
* forbids an attempt waiting for another subscription's attempt to FINISH; no
|
|
15
|
+
* attempt had finished, so there was nothing it could have waited for. The
|
|
16
|
+
* contention was never established, so the row is unjudged, not failed. A
|
|
17
|
+
* bounded pool still fails: its healthy attempt starts only after a held
|
|
18
|
+
* attempt closes, or never while they stay open.
|
|
19
|
+
*
|
|
20
|
+
* 2. **A detail string that mixed two sampling times.** It read "2 held
|
|
21
|
+
* attempt(s) arrived (at most 7 open at once)": the 2 was sampled at
|
|
22
|
+
* terminal, the 7 at the verdict. The detail now names each time.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface IsolationObservation {
|
|
26
|
+
/** §A.2's floor. */
|
|
27
|
+
readonly floor: number;
|
|
28
|
+
/** When the leg saw the run terminal (the healthy delivery is due by then). */
|
|
29
|
+
readonly terminalAt: number;
|
|
30
|
+
/** When the healthy attempt arrived, or null if it did not within the window. */
|
|
31
|
+
readonly healthyAt: number | null;
|
|
32
|
+
/** Held attempts open when the healthy attempt arrived (-1 if it did not). */
|
|
33
|
+
readonly openAtHealthy: number;
|
|
34
|
+
/** Held attempts that had arrived by `terminalAt`. */
|
|
35
|
+
readonly arrivedAtTerminal: number;
|
|
36
|
+
/** Held attempts still open at `terminalAt`. */
|
|
37
|
+
readonly openAtTerminal: number;
|
|
38
|
+
/** Held attempts that had arrived by the verdict. */
|
|
39
|
+
readonly arrivedByVerdict: number;
|
|
40
|
+
/** The most held attempts open at once, up to the verdict. */
|
|
41
|
+
readonly peakOpen: number;
|
|
42
|
+
/** When the first held attempt closed (the host abandoning it), or Infinity. */
|
|
43
|
+
readonly earliestClose: number;
|
|
44
|
+
readonly windowMs: number;
|
|
45
|
+
readonly delayMs: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type IsolationVerdict =
|
|
49
|
+
/** The healthy attempt started while `floor` held attempts were open. */
|
|
50
|
+
| { readonly kind: 'pass'; readonly detail: string }
|
|
51
|
+
| { readonly kind: 'fail'; readonly message: string; readonly detail: string }
|
|
52
|
+
/** Unjudged, after asserting `peakOpen >= floor` (which fails a pool below the floor). */
|
|
53
|
+
| { readonly kind: 'blocked-after-peak'; readonly message: string; readonly detail: string }
|
|
54
|
+
| { readonly kind: 'blocked'; readonly message: string; readonly detail: string };
|
|
55
|
+
|
|
56
|
+
/** What the leg saw, each count against the time it was sampled. */
|
|
57
|
+
export function isolationDetail(o: IsolationObservation): string {
|
|
58
|
+
const at = (t: number): string => `${t - o.terminalAt}ms after the run was seen terminal`;
|
|
59
|
+
const healthy = o.healthyAt === null
|
|
60
|
+
? `the healthy attempt did not arrive within ${o.windowMs}ms of that`
|
|
61
|
+
: `the healthy attempt arrived ${at(o.healthyAt)}, with ${o.openAtHealthy} held open`;
|
|
62
|
+
const closed = Number.isFinite(o.earliestClose) ? `the first held attempt was closed by the host ${at(o.earliestClose)}` : 'no held attempt was closed by the host';
|
|
63
|
+
return `at terminal, ${o.arrivedAtTerminal} held attempt(s) had arrived and ${o.openAtTerminal} were open; by the verdict, ${o.arrivedByVerdict} had arrived, at most ${o.peakOpen} open at once; ${healthy}; ${closed}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isolationVerdict(o: IsolationObservation): IsolationVerdict {
|
|
67
|
+
const detail = isolationDetail(o);
|
|
68
|
+
const F = o.floor;
|
|
69
|
+
if (o.healthyAt !== null && o.openAtHealthy >= F) return { kind: 'pass', detail };
|
|
70
|
+
if (o.openAtTerminal >= F) {
|
|
71
|
+
// The healthy attempt waited (or never came), and the contention §A.2
|
|
72
|
+
// names was present when it fell due.
|
|
73
|
+
return { kind: 'fail', message: `with ${F} subscriptions' attempts unanswered, a further subscription's attempt MUST still start; it waited for the host to release one`, detail };
|
|
74
|
+
}
|
|
75
|
+
if (o.arrivedAtTerminal >= F || o.earliestClose < o.terminalAt) {
|
|
76
|
+
// Contention was not sustained to the due time. Either the host's own
|
|
77
|
+
// delivery timeout released the held attempts early (its choice), or it
|
|
78
|
+
// never opened F of them at once. peakOpen separates the two: below F the
|
|
79
|
+
// host never had F attempts outstanding at once, which is the defect.
|
|
80
|
+
const released = Number.isFinite(o.earliestClose) ? `${o.terminalAt - o.earliestClose}ms ` : '';
|
|
81
|
+
return { kind: 'blocked-after-peak', message: `unjudged: ${F} held attempts were open at once, but the host closed held attempts ${released}before the run was terminal (its delivery timeout is shorter than this leg's ${o.delayMs}ms delay), so ${F} attempts were not outstanding when the healthy delivery fell due`, detail };
|
|
82
|
+
}
|
|
83
|
+
if (o.healthyAt !== null && !(o.earliestClose < o.healthyAt)) {
|
|
84
|
+
// Fewer than F held attempts had arrived when the healthy delivery fell
|
|
85
|
+
// due, and it started before any held attempt finished: it cannot have
|
|
86
|
+
// waited for one. The held fan-out was late, so the contention the floor
|
|
87
|
+
// names never existed when it mattered.
|
|
88
|
+
return { kind: 'blocked', message: `unjudged: only ${o.arrivedAtTerminal} of the ${F} held attempts had arrived when the healthy delivery fell due, and the healthy attempt started before any held attempt finished, so it did not wait for one; the held run.started deliveries arrived late, and the contention §A.2 names was not established`, detail };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
kind: 'fail',
|
|
92
|
+
message: o.healthyAt === null
|
|
93
|
+
? `the host never had ${F} subscriptions' attempts outstanding at once, and the healthy attempt did not start within ${o.windowMs}ms while the held attempts stayed open`
|
|
94
|
+
: `the host never had ${F} subscriptions' attempts outstanding at once, and the healthy attempt started only after a held attempt finished`,
|
|
95
|
+
detail,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* host released one to make room), or never within the window, AND the held
|
|
27
27
|
* attempts had stayed open until the run was terminal: the contention the
|
|
28
28
|
* floor names existed when the healthy delivery fell due, and it waited.
|
|
29
|
+
* - blocked (unjudged, suite 2.44.2) — fewer than 8 held attempts had arrived
|
|
30
|
+
* when the healthy delivery fell due, and the healthy attempt started before
|
|
31
|
+
* any held attempt finished: it waited for nothing, and the contention was
|
|
32
|
+
* never established (a late `run.started` fan-out). The rule and its tests
|
|
33
|
+
* are in `lib/delivery-isolation-verdict.ts`.
|
|
29
34
|
* - blocked (unjudged) — the host had all 8 held attempts open at once, but
|
|
30
35
|
* its own delivery timeout closed them before the run was terminal.
|
|
31
36
|
* Contention was not sustained when the healthy delivery fell due, so this
|
|
@@ -56,6 +61,7 @@ import { readErrorCode } from '../lib/error-envelope.js';
|
|
|
56
61
|
import { blockedDespiteAssertions, softSkip } from '../lib/soft-skip.js';
|
|
57
62
|
import { req } from '../lib/requirement-ids.js';
|
|
58
63
|
import { RETRY_WAIT_FLOOR_MS } from '../lib/webhook-retry-window.js';
|
|
64
|
+
import { isolationVerdict } from '../lib/delivery-isolation-verdict.js';
|
|
59
65
|
|
|
60
66
|
export const REQUIRES_HOST_CALLBACK = 'the host POSTs webhook deliveries to nine suite-owned subscriptions on the scoped receiver behind OPENWOP_WEBHOOK_RECEIVER_URL, eight of which the suite holds open';
|
|
61
67
|
|
|
@@ -179,44 +185,30 @@ describe('RFC 0215 §A — one subscription\'s receiver does not hold another\'s
|
|
|
179
185
|
const terminalAt = await waitTerminal(runId, DELAY_MS + 30_000);
|
|
180
186
|
if (terminalAt === null) return blockedDespiteAssertions(`${FIXTURE} run ${runId} did not reach a terminal status — the healthy delivery never fell due`);
|
|
181
187
|
const openAtTerminal = openCount();
|
|
182
|
-
const
|
|
188
|
+
const arrivedAtTerminal = held.size;
|
|
183
189
|
await waitFor(() => healthyAt !== null, WINDOW_MS);
|
|
184
190
|
const earliestClose = Math.min(...[...held.values()].map((h) => h.closedAt ?? Infinity));
|
|
185
|
-
const detail = `${everOpened} held attempt(s) arrived (at most ${peakOpen} open at once); ${openAtTerminal} were still open when the run was seen terminal; `
|
|
186
|
-
+ (healthyAt === null ? `the healthy attempt did not arrive within ${WINDOW_MS}ms of that` : `the healthy attempt arrived ${healthyAt - terminalAt}ms after it, with ${openAtHealthy} held open`);
|
|
187
191
|
|
|
188
|
-
if (healthyAt === null &&
|
|
192
|
+
if (healthyAt === null && held.size === 0) {
|
|
189
193
|
if (absenceIsUnmeasured(rx)) return blockedDespiteAssertions(noDeliveryCause(rx, 'webhook attempt'));
|
|
190
194
|
return blockedDespiteAssertions(`no attempt for any of the ${FLOOR + 1} subscriptions arrived — ${noDeliveryCause(rx, 'webhook attempt')}`);
|
|
191
195
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
// never opened FLOOR of them at once. The second is the defect, the first
|
|
202
|
-
// is not, and they differ in WHEN the first held attempt closed.
|
|
203
|
-
//
|
|
204
|
-
// Unfailable-leg audit (2026-09-26): this branch asserted everOpened > 0
|
|
205
|
-
// and then `softSkip('skipped')`, which records `executed-pass` with a
|
|
206
|
-
// `partial-witness:` detail. A serial dispatcher (pool of 1) with a
|
|
207
|
-
// sub-2s delivery timeout landed here and passed — every held attempt
|
|
208
|
-
// arrived eventually (everOpened reached FLOOR), one at a time, while the
|
|
209
|
-
// healthy attempt queued behind them: head-of-line blocking. peakOpen
|
|
210
|
-
// separates the two cases the comment above names: below FLOOR the host
|
|
211
|
-
// never had FLOOR attempts outstanding at once, which is the defect; at
|
|
212
|
-
// FLOOR the host's timeout released them early, which is unjudged.
|
|
196
|
+
const v = isolationVerdict({
|
|
197
|
+
floor: FLOOR, terminalAt, healthyAt, openAtHealthy, arrivedAtTerminal, openAtTerminal,
|
|
198
|
+
arrivedByVerdict: held.size, peakOpen, earliestClose, windowMs: WINDOW_MS, delayMs: DELAY_MS,
|
|
199
|
+
});
|
|
200
|
+
if (v.kind === 'blocked') return blockedDespiteAssertions(`${v.message} — ${v.detail}`);
|
|
201
|
+
if (v.kind === 'blocked-after-peak') {
|
|
202
|
+
// Unfailable-leg audit (2026-09-26): a serial dispatcher (pool of 1)
|
|
203
|
+
// with a sub-2s delivery timeout lands here; below FLOOR at once it
|
|
204
|
+
// fails, at FLOOR the host's timeout released them early (unjudged).
|
|
213
205
|
expect(
|
|
214
206
|
peakOpen,
|
|
215
|
-
req(ID, DOC, `a host MUST sustain ${FLOOR} subscriptions' attempts outstanding at once — at most ${peakOpen} were ever open together, so the held attempts were dispatched below the floor — ${detail}`),
|
|
207
|
+
req(ID, DOC, `a host MUST sustain ${FLOOR} subscriptions' attempts outstanding at once — at most ${peakOpen} were ever open together, so the held attempts were dispatched below the floor — ${v.detail}`),
|
|
216
208
|
).toBeGreaterThanOrEqual(FLOOR);
|
|
217
|
-
return blockedDespiteAssertions(
|
|
218
|
-
} else {
|
|
219
|
-
expect.fail(req(ID, DOC, `the host never had ${FLOOR} subscriptions' attempts outstanding at once, and the healthy attempt waited — a bounded dispatcher below the floor — ${detail}`));
|
|
209
|
+
return blockedDespiteAssertions(`${v.message} — ${v.detail}`);
|
|
220
210
|
}
|
|
211
|
+
if (v.kind === 'fail') expect.fail(req(ID, DOC, `${v.message} — ${v.detail}`));
|
|
212
|
+
expect(openAtHealthy, req(ID, DOC, `an attempt to one subscription MUST NOT wait for attempts to other subscriptions — ${v.detail}`)).toBeGreaterThanOrEqual(FLOOR);
|
|
221
213
|
}, DELAY_MS + 30_000 + RETRY_WAIT_FLOOR_MS + 30_000);
|
|
222
214
|
});
|