@openwop/openwop-conformance 2.4.4 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.4.5] — 2026-09-18 — the strict flag that was not wired, and the soft-skip that counted as a pass
4
+
5
+ ### Fixed
6
+
7
+ - **`--require-behavior` was never parsed** — it hit the arg loop's silent
8
+ default arm, so only `OPENWOP_REQUIRE_BEHAVIOR=true` enabled strict mode. A
9
+ cut naming the flag measured the loose answer. The flag now sets the variable,
10
+ and an unknown `--flag` exits 2 rather than being ignored.
11
+ - **`resolveItRecord` dropped the `partial-witness:` note** that
12
+ `resolveFileRecord` computes, so a leg that asserted and then soft-skipped
13
+ recorded a bare `executed-pass` at the granularity the RFC acceptance gate
14
+ reads.
15
+ - **`v2-bound-id-kinds`** now routes its mint through `resolveRegistrationUrl`,
16
+ so `OPENWOP_WEBHOOK_RECEIVER_URL` works on the leg whose blocked note told
17
+ operators to set it.
18
+ - **`v2-webhook-delivery-shape`** now honours `OPENWOP_WEBHOOK_RECEIVER_PORT`
19
+ and is registered in the receiver guard's scenario list. Two major-2 files
20
+ pin that port, so the webhook lane requires `--max-workers 1`.
21
+
3
22
  ## [2.4.4] — 2026-09-18 — the corpus's first `Superseded` RFC
4
23
 
5
24
  **Why a patch, and why the suite version moves at all.** No conformance code
package/dist/cli.js CHANGED
@@ -163,9 +163,24 @@ function parseArgs(argv) {
163
163
  case '--max-workers':
164
164
  maxWorkers = parseMaxWorkers(nextValue(), '--max-workers');
165
165
  break;
166
+ // `--require-behavior` was PRESCRIBED as the certification setting by the
167
+ // v2 migration runbook and recorded in INTEROP-MATRIX rows, and the CLI
168
+ // never parsed it: it fell into the silent default arm below while only
169
+ // `OPENWOP_REQUIRE_BEHAVIOR=true` did anything. So a cut that named the
170
+ // flag measured the LOOSE answer and reported the strict one — the exact
171
+ // failure shape the runbook warns about, in the tool that measures it.
172
+ case '--require-behavior':
173
+ process.env['OPENWOP_REQUIRE_BEHAVIOR'] = 'true';
174
+ break;
166
175
  default:
167
176
  if (arg.startsWith('-')) {
168
- // Unknown flag — pass through to vitest by ignoring here.
177
+ // An unknown flag is now a HARD ERROR, not a silent pass-through. The
178
+ // old arm is why the miss above went unnoticed for the whole v2
179
+ // program: a typo or a removed flag looked identical to a working one.
180
+ process.stderr.write(`openwop-conformance: unknown flag ${arg}\n` +
181
+ 'Run `openwop-conformance --help` for usage. (A flag the CLI does not know is refused rather than ignored: ' +
182
+ 'a certification run MUST NOT silently measure something other than what its command line says.)\n');
183
+ process.exit(2);
169
184
  }
170
185
  }
171
186
  }
@@ -245,6 +260,9 @@ Certification (RFC 0089):
245
260
  to <out.json>. Requires --base-url (and --api-key as usual).
246
261
 
247
262
  Other:
263
+ --require-behavior Strict mode: an ADVERTISED behaviour that cannot be
264
+ observed FAILS instead of soft-skipping (RFC 0148 §B).
265
+ Equivalent to OPENWOP_REQUIRE_BEHAVIOR=true.
248
266
  --help, -h Show this message
249
267
 
250
268
  Examples:
@@ -99,8 +99,19 @@ export const PARTIAL_WITNESS_PREFIX = 'partial-witness: ';
99
99
  export function resolveItRecord(state, assertionCalls, gate, noted, firstError) {
100
100
  if (state === 'fail')
101
101
  return { disposition: 'executed-fail', detail: `the test executed and failed: ${(firstError ?? 'no message').slice(0, 300)}` };
102
- if (state === 'pass' && assertionCalls > 0)
103
- return { disposition: 'executed-pass' };
102
+ if (state === 'pass' && assertionCalls > 0) {
103
+ // A leg that asserted AND THEN soft-skipped is only a partial witness, and
104
+ // the file-level record has always said so (`resolveFileRecord` below).
105
+ // This `it`-level record dropped the note — and the `it`-level rows are the
106
+ // ones RFC 0174 §B.1 rule 4 reads for acceptance. Measured consequence:
107
+ // `openwop.requirement.0173.webhook-durable-delivery.dead-letter` ends in an
108
+ // UNCONDITIONAL `softSkip('blocked', 'no normative dead-letter read surface…')`
109
+ // on every host, and still recorded a bare `executed-pass` that satisfied
110
+ // RFC 0173 §B's dead-letter row on every bundle in the corpus.
111
+ return noted !== null
112
+ ? { disposition: 'executed-pass', detail: `${PARTIAL_WITNESS_PREFIX}${noted.kind}: ${noted.reason}` }
113
+ : { disposition: 'executed-pass' };
114
+ }
104
115
  if (gate !== undefined)
105
116
  return { disposition: gate.disposition, detail: gate.detail ?? `${gate.disposition} (gate recorded no reason)` };
106
117
  if (noted !== null)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.4.4",
4
- "stampSha256": "fe5763e65915d94bda320daa26bd1b9ab72cd069a347f2e7f9029ec04d714f67"
3
+ "version": "2.4.5",
4
+ "stampSha256": "2d625db1edc658e5ca483a95028e5db84c62bef71c8ced299e817c6875fbfbfd"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.4.4",
3
+ "version": "2.4.5",
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.4.4"
59
+ "@openwop/spec-artifacts": "2.4.5"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -26009,7 +26009,7 @@
26009
26009
  {
26010
26010
  "id": "openwop.it.v2-bound-id-kinds.subscriptionid-post-webhooks-mints-a-bound-webhookid-the-projected-segment-is-ac",
26011
26011
  "file": "v2-bound-id-kinds.test.ts",
26012
- "line": 60,
26012
+ "line": 61,
26013
26013
  "title": "subscriptionId: POST /webhooks mints a bound webhookId, the projected segment is accepted, and a foreign tenant segment is refused 403",
26014
26014
  "explicitId": "openwop.requirement.0187.bound-id-kinds.webhook",
26015
26015
  "citations": [
@@ -26036,7 +26036,7 @@
26036
26036
  {
26037
26037
  "id": "openwop.it.v2-bound-id-kinds.interruptid-the-id-a-suspension-puts-on-the-wire-is-bound",
26038
26038
  "file": "v2-bound-id-kinds.test.ts",
26039
- "line": 87,
26039
+ "line": 95,
26040
26040
  "title": "interruptId: the id a suspension puts on the wire is bound",
26041
26041
  "explicitId": null,
26042
26042
  "citations": []
@@ -26044,7 +26044,7 @@
26044
26044
  {
26045
26045
  "id": "openwop.it.v2-bound-id-kinds.effectid-every-row-get-runs-runid-effects-serves-is-bound",
26046
26046
  "file": "v2-bound-id-kinds.test.ts",
26047
- "line": 108,
26047
+ "line": 116,
26048
26048
  "title": "effectId: every row GET /runs/{runId}/effects serves is bound",
26049
26049
  "explicitId": "openwop.requirement.0187.bound-id-kinds.per-kind",
26050
26050
  "citations": [
@@ -26057,7 +26057,7 @@
26057
26057
  {
26058
26058
  "id": "openwop.it.v2-bound-id-kinds.deliveryid-the-kind-has-no-wire-surface-to-be-bound-on-a-corpus-gap-recorded",
26059
26059
  "file": "v2-bound-id-kinds.test.ts",
26060
- "line": 123,
26060
+ "line": 131,
26061
26061
  "title": "deliveryId: the kind has no wire surface to be bound on — a corpus gap, recorded",
26062
26062
  "explicitId": null,
26063
26063
  "citations": []
@@ -29321,7 +29321,7 @@
29321
29321
  {
29322
29322
  "id": "openwop.it.v2-webhook-delivery-shape.a-major-2-subscriber-receives-the-v2-rendering-the-delivery-validates-and-run-st",
29323
29323
  "file": "v2-webhook-delivery-shape.test.ts",
29324
- "line": 175,
29324
+ "line": 186,
29325
29325
  "title": "a major-2 subscriber receives the v2 rendering: the delivery validates, and run.started.owner carries subject, never principal",
29326
29326
  "explicitId": "openwop.requirement.0171.webhook-delivery-shape",
29327
29327
  "citations": [
@@ -29352,7 +29352,7 @@
29352
29352
  {
29353
29353
  "id": "openwop.it.v2-webhook-delivery-shape.a-major-1-subscriber-still-receives-the-v1-rendering-the-v1-wire-does-not-move-m",
29354
29354
  "file": "v2-webhook-delivery-shape.test.ts",
29355
- "line": 194,
29355
+ "line": 205,
29356
29356
  "title": "a major-1 subscriber still receives the v1 rendering — the v1 wire does not move mid-overlap",
29357
29357
  "explicitId": "openwop.requirement.0171.webhook-delivery-shape",
29358
29358
  "citations": [
@@ -29370,7 +29370,7 @@
29370
29370
  {
29371
29371
  "id": "openwop.it.v2-webhook-delivery-shape.a-seeded-era-2-run-is-delivered-projected-too-the-fan-out-branch-a-fresh-run-can",
29372
29372
  "file": "v2-webhook-delivery-shape.test.ts",
29373
- "line": 217,
29373
+ "line": 228,
29374
29374
  "title": "a seeded era-2 run is delivered projected too — the fan-out branch a fresh run cannot reach (seam-gated)",
29375
29375
  "explicitId": "openwop.requirement.0171.webhook-delivery-shape",
29376
29376
  "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.4.4",
5
- "corpusTag": "v2.4.4",
4
+ "version": "2.4.5",
5
+ "corpusTag": "v2.4.5",
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": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "563976e89c42a4d1f71198ec7af1c3d10dbf619b37fda08d15723f9a0dfbedf8",
13
- "api/v2/asyncapi.yaml": "e3670f2bcc45610ba41ac51ed71f86b2e112705678d9bc44bd6be73412ac9653",
14
- "api/v2/openapi.yaml": "1ec6df091e6406760f12ee21ee9f236ec14b30afd59b1cd3295c1dd490858067",
12
+ "api/seams-v2.yaml": "c630eb49d587cb3453b956ad7a7877b7afc7ce5730928df27b33b317736482c4",
13
+ "api/v2/asyncapi.yaml": "ce6eac3ac2407ae8a56ea08d4b4cbeca5b3c5bf573beb7f716f0f07d97f2a752",
14
+ "api/v2/openapi.yaml": "1e9a68170a184d71103a4dc03400f7f321c390042c00f42b1f2d9ebae50ff7ac",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
@@ -200,7 +200,7 @@
200
200
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
201
201
  "spec/v1/alias-detectors.json": "e90916db12d2297e2e560b2d54d2c4702d6cb3bef7179a575844687a7ec17262",
202
202
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
203
- "spec/v1/core-standard-manifest.json": "d0a809262b519c83c97b7007023bd47982c5919d25207efa9d49272d2d9bb657",
203
+ "spec/v1/core-standard-manifest.json": "7b47c64730229681956af2a0cf2c9b8fe7ac641e590cf87a157e90fe49cd6678",
204
204
  "spec/v1/deprecations.json": "c81c8f71460936c8d489d8270477c7661cf4a5d7b7bad86c98899750b5656027",
205
205
  "spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
206
206
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -219,7 +219,7 @@
219
219
  "spec/v2/core/errors.md": "ad85fc830139e587f20deabf88297b91b1fb6ce31e80d5442624a650246ab16e",
220
220
  "spec/v2/core/events.md": "f1bac16419740f3b722febb6f5caaabed8838e092cba3d080fe9184024049dcf",
221
221
  "spec/v2/core/form-content-packs.md": "6ffaae85d4550be48dd53fb6e2959af8aceb35bd87c5343b722885084f197713",
222
- "spec/v2/core/headers.md": "c1e0df141b49189a04be9462638932e71561977f56857e35011f927c9320042b",
222
+ "spec/v2/core/headers.md": "d79eb0babdd4a1a8689d40dd7f84c711c729092259b2afe859481a0902dd7342",
223
223
  "spec/v2/core/idempotency.md": "b86c352d964aebad532727f314e26ed2177d49de382076fa64d3162e2f56118d",
224
224
  "spec/v2/core/identity.md": "9d2b823eb899463807452fe5721dcf09881837c161be024175957db27870b243",
225
225
  "spec/v2/core/interop.md": "d4c404e469e1d9d9be88bc2c939d777e7d7c1d0419b1251c18770f039838a401",
@@ -271,8 +271,8 @@
271
271
  "spec/v2/path-manifest.json": "a6f4e652e866889e8bde815874062f62929aee95dc8e58e7152c56adee262163",
272
272
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
273
273
  "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
274
- "spec/v2/release.json": "06b8e81a8c0ff725e20d7609d52677e5d0d2a29abc853ecf1e10a415fc69164e",
274
+ "spec/v2/release.json": "a6818de7c8879dc510a539c0c25f7bd06f489578f7454921504551cd1129dfea",
275
275
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
276
276
  },
277
- "corpusCommit": "64aaa871715b242bb5965e8877bf0a8f13693419"
277
+ "corpusCommit": "9e47426865890fef13e6c0759f428f0e8454ba83"
278
278
  }
package/src/cli.ts CHANGED
@@ -181,9 +181,26 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
181
181
  case '--max-workers':
182
182
  maxWorkers = parseMaxWorkers(nextValue(), '--max-workers');
183
183
  break;
184
+ // `--require-behavior` was PRESCRIBED as the certification setting by the
185
+ // v2 migration runbook and recorded in INTEROP-MATRIX rows, and the CLI
186
+ // never parsed it: it fell into the silent default arm below while only
187
+ // `OPENWOP_REQUIRE_BEHAVIOR=true` did anything. So a cut that named the
188
+ // flag measured the LOOSE answer and reported the strict one — the exact
189
+ // failure shape the runbook warns about, in the tool that measures it.
190
+ case '--require-behavior':
191
+ process.env['OPENWOP_REQUIRE_BEHAVIOR'] = 'true';
192
+ break;
184
193
  default:
185
194
  if (arg.startsWith('-')) {
186
- // Unknown flag — pass through to vitest by ignoring here.
195
+ // An unknown flag is now a HARD ERROR, not a silent pass-through. The
196
+ // old arm is why the miss above went unnoticed for the whole v2
197
+ // program: a typo or a removed flag looked identical to a working one.
198
+ process.stderr.write(
199
+ `openwop-conformance: unknown flag ${arg}\n` +
200
+ 'Run `openwop-conformance --help` for usage. (A flag the CLI does not know is refused rather than ignored: ' +
201
+ 'a certification run MUST NOT silently measure something other than what its command line says.)\n',
202
+ );
203
+ process.exit(2);
187
204
  }
188
205
  }
189
206
  }
@@ -266,6 +283,9 @@ Certification (RFC 0089):
266
283
  to <out.json>. Requires --base-url (and --api-key as usual).
267
284
 
268
285
  Other:
286
+ --require-behavior Strict mode: an ADVERTISED behaviour that cannot be
287
+ observed FAILS instead of soft-skipping (RFC 0148 §B).
288
+ Equivalent to OPENWOP_REQUIRE_BEHAVIOR=true.
269
289
  --help, -h Show this message
270
290
 
271
291
  Examples:
@@ -106,7 +106,19 @@ export function resolveItRecord(
106
106
  firstError?: string,
107
107
  ): { disposition: Disposition; detail?: string } {
108
108
  if (state === 'fail') return { disposition: 'executed-fail', detail: `the test executed and failed: ${(firstError ?? 'no message').slice(0, 300)}` };
109
- if (state === 'pass' && assertionCalls > 0) return { disposition: 'executed-pass' };
109
+ if (state === 'pass' && assertionCalls > 0) {
110
+ // A leg that asserted AND THEN soft-skipped is only a partial witness, and
111
+ // the file-level record has always said so (`resolveFileRecord` below).
112
+ // This `it`-level record dropped the note — and the `it`-level rows are the
113
+ // ones RFC 0174 §B.1 rule 4 reads for acceptance. Measured consequence:
114
+ // `openwop.requirement.0173.webhook-durable-delivery.dead-letter` ends in an
115
+ // UNCONDITIONAL `softSkip('blocked', 'no normative dead-letter read surface…')`
116
+ // on every host, and still recorded a bare `executed-pass` that satisfied
117
+ // RFC 0173 §B's dead-letter row on every bundle in the corpus.
118
+ return noted !== null
119
+ ? { disposition: 'executed-pass', detail: `${PARTIAL_WITNESS_PREFIX}${noted.kind}: ${noted.reason}` }
120
+ : { disposition: 'executed-pass' };
121
+ }
110
122
  if (gate !== undefined) return { disposition: gate.disposition, detail: gate.detail ?? `${gate.disposition} (gate recorded no reason)` };
111
123
  if (noted !== null) return { disposition: noted.kind, detail: noted.reason };
112
124
  if (state === 'pass') return { disposition: 'blocked', detail: 'unclassified return: the test passed with zero assertions and recorded no reason — RFC 0148 §A resolves it to blocked, never to a pass' };
@@ -31,6 +31,7 @@ import { driver, type OpenWOPResponse } from '../lib/driver.js';
31
31
  import { v2Discovery, gateFamily } from '../lib/v2.js';
32
32
  import { readErrorCode } from '../lib/error-envelope.js';
33
33
  import { softSkip } from '../lib/soft-skip.js';
34
+ import { resolveRegistrationUrl } from '../lib/webhook-receiver.js';
34
35
  import { req } from '../lib/requirement-ids.js';
35
36
  import { projectBoundId, BOUND_ID } from '../lib/bound-id.js';
36
37
 
@@ -60,9 +61,16 @@ describe('v2 bound-id kinds (identity.md §5, RFC 0187 §A)', () => {
60
61
  it('subscriptionId: POST /webhooks mints a bound webhookId, the projected segment is accepted, and a foreign tenant segment is refused 403', async () => {
61
62
  if (!(await v2Discovery())) return softSkip('blocked', 'v2 discovery unreachable');
62
63
  if (!(await gateFamily('webhooks'))) return softSkip('inapplicable', 'webhooks family not advertised — no subscription to mint (gate recorded under openwop.family.webhooks)');
63
- const reg = await http(() => driver.post('/webhooks', { url: 'https://subscriber.invalid/hook', events: ['run.completed'] }));
64
+ // Route the mint through `resolveRegistrationUrl` so an operator who sets
65
+ // OPENWOP_WEBHOOK_RECEIVER_URL actually gets a reachable registration here.
66
+ // Before this, the blocked note TOLD them to set that variable and this file
67
+ // never read it — inoperative advice in a suite where one blocked row denies
68
+ // every claimed profile (RFC 0168 §E.1). The fallback stays a reserved
69
+ // `.invalid` host: this leg only needs the mint, never a delivery.
70
+ const registration = resolveRegistrationUrl('https://subscriber.invalid/hook');
71
+ const reg = await http(() => driver.post('/webhooks', { url: registration.url, events: ['run.completed'] }));
64
72
  if (reg === null) return softSkip('blocked', 'POST /webhooks unreachable (fetch failed)');
65
- if (reg.status === 400 && readErrorCode(reg.json) === 'webhook_url_rejected') return softSkip('blocked', 'host SSRF guard rejected the fixture receiver URL — set OPENWOP_WEBHOOK_RECEIVER_URL or allow the reserved .invalid host to witness');
73
+ if (reg.status === 400 && readErrorCode(reg.json) === 'webhook_url_rejected') return softSkip('blocked', `host SSRF guard rejected the registration URL ${registration.url}${registration.tunnelled ? ' (from OPENWOP_WEBHOOK_RECEIVER_URL)' : ' — set OPENWOP_WEBHOOK_RECEIVER_URL to a public https receiver, which THIS leg now honours'}`);
66
74
  expect(reg.status, req(WEBHOOK_ID, 'webhooks.md §Surfaces', 'POST /webhooks MUST answer 201 { webhookId }')).toBe(201);
67
75
  const webhookId = (reg.json as { webhookId?: unknown } | null)?.webhookId;
68
76
  expectBound(webhookId, 'webhookId', 'POST /webhooks 201', WEBHOOK_ID);
@@ -77,11 +77,22 @@ async function startReceiver(): Promise<{ server: Server; url: string; deliverie
77
77
  res.writeHead(204); res.end();
78
78
  });
79
79
  });
80
- const { bind, advertise } = receiverBinding();
81
- await new Promise<void>((resolve) => server.listen(0, bind, () => resolve()));
80
+ // Honour OPENWOP_WEBHOOK_RECEIVER_PORT like `v2-webhook-durable-delivery`
81
+ // does. Without it, a tunnelled run registers the tunnel URL here and the
82
+ // tunnel forwards to the PINNED port — held by the other receiver, which
83
+ // answers 500 by design — so this file's `deliveries` stays empty, its legs
84
+ // soft-skip, and (because `register()` already asserted) the rows resolve
85
+ // `executed-pass`. A wire-shape scenario that never opened a delivery body
86
+ // went green. Two major-2 files now want the same pinned port, so the
87
+ // webhook lane MUST run with `--max-workers 1`, which is already the
88
+ // certification setting.
89
+ const pinned = Number(process.env['OPENWOP_WEBHOOK_RECEIVER_PORT'] ?? '');
90
+ const bindPort = Number.isInteger(pinned) && pinned > 0 && pinned < 65536 ? pinned : 0;
91
+ const binding = receiverBinding();
92
+ await new Promise<void>((resolve) => server.listen(bindPort, binding.bind, () => resolve()));
82
93
  const addr = server.address();
83
94
  const port = typeof addr === 'object' && addr ? addr.port : 0;
84
- return { server, url: `http://${advertise}:${port}/hook`, deliveries };
95
+ return { server, url: `http://${binding.advertise}:${port}/hook`, deliveries };
85
96
  }
86
97
 
87
98
  /**