@openwop/openwop-conformance 2.3.2 → 2.3.3
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,17 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.3.3] — 2026-09-17 — the retirement lane bites again
|
|
4
|
+
|
|
5
|
+
**Why a patch.** One leg corrected, nothing added or removed.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`v2-bound-id-path-projection.test.ts`** — the double-projection leg expects
|
|
10
|
+
`404` on a dual-stack host and `400 validation_error` on a single-major one
|
|
11
|
+
(`identity.md` §5 refuses the bare form once no 1.x member is advertised),
|
|
12
|
+
read from `protocolVersions`. Found by the reference host's retirement lane.
|
|
13
|
+
- **`v2-webhook-delivery-shape.test.ts`** — one Ajv per wire, cached across legs.
|
|
14
|
+
|
|
3
15
|
## [2.3.2] — 2026-09-17 — the webhook reader read the wrong field
|
|
4
16
|
|
|
5
17
|
**Why a patch.** No scenario is added or removed; one is corrected. The 2.3.1 file asserted `event.owner` on a `run.completed` delivery — the envelope is closed and names no `owner`, and that payload carries none — so leg 1 was unsatisfiable and leg 3 vacuous on every host. Found by the reference host's W4 cut.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
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.3.
|
|
59
|
+
"@openwop/spec-artifacts": "2.3.3"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -26042,6 +26042,10 @@
|
|
|
26042
26042
|
"requirement": null,
|
|
26043
26043
|
"interpolated": true
|
|
26044
26044
|
},
|
|
26045
|
+
{
|
|
26046
|
+
"section": "spec/v2/core/identity.md §5",
|
|
26047
|
+
"requirement": "a single-major host refuses the bare form with validation_error (identity.md §5), not not_found"
|
|
26048
|
+
},
|
|
26045
26049
|
{
|
|
26046
26050
|
"section": "spec/v2/core/identity.md §5",
|
|
26047
26051
|
"requirement": null,
|
|
@@ -29229,7 +29233,7 @@
|
|
|
29229
29233
|
{
|
|
29230
29234
|
"id": "openwop.it.v2-webhook-delivery-shape.a-major-2-subscriber-receives-the-v2-rendering-the-delivery-validates-and-run-st",
|
|
29231
29235
|
"file": "v2-webhook-delivery-shape.test.ts",
|
|
29232
|
-
"line":
|
|
29236
|
+
"line": 175,
|
|
29233
29237
|
"title": "a major-2 subscriber receives the v2 rendering: the delivery validates, and run.started.owner carries subject, never principal",
|
|
29234
29238
|
"explicitId": "openwop.requirement.0171.webhook-delivery-shape",
|
|
29235
29239
|
"citations": [
|
|
@@ -29260,7 +29264,7 @@
|
|
|
29260
29264
|
{
|
|
29261
29265
|
"id": "openwop.it.v2-webhook-delivery-shape.a-major-1-subscriber-still-receives-the-v1-rendering-the-v1-wire-does-not-move-m",
|
|
29262
29266
|
"file": "v2-webhook-delivery-shape.test.ts",
|
|
29263
|
-
"line":
|
|
29267
|
+
"line": 194,
|
|
29264
29268
|
"title": "a major-1 subscriber still receives the v1 rendering — the v1 wire does not move mid-overlap",
|
|
29265
29269
|
"explicitId": "openwop.requirement.0171.webhook-delivery-shape",
|
|
29266
29270
|
"citations": [
|
|
@@ -29278,7 +29282,7 @@
|
|
|
29278
29282
|
{
|
|
29279
29283
|
"id": "openwop.it.v2-webhook-delivery-shape.a-seeded-era-2-run-is-delivered-projected-too-the-fan-out-branch-a-fresh-run-can",
|
|
29280
29284
|
"file": "v2-webhook-delivery-shape.test.ts",
|
|
29281
|
-
"line":
|
|
29285
|
+
"line": 217,
|
|
29282
29286
|
"title": "a seeded era-2 run is delivered projected too — the fan-out branch a fresh run cannot reach (seam-gated)",
|
|
29283
29287
|
"explicitId": "openwop.requirement.0171.webhook-delivery-shape",
|
|
29284
29288
|
"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.3.
|
|
5
|
-
"corpusTag": "v2.3.
|
|
4
|
+
"version": "2.3.3",
|
|
5
|
+
"corpusTag": "v2.3.3",
|
|
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": "ac5a7fdff795eeee34d61af878afd61657ade24745dc8e87b970a18148571ed5",
|
|
13
|
+
"api/v2/asyncapi.yaml": "1bb9761fc9e90b5dd5c05f208b01d597691b52792dd2a488d4382467390d55dd",
|
|
14
|
+
"api/v2/openapi.yaml": "883ac5a1e6d9056b1acdd79538341d05a005f3f58fb9751237fd6d9ba0167fc9",
|
|
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": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
|
|
202
202
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
203
|
-
"spec/v1/core-standard-manifest.json": "
|
|
203
|
+
"spec/v1/core-standard-manifest.json": "2c9150bdde2ae37e788f947110acb2503ff33299f5cf74bc522bcf32f10bd02e",
|
|
204
204
|
"spec/v1/deprecations.json": "508071308e762c281807b487e8f3840a1a69b978071894593e18f2e2d3e4b5c0",
|
|
205
205
|
"spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
|
|
206
206
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
"spec/v2/core/runs.md": "f7a95fc82fda5b7c0c613de6e5edd376bbd6d8ba4c665a500c94eacbe42b86f2",
|
|
232
232
|
"spec/v2/core/security-defaults.md": "1d38aa03b9d2507f1d4a985333cc57ae7892cecc7356f6342fbd1672e34f638a",
|
|
233
233
|
"spec/v2/core/versioning.md": "d731782eeabb953919c7c7057340b7f9c2916b6186790e6b04c7d8ea60767189",
|
|
234
|
-
"spec/v2/core/webhooks.md": "
|
|
234
|
+
"spec/v2/core/webhooks.md": "158aff629f66cf1ea483e188eba3ef9695d9868d9b525353189db6d7a3c099d6",
|
|
235
235
|
"spec/v2/core/workflow-chain-packs.md": "ef2b07d2d99334eaec0cc370314e2ad2f84ea58deb699e5d92e6e52d6056613f",
|
|
236
236
|
"spec/v2/declaration.json": "a09bac61c1b825e4a77b5abc0f86f1f8c20c381e0650eb08c2f6d0ef0aaec1dc",
|
|
237
237
|
"spec/v2/declaration.schema.json": "c86e3083fb9789c1958ecb4576cc6f71aa90e6f339521139238543ed08211dd4",
|
|
@@ -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": "
|
|
274
|
+
"spec/v2/release.json": "ffb0a24cba6ab87c4b99a76d51868e42eb07bd337157836cf5e59b9e13ad44e0",
|
|
275
275
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
|
|
276
276
|
},
|
|
277
|
-
"corpusCommit": "
|
|
277
|
+
"corpusCommit": "c2e93ea53a5c87dea90697dc166d540b6fc0f945"
|
|
278
278
|
}
|
|
@@ -102,12 +102,26 @@ describe('v2 bound-id path projection (identity.md §5)', () => {
|
|
|
102
102
|
// projections on a single read path, so this is a live shape, not a
|
|
103
103
|
// thought experiment. Asserting 404 (not merely "not 200") keeps the leg
|
|
104
104
|
// from passing on a 500.
|
|
105
|
+
// What "not resolve" looks like depends on the host's majors. Decoded once, a
|
|
106
|
+
// double projection is a BARE id (it still carries `~2F`, no slash). Through
|
|
107
|
+
// the overlap the bare form is admitted and resolved under the caller's tenant
|
|
108
|
+
// — no such run, 404. Once the host advertises no 1.x member, identity.md §5
|
|
109
|
+
// requires the bare form itself to be refused `400 validation_error` — and
|
|
110
|
+
// the reference host's retirement lane (2.3.2) failed a CONFORMANT retired
|
|
111
|
+
// host on an unconditional 404 here.
|
|
105
112
|
const twice = projectBoundId(projected);
|
|
106
113
|
const doubled = await http(() => driver.get(`/runs/${twice}`));
|
|
114
|
+
const disc = await v2Discovery();
|
|
115
|
+
const versions = Array.isArray(disc?.['protocolVersions']) ? (disc?.['protocolVersions'] as unknown[]).map(String) : [];
|
|
116
|
+
const singleMajor = !versions.some((v) => v.startsWith('1.'));
|
|
117
|
+
const want = singleMajor ? 400 : 404;
|
|
107
118
|
expect(
|
|
108
119
|
doubled?.status ?? null,
|
|
109
|
-
req(ID, DOC, `a DOUBLE-projected segment MUST NOT resolve — the codec is not idempotent, so projecting twice yields a different id and a host that does it strands its own links. Expected
|
|
110
|
-
).toBe(
|
|
120
|
+
req(ID, DOC, `a DOUBLE-projected segment MUST NOT resolve — the codec is not idempotent, so projecting twice yields a different id and a host that does it strands its own links. Expected ${want} (${singleMajor ? 'single-major host: the decoded bare form is refused 400 validation_error' : 'dual-stack host: the bare form resolves under the caller tenant and is not found'}) for ${twice}, got ${doubled?.status ?? 'no response'} ${readErrorCode(doubled?.json) ?? ''}`.trim()),
|
|
121
|
+
).toBe(want);
|
|
122
|
+
if (singleMajor) {
|
|
123
|
+
expect(readErrorCode(doubled?.json), req(ID, DOC, 'a single-major host refuses the bare form with validation_error (identity.md §5), not not_found')).toBe('validation_error');
|
|
124
|
+
}
|
|
111
125
|
|
|
112
126
|
// DECODER RULE. A `~` not introducing two hex digits is malformed input, so
|
|
113
127
|
// 400 — not 404, which would say "no such run" about a request that never
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `spec/v2/core/webhooks.md` §Delivery — a webhook delivery's `event` is the
|
|
3
|
-
* verbatim run event AS THE SUBSCRIBER'S CONTRACT RENDERS IT (suite 2.3.
|
|
3
|
+
* verbatim run event AS THE SUBSCRIBER'S CONTRACT RENDERS IT (suite 2.3.3,
|
|
4
4
|
* target major 2; gated on the `webhooks` family; creates one run per leg).
|
|
5
5
|
*
|
|
6
6
|
* `webhooks.md:22`: "The delivery envelope is generated from the same payload
|
|
@@ -146,8 +146,11 @@ function ownerOf(event: Record<string, unknown>): Record<string, unknown> | null
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/** One Ajv per wire: the v2 tree under `schemas/v2/`, the v1 tree at the root — each addressed by its absolute `$id`. */
|
|
149
|
+
const AJV_BY_MAJOR = new Map<1 | 2, Ajv2020>();
|
|
149
150
|
function validators(major: 1 | 2): { ref: (id: string) => Validator } {
|
|
150
|
-
const
|
|
151
|
+
const cached = AJV_BY_MAJOR.get(major);
|
|
152
|
+
if (cached) return { ref: (id) => { const fn = cached.compile({ $ref: id }); return (doc) => ({ ok: fn(doc) === true, errors: cached.errorsText(fn.errors, { separator: '; ' }) }); } };
|
|
153
|
+
const a = new Ajv2020({ strict: false, allErrors: true }); addFormats(a); AJV_BY_MAJOR.set(major, a);
|
|
151
154
|
const dir = major === 2 ? join(SCHEMAS_DIR, 'v2') : SCHEMAS_DIR;
|
|
152
155
|
for (const f of readdirSync(dir)) {
|
|
153
156
|
const full = join(dir, f);
|