@openwop/openwop-conformance 2.45.20 → 2.45.21

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,9 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.45.21] — 2026-10-05 — `0236.webhook.host-variant` gets its own row
4
+
5
+ - **`v2-host-event-delivery`: no-fan-out is its own `it`.** A bundle records one requirement id per `it` (the last one asserted), so the webhook `it` that asserted host-variant and then no-fan-out recorded only no-fan-out. Found by the v2 reference host's 2.45.20 loopback rehearsal: five of the six RFC 0236 ids appeared. The new `it` re-runs the same gate, so a host without the families records the same disposition.
6
+
3
7
  ## [2.45.20] — 2026-10-05 — host events (RFC 0236 Active); `channelPresence` delivery witnessed through the §13 seam at v2
4
8
 
5
9
  - **New `v2-channel-presence-delivery` (seam-gated).** `conversation.md` now restates two RFC 0110 rules: a host advertising `channelPresence` MUST emit it, and MUST NOT deliver it to a non-member. Two legs check them through the snapshot seam, asking as the member first so an unwired seam is never read as a refusal.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.45.20",
4
- "stampSha256": "3fedfa905f02f5fc87e0ecea242b9485a56be19b66f7cb2e04c37d6785888604"
3
+ "version": "2.45.21",
4
+ "stampSha256": "ee91d9e41fbcdc73f379cf977fee5ac3e44590a80349c693da2cc5f3a566c68b"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.45.20",
3
+ "version": "2.45.21",
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.45.20"
59
+ "@openwop/spec-artifacts": "2.45.21"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "generatedFrom": "src/scenarios/*.test.ts",
4
4
  "counts": {
5
5
  "files": 728,
6
- "tests": 2762,
7
- "withStableId": 2762,
6
+ "tests": 2763,
7
+ "withStableId": 2763,
8
8
  "interpolatedTitles": 0,
9
9
  "explicitIds": 2626
10
10
  },
@@ -31509,7 +31509,7 @@
31509
31509
  {
31510
31510
  "id": "openwop.it.v2-host-event-delivery.an-emitted-durable-host-event-arrives-on-host-events-as-a-valid-envelope",
31511
31511
  "file": "v2-host-event-delivery.test.ts",
31512
- "line": 122,
31512
+ "line": 131,
31513
31513
  "title": "an emitted durable host event arrives on /host/events as a valid envelope",
31514
31514
  "explicitId": "openwop.requirement.0236.host-event.envelope",
31515
31515
  "citations": [
@@ -31522,7 +31522,7 @@
31522
31522
  {
31523
31523
  "id": "openwop.it.v2-host-event-delivery.an-ephemeral-host-event-has-no-id-and-is-not-redelivered-after-last-event-id",
31524
31524
  "file": "v2-host-event-delivery.test.ts",
31525
- "line": 136,
31525
+ "line": 145,
31526
31526
  "title": "an ephemeral host event has no id: and is not redelivered after Last-Event-ID",
31527
31527
  "explicitId": null,
31528
31528
  "citations": []
@@ -31530,7 +31530,7 @@
31530
31530
  {
31531
31531
  "id": "openwop.it.v2-host-event-delivery.registerwebhook-refuses-an-ephemeral-host-event-type-with-400-validation-error",
31532
31532
  "file": "v2-host-event-delivery.test.ts",
31533
- "line": 153,
31533
+ "line": 162,
31534
31534
  "title": "registerWebhook refuses an ephemeral host-event type with 400 validation_error",
31535
31535
  "explicitId": "openwop.requirement.0236.webhook.ephemeral-refused",
31536
31536
  "citations": [
@@ -31546,10 +31546,10 @@
31546
31546
  ]
31547
31547
  },
31548
31548
  {
31549
- "id": "openwop.it.v2-host-event-delivery.a-subscribed-durable-host-event-is-delivered-as-hostevent-signed-the-ephemeral-o",
31549
+ "id": "openwop.it.v2-host-event-delivery.a-subscribed-durable-host-event-is-delivered-as-hostevent-signed",
31550
31550
  "file": "v2-host-event-delivery.test.ts",
31551
- "line": 166,
31552
- "title": "a subscribed durable host event is delivered as { hostEvent }, signed; the ephemeral one is not",
31551
+ "line": 175,
31552
+ "title": "a subscribed durable host event is delivered as { hostEvent }, signed",
31553
31553
  "explicitId": "openwop.requirement.0236.webhook.host-variant",
31554
31554
  "citations": [
31555
31555
  {
@@ -31559,10 +31559,18 @@
31559
31559
  }
31560
31560
  ]
31561
31561
  },
31562
+ {
31563
+ "id": "openwop.it.v2-host-event-delivery.the-same-subscription-never-receives-the-ephemeral-host-event",
31564
+ "file": "v2-host-event-delivery.test.ts",
31565
+ "line": 211,
31566
+ "title": "the same subscription never receives the ephemeral host event",
31567
+ "explicitId": null,
31568
+ "citations": []
31569
+ },
31562
31570
  {
31563
31571
  "id": "openwop.it.v2-host-event-delivery.a-second-tenant-receives-nothing-for-the-first-tenant-s-host-event",
31564
31572
  "file": "v2-host-event-delivery.test.ts",
31565
- "line": 200,
31573
+ "line": 224,
31566
31574
  "title": "a second tenant receives nothing for the first tenant’s host event",
31567
31575
  "explicitId": null,
31568
31576
  "citations": []
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
3
3
  "package": "@openwop/spec-artifacts",
4
- "version": "2.45.20",
5
- "corpusTag": "v2.45.20",
4
+ "version": "2.45.21",
5
+ "corpusTag": "v2.45.21",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "ac6de5303872664b78ff419c035915593e01599e02835e777a0123de1baeb398",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "3d62475a6e5d2f0e005aa58b95c197f8846104d8cb7753c2161d7b6948cb89b4",
13
- "api/v2/asyncapi.yaml": "44ea76f380b18c54ccdb51eaa091289212cd044e656c4d97e94df7bf048972e9",
14
- "api/v2/openapi.yaml": "4ebf762aabf5d25318923ac0c8a2b865e11d7e07b2a2456ab6b535d91e56da33",
12
+ "api/seams-v2.yaml": "b589c75cf872617be98ffe553cd4e738b03469d26ee1e8a139d07cb185e61be1",
13
+ "api/v2/asyncapi.yaml": "a0af325b50f06b2c493d5e7e6cab81cc7af3928065d9c12560a32029b1983a78",
14
+ "api/v2/openapi.yaml": "173a39da5f735c10b9e24c3582d7aeb05c42e4c221761dfc940e894d9f0728fd",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "a829e3208413062388e52ab6ba714d35889c9c7645db38a290b0c4f4992cd3da",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -208,7 +208,7 @@
208
208
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
209
209
  "spec/v1/alias-detectors.json": "0b2f28808ffe0b9d98a187775038c3406bb70919747a788a6e4f866e293ae6d3",
210
210
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
211
- "spec/v1/core-standard-manifest.json": "d7c4865e30b6eed18c932ca0c4e4bbcbcb3f4cfedfdd560876a436d0ed774ba0",
211
+ "spec/v1/core-standard-manifest.json": "9aa532383ef7f21117d806a74796680a296c8963f5dae666600f318d560ec25c",
212
212
  "spec/v1/deprecations.json": "a3293072ea951857395d05f0437264e88965f1e9019ff61c55bacfa9c1aaa17c",
213
213
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
214
214
  "spec/v1/end-of-support.json": "35f0bf3b70fe78b16e2a2fd81a153fc27e703f6c16376c6fe7cdf1f4f2c4e6d4",
@@ -311,9 +311,9 @@
311
311
  "spec/v2/path-manifest.json": "b84c7561f50c7df0cb61b202bd6595acad3ee7edca7a45f711949bea3c69bd27",
312
312
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
313
313
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
314
- "spec/v2/release.json": "84c88ee17dd416060ba7d564fb3d47fdb528c4efa9cd8e483749b77557dac816",
314
+ "spec/v2/release.json": "839927946b61bb164d5b55121ccf51c7e109b8f444dcb09a5de0412ebfb4031e",
315
315
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
316
- "spec/v2/surface-baseline.json": "11600f3b5d689962b34aaa1bdfa4c3e2a47632b914a945b356b825dd494ec9db"
316
+ "spec/v2/surface-baseline.json": "0b284b59b87c69acbaf06caff0da67bee0e4221ac8de1f3fd21cc9c5e5922f0f"
317
317
  },
318
- "corpusCommit": "d6b2495ddf9ac4b82580a9e0f055f29beccfe24a"
318
+ "corpusCommit": "7a395e42ec42b03017af5cadc2ef9006a606cef8"
319
319
  }
@@ -18,7 +18,9 @@
18
18
  * 0236.webhook.host-variant a subscribed durable event arrives as
19
19
  * `{ hostEvent }`, typed and signed;
20
20
  * 0236.ephemeral.no-fan-out the same subscription never receives the
21
- * ephemeral event;
21
+ * ephemeral event (its own `it`, reading what
22
+ * the host-variant leg observed: a bundle
23
+ * records one requirement id per `it`);
22
24
  * 0236.host-event.tenant-isolation a second tenant's stream receives nothing
23
25
  * for the first tenant's event.
24
26
  *
@@ -111,6 +113,13 @@ async function streamWhile(act: () => Promise<void>, opts: { lastEventId?: strin
111
113
  return [...(await sub).events];
112
114
  }
113
115
 
116
+ /**
117
+ * What the host-variant leg observed, for the no-fan-out leg. They are two
118
+ * `it`s because a bundle records one requirement id per `it` (the last one it
119
+ * asserts), so one `it` asserting both ids recorded only no-fan-out (2.45.20).
120
+ */
121
+ let fanOut: { bodies: string[]; ephemeralId: string } | { skipped: string } = { skipped: 'the host-variant leg did not run' };
122
+
114
123
  const registered: string[] = [];
115
124
  let receiver: ScopedReceiver | null = null;
116
125
  afterEach(async () => {
@@ -163,7 +172,8 @@ describe('v2 host events (RFC 0236 — seam-gated)', () => {
163
172
  expect(readErrorCode(res.json), req(ID_REFUSED, 'spec/v2/core/webhooks.md §Surfaces', 'the refusal code MUST be validation_error')).toBe('validation_error');
164
173
  }, 20_000);
165
174
 
166
- it('a subscribed durable host event is delivered as { hostEvent }, signed; the ephemeral one is not', async () => {
175
+ it('a subscribed durable host event is delivered as { hostEvent }, signed', async () => {
176
+ fanOut = { skipped: 'the host-variant leg skipped before observing deliveries' };
167
177
  const g = await gate();
168
178
  if ('kind' in g) return skip(g);
169
179
  if ((await familyAdvertised('webhooks')) === null) return softSkip('inapplicable', 'the host does not advertise webhooks');
@@ -193,10 +203,24 @@ describe('v2 host events (RFC 0236 — seam-gated)', () => {
193
203
  }
194
204
  const h = (name: string): string | undefined => { const v = hit.headers[name]; return Array.isArray(v) ? v[0] : v; };
195
205
  const verified = verifyWebhookDelivery(SECRET, h('openwop-signature') ?? '', h('openwop-signature-algorithm'), h('openwop-timestamp') ?? '', hit.body, createReceiverState()).accepted === true;
206
+ // Everything the subscription received up to the durable delivery, which was emitted after the ephemeral one.
207
+ fanOut = { bodies: hits.map((x) => x.body), ephemeralId };
196
208
  assertAll(ID_HOST_BODY, WDOC, judgeHostBody(hit.body, h('openwop-event-type'), verified, durableId, d.type, webhookDelivery));
197
- assertAll(ID_NO_FAN_OUT, DOC, [judgeNoFanOut(hits.map((x) => x.body), ephemeralId)]);
198
209
  }, 30_000);
199
210
 
211
+ it('the same subscription never receives the ephemeral host event', async () => {
212
+ // The same gate as the host-variant leg, so a host without the families records the same disposition here.
213
+ const g = await gate();
214
+ if ('kind' in g) return skip(g);
215
+ if ((await familyAdvertised('webhooks')) === null) return softSkip('inapplicable', 'the host does not advertise webhooks');
216
+ const d = pick(g.types, 'durable');
217
+ const e = pick(g.types, 'ephemeral');
218
+ if ('kind' in d) return skip(d);
219
+ if ('kind' in e) return skip(e);
220
+ if ('skipped' in fanOut) return softSkip('blocked', `no-fan-out reads the deliveries the host-variant leg observed, and ${fanOut.skipped}`);
221
+ assertAll(ID_NO_FAN_OUT, DOC, [judgeNoFanOut(fanOut.bodies, fanOut.ephemeralId)]);
222
+ });
223
+
200
224
  it('a second tenant receives nothing for the first tenant’s host event', async () => {
201
225
  const g = await gate();
202
226
  if ('kind' in g) return skip(g);