@openwop/openwop-conformance 2.24.0 → 2.26.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.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.26.0] — 2026-09-19 — RFC 0188 Accepted
|
|
4
|
+
|
|
5
|
+
- No scenario change. Suite pinned to corpus `2.26.0`; the reference-host bundle it certified is checked in at suite 2.25.0.
|
|
6
|
+
|
|
7
|
+
## [2.25.0] — 2026-09-19 — a leg that asserted nothing
|
|
8
|
+
|
|
9
|
+
- `v2-webhook-durable-delivery` — the `0188.dead-letter-content-free` leg read a **fresh** subscription's sink, looped over zero rows, and asserted nothing; RFC 0148 §A resolves that silent return to `blocked`, denying certification. It now exhausts a real delivery first and asserts over the records it gets, and records `blocked` with a reason when the sink is empty.
|
|
10
|
+
- Suite pinned to corpus `2.25.0`.
|
|
11
|
+
|
|
3
12
|
## [2.24.0] — 2026-09-19 — the envelope-kind catalog gets a seat
|
|
4
13
|
|
|
5
14
|
- `v2-envelope-catalog.test.ts` — six corpus witnesses for RFC 0193. The generator leg sabotages a scratch copy (removes a seat file, asserts the refusal names the family), because the committed tree is clean and a bare exit-0 wrapper would pass just as happily with the guard deleted. The optionality leg asserts no seat is `required` — MyndHyve publishes two of these records without one, and the v2 root is closed.
|
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.
|
|
14
|
+
npm install --legacy-peer-deps @openwop/openwop-conformance@2.26.0 @openwop/spec-artifacts@2.26.0
|
|
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.
|
|
3
|
+
"version": "2.26.0",
|
|
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.
|
|
59
|
+
"@openwop/spec-artifacts": "2.26.0"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -29469,7 +29469,8 @@
|
|
|
29469
29469
|
"citations": [
|
|
29470
29470
|
{
|
|
29471
29471
|
"section": "RFC 0188 §B.1",
|
|
29472
|
-
"requirement":
|
|
29472
|
+
"requirement": null,
|
|
29473
|
+
"interpolated": true
|
|
29473
29474
|
}
|
|
29474
29475
|
]
|
|
29475
29476
|
},
|
|
@@ -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.
|
|
5
|
-
"corpusTag": "v2.
|
|
4
|
+
"version": "2.26.0",
|
|
5
|
+
"corpusTag": "v2.26.0",
|
|
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": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
12
|
+
"api/seams-v2.yaml": "e428e9987de0e143fc1bb69f5c03b8f907d10b12e4fd07d007d9e9da18d5633f",
|
|
13
|
+
"api/v2/asyncapi.yaml": "ef1f4731966838122e8efdfe21bc32197afa01863a7ac673f09fe0b960ea07e6",
|
|
14
|
+
"api/v2/openapi.yaml": "b48ce1f4078ca6e760f26a86497ee78922709149f83626507593ed2ff0c016c0",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
202
202
|
"spec/v1/alias-detectors.json": "40069d5976eeb6ba1384a648e57e5cfd673db3fce36175115fb8293bee9664d4",
|
|
203
203
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
204
|
-
"spec/v1/core-standard-manifest.json": "
|
|
204
|
+
"spec/v1/core-standard-manifest.json": "672ff5dffbf6f6405dd5c49b131da3b09f6d07224c03b666d14b67ecf0b96c38",
|
|
205
205
|
"spec/v1/deprecations.json": "307083ce29c23fd406015951f99a30d78d6187ff061d38dc9732f62191b40f3f",
|
|
206
206
|
"spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
|
|
207
207
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -279,8 +279,8 @@
|
|
|
279
279
|
"spec/v2/path-manifest.json": "c123c9fd77dc1b3f9c2e9346ace80cdae007e6511138894956abda01e61ec1c6",
|
|
280
280
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
281
281
|
"spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
|
|
282
|
-
"spec/v2/release.json": "
|
|
282
|
+
"spec/v2/release.json": "462c3eb32192b27b3f365566bb240cbd33cc0d5096ce7f07dc64a1d4e57be1e4",
|
|
283
283
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
|
|
284
284
|
},
|
|
285
|
-
"corpusCommit": "
|
|
285
|
+
"corpusCommit": "6f3e9efef34d53e5ddce596470d7bc13c515d19b"
|
|
286
286
|
}
|
|
@@ -400,22 +400,57 @@ describe('RFC 0173 §B — webhook-durable-delivery (gated on webhooks)', () =>
|
|
|
400
400
|
it('a dead-letter record carries no delivered payload', async () => {
|
|
401
401
|
// Its own `it`: RFC 0168 §A.1 allows one explicit requirement id per it(),
|
|
402
402
|
// and `check-req-only` enforces it.
|
|
403
|
-
|
|
403
|
+
//
|
|
404
|
+
// THIS LEG USED TO REGISTER A FRESH SUBSCRIPTION AND READ ITS SINK. A fresh
|
|
405
|
+
// subscription has no dead letters by construction, so the loop over
|
|
406
|
+
// `deliveries` ran zero times, the test asserted NOTHING, and RFC 0148 §A
|
|
407
|
+
// resolves a silent return to `blocked` — which denies certification
|
|
408
|
+
// (RFC 0168 §E.1). It went unnoticed because every host recorded
|
|
409
|
+
// `inapplicable` for want of the facet: the first host ever to advertise
|
|
410
|
+
// `webhooks.deadLetter` was refused certification by this leg on its first
|
|
411
|
+
// cut, with 232 other rows passing and zero failing.
|
|
412
|
+
//
|
|
413
|
+
// §B.1 is a claim about what a REAL record carries, so the leg has to make
|
|
414
|
+
// one: exhaust a delivery against a receiver that never succeeds — the same
|
|
415
|
+
// move the sibling 0173 dead-letter leg performs — and then read.
|
|
416
|
+
const doc = await discovery();
|
|
417
|
+
if (!doc) return softSkip('blocked', 'discovery unreachable');
|
|
404
418
|
const fam = await gateFamily('webhooks');
|
|
405
|
-
if (!fam
|
|
406
|
-
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
await
|
|
419
|
+
if (!fam) return softSkip('inapplicable', 'webhooks family not advertised');
|
|
420
|
+
if (!fam['deadLetter']) return softSkip('inapplicable', 'host does not advertise the webhooks.deadLetter facet — RFC 0188 §A.5 makes the read a 404 rather than an obligation');
|
|
421
|
+
if (!fixtureAdvertised(doc, FIXTURE)) return softSkip('inapplicable', `${FIXTURE} fixture not advertised — no delivery to exhaust`);
|
|
422
|
+
|
|
423
|
+
const receiver = await startReceiver(Number.POSITIVE_INFINITY); // never succeeds
|
|
424
|
+
active = receiver.server;
|
|
425
|
+
const sub = await register(receiver.url);
|
|
426
|
+
if (sub === null) return softSkip('blocked', 'registration refused (reason recorded above)');
|
|
427
|
+
const create = await driver.post('/runs', { workflowId: FIXTURE });
|
|
428
|
+
if (create.status !== 201) {
|
|
429
|
+
await driver.delete(`/webhooks/${encodeURIComponent(sub.webhookId)}`);
|
|
430
|
+
return softSkip('blocked', `POST /runs answered ${create.status} — no delivery to exhaust`);
|
|
431
|
+
}
|
|
432
|
+
const runId = (create.json as { runId: string }).runId;
|
|
433
|
+
await waitTerminal(runId, 10_000);
|
|
434
|
+
const policy = advertisedRetryPolicy(doc);
|
|
435
|
+
// Filter by SUBSCRIPTION as well as run, as the sibling leg does: a host
|
|
436
|
+
// that cannot reach loopback shares one tunnelled receiver URL across
|
|
437
|
+
// scenarios, so another scenario's attempts land in this budget otherwise.
|
|
438
|
+
const ours = () => receiver.attempts.filter((a) => a.runId === runId && a.webhookId === sub.webhookId);
|
|
439
|
+
await waitFor(() => ours().length >= (policy?.maxAttempts ?? 2), retryWaitMs(doc));
|
|
440
|
+
await new Promise((r) => setTimeout(r, 1_000));
|
|
441
|
+
|
|
442
|
+
const sink = await driver.get(`/webhooks/${projectBoundId(sub.webhookId)}/dead-letters`);
|
|
443
|
+
await driver.delete(`/webhooks/${encodeURIComponent(sub.webhookId)}`);
|
|
412
444
|
if (sink.status !== 200) return softSkip('blocked', `the dead-letter read answered ${sink.status}`);
|
|
413
445
|
const rows = ((sink.json as { deliveries?: Array<Record<string, unknown>> } | null)?.deliveries ?? []);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
).toBe(true);
|
|
446
|
+
// An empty sink is NOT a pass. Recording one as a pass is exactly the
|
|
447
|
+
// vacuous witness this leg used to produce; say so instead.
|
|
448
|
+
if (rows.length === 0) {
|
|
449
|
+
return softSkip('blocked', 'the exhausted delivery did not reach the sink inside the retry window — §B.1 is a claim about a real record and there is none here to read');
|
|
419
450
|
}
|
|
451
|
+
expect(
|
|
452
|
+
rows.every((r) => r['body'] === undefined && r['headers'] === undefined && r['secret'] === undefined),
|
|
453
|
+
req('openwop.requirement.0188.dead-letter-content-free', 'RFC 0188 §B.1', `a dead-letter record MUST NOT carry the delivered body, headers or subscription secret — the queue is precisely the traffic the subscriber never received, so a record carrying any of it turns one read scope into a replay of that traffic for the whole retention window (read ${rows.length} record(s))`),
|
|
454
|
+
).toBe(true);
|
|
420
455
|
}, DEAD_LETTER_TEST_TIMEOUT_MS);
|
|
421
456
|
});
|