@openwop/openwop-conformance 1.136.11 → 1.139.0

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.
Files changed (36) hide show
  1. package/README.md +268 -3
  2. package/coverage.md +2 -2
  3. package/dist/cli.js +8 -1
  4. package/dist/lib/requirement-ledger.js +8 -0
  5. package/dist/lib/scenario-disposition.js +30 -0
  6. package/package.json +1 -1
  7. package/schemas/CORPUS-STAMP.json +2 -2
  8. package/schemas/capabilities.schema.json +1 -1
  9. package/schemas/certification-bundle-v2.schema.json +11 -1
  10. package/src/cli.ts +8 -1
  11. package/src/global-setup.ts +169 -0
  12. package/src/lib/fork-availability.test.ts +69 -0
  13. package/src/lib/fork-availability.ts +61 -0
  14. package/src/lib/global-setup.test.ts +76 -0
  15. package/src/lib/polling.test.ts +80 -0
  16. package/src/lib/polling.ts +39 -2
  17. package/src/lib/requirement-ledger.test.ts +75 -0
  18. package/src/lib/requirement-ledger.ts +9 -0
  19. package/src/lib/risk-disposition.test.ts +91 -0
  20. package/src/lib/scenario-disposition.ts +30 -0
  21. package/src/lib/webhook-receiver.test.ts +76 -0
  22. package/src/lib/webhook-receiver.ts +24 -3
  23. package/src/scenarios/a2a-task-roundtrip.test.ts +66 -10
  24. package/src/scenarios/agent-channel-dispatch.test.ts +3 -3
  25. package/src/scenarios/conformance-execution-witness.test.ts +29 -0
  26. package/src/scenarios/cross-host-traceparent-propagation.test.ts +10 -6
  27. package/src/scenarios/durability-poison-exhaustion.test.ts +154 -0
  28. package/src/scenarios/replay-fanout-suppression.test.ts +328 -0
  29. package/src/scenarios/replay-fork-arbitrary.test.ts +9 -3
  30. package/src/scenarios/replay-fork.test.ts +10 -2
  31. package/src/scenarios/replay-side-effect-suppression.test.ts +3 -2
  32. package/src/scenarios/replayDeterminism.test.ts +8 -6
  33. package/src/scenarios/webhook-receiver-adversarial.test.ts +9 -3
  34. package/src/scenarios/webhook-signed-delivery.test.ts +76 -13
  35. package/src/setup.ts +24 -5
  36. package/vitest.config.ts +7 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Poison work reaches a bounded, operator-visible terminal state
3
+ * (RFC 0158 §C.8, conformance row `durability/poison-exhaustion`).
4
+ *
5
+ * THE REQUIREMENT: work that fails deterministically **MUST** reach a terminal,
6
+ * operator-visible state within a bounded number of attempts, and **MUST NOT**
7
+ * be redelivered indefinitely.
8
+ *
9
+ * ── Why the obvious assertion proves only half of it ─────────────────────────
10
+ * "The run reached `failed`" is the half everyone writes, and `failure-path.
11
+ * test.ts` already covers it. It says nothing about the second clause: a host
12
+ * that redelivers forever ALSO reports a terminal status at some point, or
13
+ * reports one while the work keeps being re-dispatched behind it. An assertion
14
+ * that stops at the terminal status cannot distinguish "terminated after N
15
+ * attempts" from "still going".
16
+ *
17
+ * So the load-bearing leg here is STABILITY AFTER TERMINAL: count the attempts
18
+ * on the run's own log, wait a scaled quiet window, count again, and assert the
19
+ * number did not move. A host still redelivering shows new attempts; a host that
20
+ * stopped shows the same count. That is the falsifiable form of "MUST NOT be
21
+ * redelivered indefinitely" — and, unlike a ceiling, it needs no number the wire
22
+ * does not carry (see below).
23
+ *
24
+ * ── What this scenario deliberately does NOT assert ──────────────────────────
25
+ * Conformance to a SPECIFIC declared attempt bound. RFC 0158 §E mints no
26
+ * advertised capability field, so a host's declared bound is not on the wire and
27
+ * the suite cannot read it. Asserting against a number the suite invented would
28
+ * be a bound of the suite's own making, which is the inverse of the §B.5
29
+ * discipline ("derived from the mechanism that enforces it"). When
30
+ * `OPENWOP_DECLARED_ATTEMPT_BOUND` is supplied by the operator — the same
31
+ * operator-supplied-input shape as `OPENWOP_OPTED_OUT_PROFILES` — the ceiling leg
32
+ * runs too. Without it, boundedness is still asserted; only the specific bound is
33
+ * not.
34
+ *
35
+ * ── Honest about its gating ──────────────────────────────────────────────────
36
+ * This is NOT a pure black-box scenario. Attempt counts live on the run's event
37
+ * log, which is read through the EXISTING `/v1/host/sample/test/runs/{runId}/
38
+ * events` seam — no new seam, but a seam. A host that has not wired it records
39
+ * `blocked`: unobservable, not unmet. Outside every profile floor, for the same
40
+ * reason.
41
+ *
42
+ * @see RFCS/0158-durable-execution-and-disaster-recovery-qualification.md §C.8
43
+ * @see spec/v1/host-sample-test-seams.md
44
+ */
45
+
46
+ import { describe, expect, it } from 'vitest';
47
+ import { driver } from '../lib/driver.js';
48
+ import { pollUntilTerminal, scaledTimeoutMs } from '../lib/polling.js';
49
+ import { isFixtureAdvertised } from '../lib/fixtures.js';
50
+ import { softSkip } from '../lib/soft-skip.js';
51
+ import { queryTestEvents, requireEvents } from '../lib/event-log-query.js';
52
+ import { recordRequirement } from '../lib/requirement-ledger.js';
53
+ import { requirementIdForFile } from '../lib/scenario-disposition.js';
54
+
55
+ const WORKFLOW_ID = 'conformance-failure';
56
+ const FILE = 'durability-poison-exhaustion.test.ts';
57
+ const REQ = requirementIdForFile(FILE);
58
+
59
+ /** Attempt-bearing event types. A `node.started` is attempt 1; each
60
+ * `node.retried` is one more. Counting BOTH means a host that re-dispatches
61
+ * without emitting `node.retried` is still caught. */
62
+ const ATTEMPT_TYPES = new Set(['node.started', 'node.retried']);
63
+
64
+ /** How long to watch for further attempts after the run reports terminal.
65
+ * Scaled: a longer wait is a STRONGER claim here, because it is a wait for
66
+ * something that must not happen. */
67
+ const QUIET_WINDOW_MS = 4_000;
68
+
69
+ function declaredAttemptBound(): number | null {
70
+ const raw = process.env['OPENWOP_DECLARED_ATTEMPT_BOUND'];
71
+ if (raw === undefined || raw.trim() === '') return null;
72
+ const n = Number(raw);
73
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
74
+ }
75
+
76
+ const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
77
+
78
+ // Recorded at MODULE scope, not inside the `it`. `describe.skipIf` never runs the
79
+ // callback, so a `recordRequirement` in the test body is dead code on exactly the
80
+ // path it exists to classify — the same shape as the note written after
81
+ // `ctx.skip()` threw, which left seven files carrying notes the ledger never saw.
82
+ // Caught by reading a CI log: this file and `failure-path.test.ts` both skipped
83
+ // against the postgres host, which does not advertise the fixture, and the row
84
+ // would have been recorded `blocked` with the unclassified-return marker instead
85
+ // of `inapplicable` with a reason.
86
+ if (SKIP_NO_FIXTURE) {
87
+ recordRequirement(REQ, 'inapplicable', `fixture ${WORKFLOW_ID} not advertised`);
88
+ }
89
+
90
+ describe.skipIf(SKIP_NO_FIXTURE)('RFC 0158 §C.8 — poison work terminates within a bounded number of attempts', () => {
91
+ it('reaches terminal AND stops being retried, asserted on the log rather than on the status alone', async () => {
92
+ const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
93
+ expect(create.status, driver.describe(
94
+ 'rest-endpoints.md',
95
+ 'POST /v1/runs MUST return 201 even for work that fails at runtime',
96
+ )).toBe(201);
97
+ const runId = (create.json as { runId: string }).runId;
98
+
99
+ // ── First clause: a terminal, OPERATOR-VISIBLE state ─────────────────────
100
+ const terminal = await pollUntilTerminal(runId);
101
+ expect(terminal.status, driver.describe(
102
+ 'RFC 0158 §C.8',
103
+ 'deterministically failing work MUST reach a terminal, operator-visible state',
104
+ )).toBe('failed');
105
+
106
+ // ── The seam gate comes AFTER the control above, so a host that wired the
107
+ // fixture but not the log still proves the fixture ran. ───────────────
108
+ const first = await queryTestEvents(runId);
109
+ if (!first.ok) {
110
+ const why = first.reason === 'seam_unavailable'
111
+ ? 'event-log seam /v1/host/sample/test/runs/{runId}/events not wired — attempts are unobservable'
112
+ : `event-log seam returned HTTP ${first.status}`;
113
+ recordRequirement(REQ, 'blocked', why);
114
+ return softSkip('blocked', why);
115
+ }
116
+
117
+ const events = requireEvents(first, 'RFC 0158 §C.8 attempt counting');
118
+ // Non-vacuity: the failure must actually be ON the log. Without this, a host
119
+ // returning an empty array would sail through every count comparison below,
120
+ // since 0 === 0 after any wait.
121
+ expect(events.some((e) => e.type === 'node.failed'), driver.describe(
122
+ 'RFC 0158 §C.8',
123
+ 'the run log MUST record the deterministic failure — an empty log makes every attempt count vacuous',
124
+ )).toBe(true);
125
+
126
+ const attemptsBefore = events.filter((e) => ATTEMPT_TYPES.has(e.type)).length;
127
+ expect(attemptsBefore, driver.describe(
128
+ 'RFC 0158 §C.8',
129
+ 'at least one attempt MUST be recorded — zero attempts means nothing was delivered',
130
+ )).toBeGreaterThan(0);
131
+
132
+ // ── Second clause, the load-bearing one: NOT redelivered indefinitely ────
133
+ await new Promise((r) => setTimeout(r, scaledTimeoutMs(QUIET_WINDOW_MS)));
134
+ const second = await queryTestEvents(runId);
135
+ const after = requireEvents(second, 'RFC 0158 §C.8 attempt counting (post-terminal)');
136
+ const attemptsAfter = after.filter((e) => ATTEMPT_TYPES.has(e.type)).length;
137
+
138
+ expect(attemptsAfter, driver.describe(
139
+ 'RFC 0158 §C.8',
140
+ 'attempts MUST NOT continue after the run reports terminal — a host still redelivering records more',
141
+ )).toBe(attemptsBefore);
142
+
143
+ // ── Optional ceiling, only when the operator supplies the declared bound ─
144
+ const bound = declaredAttemptBound();
145
+ if (bound !== null) {
146
+ expect(attemptsAfter, driver.describe(
147
+ 'RFC 0158 §C.8 + §B.5',
148
+ 'total attempts MUST NOT exceed the operator-declared attempt bound',
149
+ )).toBeLessThanOrEqual(bound);
150
+ }
151
+
152
+ recordRequirement(REQ, 'executed-pass', undefined, { assertionCount: bound === null ? 5 : 6 });
153
+ });
154
+ });
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Host-initiated fan-out is an external effect (`replay.md` §"Host-initiated
3
+ * fan-out is an external effect").
4
+ *
5
+ * THE MUST NOT: a host that projects its event log outward — webhook delivery,
6
+ * outbound streams, analytics or audit sinks — MUST NOT emit those outbound
7
+ * deliveries for events a `mode: "replay"` fork re-emits as fixed history.
8
+ *
9
+ * WHY THIS SCENARIO EXISTS. Until now this was the largest normative MUST NOT on
10
+ * the replay surface with **no conformance scenario and no SECURITY invariant**.
11
+ * Two hosts had it deployed and the interop matrix still had to record it as
12
+ * *not suite-witnessed*, because both rows rested on the hosts' own tests. A
13
+ * host's own test is evidence about that host; it is not a witness of the wire.
14
+ *
15
+ * WHY IT IS OBSERVABLE AT ALL, unlike some other unmeasured invariants: the
16
+ * outbound side has a real probe surface. The suite can BE the subscriber — boot
17
+ * a local receiver, register it via `POST /v1/webhooks`, and read what actually
18
+ * arrives. The property is stated over exactly what a subscriber sees, so this
19
+ * asserts the thing itself rather than an adjacent proxy.
20
+ *
21
+ * ── The positive control is not decoration here; it is the whole test ────────
22
+ * "No delivery arrived" passes identically when delivery never worked at all: a
23
+ * mistyped URL, a subscription that was never created, a host that rejected the
24
+ * receiver, a fixture that emitted nothing. That is a vacuous witness in its
25
+ * purest form. So all three legs run in ONE test against ONE receiver and ONE
26
+ * subscription, and the absence is only ever asserted AFTER presence has been
27
+ * proven on that exact wiring. Do not split this into separate `it` blocks —
28
+ * separate blocks re-register, and a re-registration that silently fails turns
29
+ * the negative leg back into a vacuous pass.
30
+ *
31
+ * ── Why the timeout knob makes this leg STRICTER, not looser ─────────────────
32
+ * Absence has nothing to poll for, so the negative is "nothing arrived within
33
+ * N". `OPENWOP_POLL_TIMEOUT_SCALE` multiplies N. The reflex on seeing a timeout
34
+ * multiplier is that it weakens assertions — here it is the reverse: waiting
35
+ * longer for a delivery that must never come is a STRONGER claim, and an
36
+ * operator raising the scale on a slow host makes this scenario harder to pass,
37
+ * not easier.
38
+ *
39
+ * ── Deliberately NOT in the `openwop-replay-fork` floor ──────────────────────
40
+ * This scenario needs the host to accept a loopback receiver. A host with an
41
+ * SSRF guard on `POST /v1/webhooks` correctly refuses one, and the suite's
42
+ * standing operator contract asks such hosts for an opt-in
43
+ * (`OPENWOP_WEBHOOK_ALLOW_PRIVATE=true` on the SQLite reference). At least one
44
+ * certifying host rejects it today. Putting a receiver-gated row in that
45
+ * profile's floor would make the profile un-certifiable for a host whose
46
+ * SECURITY CONTROL IS CORRECT, and would read in the matrix as a regression that
47
+ * is not one. Capability-gated, outside the floor. Moving it into a floor is a
48
+ * separate decision needing an RFC 0148 §C argument.
49
+ *
50
+ * ── One interpretive call, stated rather than buried ─────────────────────────
51
+ * The subscription below is for `run.completed`. That is a LIFECYCLE event, and
52
+ * `replay.md` notes lifecycle events are "ambiguous noise" next to a
53
+ * recorded-fact event like `memory.written`, which is "a false statement". The
54
+ * reading applied here is that a replay fork reproduces the source's log, so the
55
+ * `run.completed` in the fork's log is RE-EMITTED HISTORY and therefore squarely
56
+ * inside "any event re-emitted as fixed history is in scope". A host that
57
+ * suppressed only recorded-fact events while delivering re-emitted lifecycle
58
+ * events would fail this scenario.
59
+ *
60
+ * That reading is no longer an inference: `replay.md` was clarified on
61
+ * 2026-08-19 to say the contrast RANKS THE HARM and does not narrow the scope,
62
+ * because the requirement above it already decides the question — replay-ness is
63
+ * read from the run, never from the event type, so suppressing by event type is
64
+ * selecting by event type. The paragraph stays here because a host that reads
65
+ * the older text should meet a visible claim rather than a surprise red.
66
+ *
67
+ * @see spec/v1/replay.md §"Host-initiated fan-out is an external effect"
68
+ * @see spec/v1/webhooks.md §"Register"
69
+ */
70
+
71
+ import { afterEach, describe, expect, it } from 'vitest';
72
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
73
+ import { softSkip } from '../lib/soft-skip.js';
74
+ import { driver } from '../lib/driver.js';
75
+ import { forkDeclined } from '../lib/fork-availability.js';
76
+ import { discoveryFamilies, readCapabilityFamily } from '../lib/discovery-capabilities.js';
77
+ import { pollUntilTerminal, scaledTimeoutMs } from '../lib/polling.js';
78
+ import { isFixtureAdvertised } from '../lib/fixtures.js';
79
+ import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
80
+ import { recordRequirement } from '../lib/requirement-ledger.js';
81
+ import { requirementIdForFile } from '../lib/scenario-disposition.js';
82
+
83
+ /**
84
+ * RFC 0148 §A — the MUST NOT gets its OWN disposition, separate from the file's.
85
+ *
86
+ * Without this the file would be recorded `executed-pass` off leg 1's assertions
87
+ * even on a run where the fork returned 501 and the MUST NOT was never
88
+ * exercised. That is a partial witness reading as a full one: the positive
89
+ * control genuinely passed, so the file is not lying, but the matrix cell for
90
+ * this requirement would claim coverage the run did not produce. Recording the
91
+ * requirement explicitly at every exit keeps the cell honest whichever path is
92
+ * taken. `setup.ts` records the file's aggregate under the SAME id and catches
93
+ * the resulting conflict — "a scenario that recorded its own file id first
94
+ * wins" — so this is the sanctioned override, not a second row.
95
+ *
96
+ * It MUST be `requirementIdForFile`, not `requirementIdForScenario`. Those two
97
+ * agree only for files in a certification FLOOR; for a non-floor scenario like
98
+ * this one they differ (`openwop.scenario.<name>` vs the registry id). Recording
99
+ * the registry id here would mint an ORPHAN requirement nothing reads, while the
100
+ * row the bundle actually carries kept the file-level `executed-pass` — a fix
101
+ * that changes nothing and looks like it did.
102
+ */
103
+ const REQUIREMENT_ID = requirementIdForFile('replay-fanout-suppression.test.ts');
104
+
105
+ interface Delivered {
106
+ readonly body: string;
107
+ }
108
+
109
+ async function startReceiver(): Promise<{ server: Server; url: string; received: Delivered[] }> {
110
+ const received: Delivered[] = [];
111
+ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
112
+ const chunks: Buffer[] = [];
113
+ req.on('data', (c: Buffer) => chunks.push(c));
114
+ req.on('end', () => {
115
+ received.push({ body: Buffer.concat(chunks).toString('utf8') });
116
+ res.writeHead(204);
117
+ res.end();
118
+ });
119
+ });
120
+ await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
121
+ const addr = server.address();
122
+ if (typeof addr !== 'object' || addr === null) throw new Error('receiver address unavailable');
123
+ return { server, url: `http://127.0.0.1:${addr.port}/`, received };
124
+ }
125
+
126
+ let activeServer: Server | null = null;
127
+ afterEach(async () => {
128
+ if (activeServer) {
129
+ await new Promise<void>((resolve) => activeServer!.close(() => resolve()));
130
+ activeServer = null;
131
+ }
132
+ });
133
+
134
+ /** Deliveries whose body mentions this run id — the fan-out attributable to it. */
135
+ function forRun(received: readonly Delivered[], runId: string): Delivered[] {
136
+ return received.filter((d) => d.body.includes(runId));
137
+ }
138
+
139
+ /**
140
+ * Wait `ms`, then report what arrived. Used for the NEGATIVE leg, where there is
141
+ * no event to poll for. Scaled, per the note in the file header.
142
+ */
143
+ async function quietWindow(ms: number): Promise<void> {
144
+ await new Promise((resolve) => setTimeout(resolve, scaledTimeoutMs(ms)));
145
+ }
146
+
147
+ /** Grace for fire-and-forget delivery on the POSITIVE legs. */
148
+ const DELIVERY_GRACE_MS = 1_500;
149
+ /**
150
+ * How long the replay fork is watched for a delivery that must never arrive.
151
+ * Deliberately several times the positive grace: a host that merely DELAYS
152
+ * fan-out rather than suppressing it must not pass by being slow.
153
+ */
154
+ const SUPPRESSION_WINDOW_MS = 6_000;
155
+
156
+ describe('replay-fanout-suppression: a replay fork MUST NOT fan out re-emitted events', () => {
157
+ it('delivers for a live run, suppresses for a replay fork, and delivers again for a branch fork', async (ctx) => {
158
+ const disco = await driver.get('/.well-known/openwop');
159
+ const caps = discoveryFamilies(disco.json) as {
160
+ webhooks?: { supported?: boolean };
161
+ replay?: { fork?: boolean; supported?: boolean };
162
+ };
163
+ if (caps.webhooks?.supported !== true) {
164
+ recordRequirement(REQUIREMENT_ID, 'inapplicable', 'host does not advertise webhooks.supported — outbound fan-out has no probe surface on this host');
165
+ return softSkip('inapplicable', '[replay-fanout-suppression] host does not advertise webhook support');
166
+ }
167
+ if (!isFixtureAdvertised('conformance-noop')) {
168
+ recordRequirement(REQUIREMENT_ID, 'inapplicable', 'conformance-noop fixture not advertised — no run to fan out');
169
+ return softSkip('inapplicable', '[replay-fanout-suppression] conformance-noop not advertised');
170
+ }
171
+
172
+ const receiver = await startReceiver();
173
+ activeServer = receiver.server;
174
+
175
+ const ownedTenant = await discoverOwnedTenant(driver);
176
+ const reg = await driver.post('/v1/webhooks', {
177
+ url: receiver.url,
178
+ events: ['run.completed'],
179
+ ...(ownedTenant ? { tenantId: ownedTenant } : {}),
180
+ });
181
+ if (reg.status === 400 && (reg.json as { error?: string }).error === 'webhook_url_rejected') {
182
+ // The host's SSRF guard refused a loopback destination. That is CORRECT
183
+ // host behaviour, not a failure — see the floor note in the file header.
184
+ recordRequirement(
185
+ REQUIREMENT_ID,
186
+ 'blocked',
187
+ "precondition not met — body.error === 'webhook_url_rejected'; the host's SSRF guard refused the loopback "
188
+ + 'receiver, which is correct host behaviour, so this requirement is unobservable rather than unmet',
189
+ );
190
+ return softSkip(
191
+ 'blocked',
192
+ '[replay-fanout-suppression] host SSRF guard rejected the loopback receiver; '
193
+ + 'set OPENWOP_WEBHOOK_ALLOW_PRIVATE=true on the host (or equivalent) to run',
194
+ );
195
+ }
196
+ expect(reg.status, driver.describe(
197
+ 'webhooks.md §"Register"',
198
+ 'POST /v1/webhooks MUST return 201 on success',
199
+ )).toBe(201);
200
+
201
+ // ── LEG 1 — POSITIVE CONTROL. Prove this wiring delivers at all. ─────────
202
+ const create = await driver.post('/v1/runs', { workflowId: 'conformance-noop' });
203
+ expect(create.status, 'failed to start conformance-noop').toBe(201);
204
+ const sourceRunId = (create.json as { runId: string }).runId;
205
+ await pollUntilTerminal(sourceRunId, { timeoutMs: 10_000 });
206
+ await quietWindow(DELIVERY_GRACE_MS);
207
+
208
+ expect(
209
+ forRun(receiver.received, sourceRunId).length,
210
+ driver.describe(
211
+ 'webhooks.md §"Register"',
212
+ 'POSITIVE CONTROL: the host must deliver the source run\'s events to this receiver — '
213
+ + 'without this, every "no delivery" assertion below is vacuous',
214
+ ),
215
+ ).toBeGreaterThan(0);
216
+
217
+ // ── CAPABILITY GATE (added 2026-08-25) ──────────────────────────────────
218
+ // This is the ONLY scenario in the replay family that never checked whether
219
+ // the host advertises replay before forking — every sibling reads
220
+ // `replay.supported` first. That omission is why it was the one scenario
221
+ // reaching the fork seam on a host that does not implement it, and why it
222
+ // hard-failed `expected 404 to be 201` on every CI run of `main` while the
223
+ // siblings quietly returned at their capability check.
224
+ //
225
+ // `inapplicable`, not `blocked`: a host that does not advertise replay is
226
+ // outside this MUST NOT's scope entirely, and `blocked` would claim the
227
+ // requirement applies but could not be witnessed — a stronger claim than
228
+ // the evidence supports. Recorded explicitly because LEG 1 above asserted,
229
+ // so the file-level disposition would otherwise be `executed-pass`.
230
+ const replayCap = await readCapabilityFamily<{ supported?: boolean; modes?: unknown }>('replay');
231
+ if (replayCap?.supported !== true) {
232
+ recordRequirement(
233
+ REQUIREMENT_ID,
234
+ 'inapplicable',
235
+ 'host does not advertise `replay.supported: true`, so a replay fork cannot occur and this MUST NOT '
236
+ + 'has nothing to constrain on this host',
237
+ );
238
+ ctx.skip();
239
+ return;
240
+ }
241
+
242
+ // ── LEG 2 — THE MUST NOT. A replay fork re-emits; it must not deliver. ───
243
+ const replay = await driver.post(`/v1/runs/${encodeURIComponent(sourceRunId)}:fork`, {
244
+ mode: 'replay',
245
+ });
246
+ if (forkDeclined(replay.status, 'fanout-suppression replay fork')) {
247
+ // Leg 1 already asserted, so the FILE is `executed-pass` — but the MUST
248
+ // NOT was never exercised, and that is what this row must say. The
249
+ // explicit record wins over the file-level one (setup.ts).
250
+ //
251
+ // 404 and 403 were NOT handled here until 2026-08-25, only 501. The
252
+ // postgres reference host 404s this route, so this was the one scenario
253
+ // in the replay family that actually reached the seam — and it hard-
254
+ // failed `expected 404 to be 201` on every CI run of `main`, absorbed by
255
+ // the 85% pass-rate floor. The suite required a host to implement the
256
+ // route in order to say it had not implemented the route.
257
+ recordRequirement(
258
+ REQUIREMENT_ID,
259
+ 'blocked',
260
+ `replay fork returned ${replay.status} — the re-emission this requirement is stated over never happened, `
261
+ + 'so the absence of deliveries below would prove nothing',
262
+ );
263
+ ctx.skip();
264
+ return;
265
+ }
266
+ expect(replay.status, 'replay fork should be accepted').toBe(201);
267
+ const replayRunId = (replay.json as { runId: string }).runId;
268
+ await pollUntilTerminal(replayRunId, { timeoutMs: 30_000 });
269
+ await quietWindow(SUPPRESSION_WINDOW_MS);
270
+
271
+ expect(
272
+ forRun(receiver.received, replayRunId).map((d) => d.body.slice(0, 200)),
273
+ driver.describe(
274
+ 'replay.md §"Host-initiated fan-out is an external effect"',
275
+ 'a mode:"replay" fork MUST NOT emit outbound deliveries for the events it re-emits as fixed history — '
276
+ + 'delivering one asserts to a subscriber that something happened in this run which did not',
277
+ ),
278
+ ).toEqual([]);
279
+
280
+ // Guard against the OTHER vacuity: a fork that emitted nothing at all would
281
+ // also deliver nothing. The fork's own log MUST still carry the re-emitted
282
+ // events (`replay.md`: suppression is outbound-only, the fork's event log
283
+ // still carries them). Without this, a host that simply failed the fork
284
+ // would pass leg 2.
285
+ const forkEvents = await driver.get(`/v1/runs/${encodeURIComponent(replayRunId)}/events`);
286
+ expect(forkEvents.status, 'fork events must be readable').toBe(200);
287
+ const forkEventList = (forkEvents.json as { events?: { type?: string }[] }).events ?? [];
288
+ expect(
289
+ forkEventList.length,
290
+ driver.describe(
291
+ 'replay.md §"Host-initiated fan-out is an external effect"',
292
+ 'suppression is OUTBOUND ONLY — the fork\'s own event log MUST still carry the re-emitted events, '
293
+ + 'so an empty fork log means leg 2 proved nothing',
294
+ ),
295
+ ).toBeGreaterThan(0);
296
+
297
+ // The MUST NOT has now been exercised against wiring proven to deliver, on a
298
+ // fork proven to have re-emitted. Recorded here rather than after leg 3,
299
+ // because leg 3 pins the boundary and may legitimately not run.
300
+ recordRequirement(REQUIREMENT_ID, 'executed-pass', undefined, { assertionCount: 4 });
301
+
302
+ // ── LEG 3 — the boundary. `branch` is explicitly OUT of scope. ───────────
303
+ // This is what distinguishes "reads replay-ness from the RUN" from "silences
304
+ // anything that is a fork". A host that suppressed both would pass legs 1-2
305
+ // and be wrong: a branch fork's events are new facts and its effects are the
306
+ // ones the operator asked for.
307
+ const branch = await driver.post(`/v1/runs/${encodeURIComponent(sourceRunId)}:fork`, {
308
+ mode: 'branch',
309
+ });
310
+ if (branch.status === 501 || branch.status === 400) {
311
+ // branch not offered on this range — leg 2 still stands on its own.
312
+ return;
313
+ }
314
+ expect(branch.status, 'branch fork should be accepted').toBe(201);
315
+ const branchRunId = (branch.json as { runId: string }).runId;
316
+ await pollUntilTerminal(branchRunId, { timeoutMs: 30_000 });
317
+ await quietWindow(DELIVERY_GRACE_MS);
318
+
319
+ expect(
320
+ forRun(receiver.received, branchRunId).length,
321
+ driver.describe(
322
+ 'replay.md §"Host-initiated fan-out is an external effect"',
323
+ 'a branch fork is deliberately OUT of scope — its events are new facts, so suppressing them '
324
+ + 'means the host keyed on "is a fork" rather than on replay-ness read from the run',
325
+ ),
326
+ ).toBeGreaterThan(0);
327
+ });
328
+ });
@@ -30,6 +30,8 @@
30
30
 
31
31
  import { describe, it, expect } from 'vitest';
32
32
  import { driver } from '../lib/driver.js';
33
+ import { softSkip } from '../lib/soft-skip.js';
34
+ import { forkDeclined } from '../lib/fork-availability.js';
33
35
  import { pollUntilTerminal } from '../lib/polling.js';
34
36
  import { isFixtureAdvertised } from '../lib/fixtures.js';
35
37
 
@@ -122,6 +124,7 @@ describe.skipIf(SKIP_NO_MULTI)(
122
124
  it('mid-fromSeq replay fork produces a new run that reaches `completed`', async (ctx) => {
123
125
  const replay = await fetchReplayCapability();
124
126
  if (replay?.supported !== true) {
127
+ softSkip('inapplicable', "host does not advertise `replay.supported: true` — the replay contract does not apply to it");
125
128
  ctx.skip();
126
129
  return;
127
130
  }
@@ -129,6 +132,7 @@ describe.skipIf(SKIP_NO_MULTI)(
129
132
  ? replay.modes.filter((m): m is string => typeof m === 'string')
130
133
  : [];
131
134
  if (!modes.includes('replay')) {
135
+ softSkip('inapplicable', "host does not advertise the `replay` fork mode — this leg's rule has no path to apply");
132
136
  ctx.skip();
133
137
  return;
134
138
  }
@@ -140,6 +144,7 @@ describe.skipIf(SKIP_NO_MULTI)(
140
144
  // Fixture's wire shape doesn't expose node.completed(b) with a
141
145
  // numeric sequence — skip rather than fail. Conformant hosts
142
146
  // with the standard event shape will hit the assertions below.
147
+ softSkip('blocked', "the advertised fixture's wire shape exposes no numeric sequence for node.completed(b), so there is no mid-run point to fork from");
143
148
  ctx.skip();
144
149
  return;
145
150
  }
@@ -149,7 +154,7 @@ describe.skipIf(SKIP_NO_MULTI)(
149
154
  { fromSeq, mode: 'replay' },
150
155
  );
151
156
 
152
- if (fork.status === 501) {
157
+ if (forkDeclined(fork.status, 'arbitrary-event fork')) {
153
158
  ctx.skip();
154
159
  return;
155
160
  }
@@ -226,7 +231,7 @@ describe.skipIf(SKIP_NO_MULTI)(
226
231
  `/v1/runs/${encodeURIComponent(sourceRunId)}:fork`,
227
232
  { fromSeq, mode: 'replay' },
228
233
  );
229
- if (fork1.status === 501) {
234
+ if (forkDeclined(fork1.status, 'arbitrary-event fork 1')) {
230
235
  ctx.skip();
231
236
  return;
232
237
  }
@@ -238,7 +243,7 @@ describe.skipIf(SKIP_NO_MULTI)(
238
243
  `/v1/runs/${encodeURIComponent(sourceRunId)}:fork`,
239
244
  { fromSeq, mode: 'replay' },
240
245
  );
241
- if (fork2.status === 501) {
246
+ if (forkDeclined(fork2.status, 'arbitrary-event fork 2')) {
242
247
  ctx.skip();
243
248
  return;
244
249
  }
@@ -294,6 +299,7 @@ describe.skipIf(SKIP_NO_MULTI)(
294
299
  it('mid-fromSeq branch fork with empty overlay produces a new run that reaches `completed`', async (ctx) => {
295
300
  const replay = await fetchReplayCapability();
296
301
  if (replay?.supported !== true) {
302
+ softSkip('inapplicable', "host does not advertise `replay.supported: true` — the replay contract does not apply to it");
297
303
  ctx.skip();
298
304
  return;
299
305
  }
@@ -25,6 +25,8 @@
25
25
 
26
26
  import { describe, it, expect } from 'vitest';
27
27
  import { driver } from '../lib/driver.js';
28
+ import { softSkip } from '../lib/soft-skip.js';
29
+ import { forkDeclined } from '../lib/fork-availability.js';
28
30
  import { pollUntilTerminal } from '../lib/polling.js';
29
31
  import { isFixtureAdvertised } from '../lib/fixtures.js';
30
32
 
@@ -60,6 +62,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in replay mode', () =
60
62
  // Visible skip — earlier this was a silent `return` that
61
63
  // collapsed to a vacuous pass and made it impossible to tell
62
64
  // unexercised tests apart from honest passes.
65
+ softSkip('inapplicable', "host does not advertise the `replay` fork mode — this leg's rule has no path to apply");
63
66
  ctx.skip();
64
67
  return;
65
68
  }
@@ -70,7 +73,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in replay mode', () =
70
73
  { fromSeq: 0, mode: 'replay' },
71
74
  );
72
75
 
73
- if (fork.status === 501) return; // mode advertised but not implemented; skip-equivalent
76
+ if (forkDeclined(fork.status, 'replay fork')) return;
74
77
  expect(fork.status, driver.describe(
75
78
  'rest-endpoints.md POST /v1/runs/{runId}:fork',
76
79
  'fork MUST return 201 on accepted replay',
@@ -101,6 +104,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in branch mode with e
101
104
  it('produces a new run that reaches terminal `completed`', async (ctx) => {
102
105
  const modes = await fetchReplayModes();
103
106
  if (!modes.includes('branch')) {
107
+ softSkip('inapplicable', "host does not advertise the `branch` fork mode — this leg's rule has no path to apply");
104
108
  ctx.skip();
105
109
  return;
106
110
  }
@@ -111,7 +115,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in branch mode with e
111
115
  { fromSeq: 0, mode: 'branch', runOptionsOverlay: {} },
112
116
  );
113
117
 
114
- if (fork.status === 501) return; // mode advertised but not implemented; skip-equivalent
118
+ if (forkDeclined(fork.status, 'branch fork')) return;
115
119
  expect(fork.status, driver.describe(
116
120
  'rest-endpoints.md POST /v1/runs/{runId}:fork',
117
121
  'branch fork MUST return 201',
@@ -137,6 +141,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
137
141
  it('rejects negative fromSeq with 400', async (ctx) => {
138
142
  const modes = await fetchReplayModes();
139
143
  if (modes.length === 0) {
144
+ softSkip('inapplicable', "host advertises no usable fork mode for this leg");
140
145
  ctx.skip();
141
146
  return;
142
147
  }
@@ -155,6 +160,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
155
160
  it('rejects fromSeq beyond source event log length with 422', async (ctx) => {
156
161
  const modes = await fetchReplayModes();
157
162
  if (modes.length === 0) {
163
+ softSkip('inapplicable', "host advertises no usable fork mode for this leg");
158
164
  ctx.skip();
159
165
  return;
160
166
  }
@@ -179,6 +185,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
179
185
  // → 400) only applies on hosts that advertise the `replay` mode.
180
186
  // A `branch`-only host has no path to even attempt the request.
181
187
  // Visible skip rather than silent vacuous pass.
188
+ softSkip('inapplicable', "host does not advertise the `replay` fork mode — the runOptionsOverlay rejection rule only applies to hosts that do");
182
189
  ctx.skip();
183
190
  return;
184
191
  }
@@ -200,6 +207,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
200
207
  it('rejects fork on a non-existent run with 404', async (ctx) => {
201
208
  const modes = await fetchReplayModes();
202
209
  if (modes.length === 0) {
210
+ softSkip('inapplicable', "host advertises no usable fork mode for this leg");
203
211
  ctx.skip();
204
212
  return;
205
213
  }
@@ -48,6 +48,7 @@ import { describe, it, expect } from 'vitest';
48
48
  import { recordRequirement } from '../lib/requirement-ledger.js';
49
49
  import { requirementIdForScenario } from '../lib/requirement-registry.js';
50
50
  import { driver } from '../lib/driver.js';
51
+ import { forkDeclined } from '../lib/fork-availability.js';
51
52
  import { pollUntilTerminal } from '../lib/polling.js';
52
53
  import { isFixtureAdvertised } from '../lib/fixtures.js';
53
54
  import { readFileSync } from 'node:fs';
@@ -173,8 +174,8 @@ describe.skipIf(SKIP_NO_FIXTURE)('replay-side-effect-suppression: a replay does
173
174
  const fork = await driver.post(`/v1/runs/${encodeURIComponent(sourceRunId)}:fork`, {
174
175
  mode: 'replay',
175
176
  });
176
- if (fork.status === 501) {
177
- ctx.skip(); // advertised but not implemented for this range — suite convention
177
+ if (forkDeclined(fork.status, 'side-effect-suppression replay fork')) {
178
+ ctx.skip();
178
179
  return;
179
180
  }
180
181
  expect(fork.status, 'fork should be accepted').toBe(201);