@openwop/openwop-conformance 2.1.0 → 2.1.2

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,23 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.1.2] — 2026-09-11 — `v2-version-header-honored` failed conformant single-major hosts
4
+
5
+ Leg 1 compared a header-less fetch with an `OpenWOP-Version: 2.0` fetch on
6
+ every host. Where `protocolVersions[]` carries no `1.x` member both name the
7
+ same contract (§1.3 row 3 + §1.1), so byte identity is correct and the
8
+ comparison measures nothing. The leg now branches: overlap unchanged;
9
+ single-major asserts the refusal of an unadvertised major (`406
10
+ protocol_version_unsupported` + root `details.protocolVersions[]`), which is
11
+ the only measurement of §1.3 row 2 left once `v2-dual-stack-negotiation` gates
12
+ itself off. No other scenario changed.
13
+
14
+ ## [2.1.1] — 2026-09-11 — `v2-run-list` is now actually selected
15
+
16
+ `scenario-majors.json` regenerated (517 files, v2 74): 2.1.0 packaged the
17
+ scenario but its registry row was missing, so `--target-major 2` never
18
+ selected it and every 2.1.0 run reported 73 files. The corpus gate now runs
19
+ `generate-scenario-majors.mjs --check`. No scenario prose changed.
20
+
3
21
  ## [2.1.0] — 2026-09-11 — the run-list scenario (RFC 0182)
4
22
 
5
23
  New scenario `v2-run-list` (target major 2, gated on the `runList` family —
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.1.0",
4
- "stampSha256": "f2df875754c421e593aab2718938fe29cad09077a079a5193f815d92c81f70f5"
3
+ "version": "2.1.2",
4
+ "stampSha256": "0edc38b6fbd090fb3878074bb5836ae535a8fd7e4fa0e835c519e55f8329c545"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
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.1.0"
59
+ "@openwop/spec-artifacts": "2.1.2"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -28941,10 +28941,15 @@
28941
28941
  {
28942
28942
  "id": "openwop.it.v2-version-header-honored.openwop-version-is-honored-or-refused-never-ignored",
28943
28943
  "file": "v2-version-header-honored.test.ts",
28944
- "line": 61,
28944
+ "line": 95,
28945
28945
  "title": "OpenWOP-Version is honored or refused — never ignored",
28946
28946
  "explicitId": "openwop.requirement.0172.version-header-honored",
28947
28947
  "citations": [
28948
+ {
28949
+ "section": "spec/v2/core/versioning.md §1.3",
28950
+ "requirement": null,
28951
+ "interpolated": true
28952
+ },
28948
28953
  {
28949
28954
  "section": "spec/v2/core/versioning.md §1.3",
28950
28955
  "requirement": "a host that does not serve major 2 MUST refuse with 406 protocol_version_unsupported, which it did"
@@ -28958,7 +28963,7 @@
28958
28963
  {
28959
28964
  "id": "openwop.it.v2-version-header-honored.a-host-advertising-two-majors-serves-two-different-representations",
28960
28965
  "file": "v2-version-header-honored.test.ts",
28961
- "line": 92,
28966
+ "line": 166,
28962
28967
  "title": "a host advertising two majors serves two different representations",
28963
28968
  "explicitId": "openwop.requirement.0172.version-header-honored",
28964
28969
  "citations": [
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "$comment": "GENERATED by conformance/scripts/generate-scenario-majors.mjs (RFC 0168 §D.3). Do not edit; add a file to BOTH_MAJORS in the generator to target both majors.",
3
3
  "counts": {
4
- "files": 516,
4
+ "files": 517,
5
5
  "v1": 445,
6
- "v2": 73
6
+ "v2": 74
7
7
  },
8
8
  "majors": {
9
9
  "a2a-1-0-agent-card.test.ts": [
@@ -1406,6 +1406,9 @@
1406
1406
  "v2-run-fork-refusals.test.ts": [
1407
1407
  2
1408
1408
  ],
1409
+ "v2-run-list.test.ts": [
1410
+ 2
1411
+ ],
1409
1412
  "v2-run-options-limits.test.ts": [
1410
1413
  2
1411
1414
  ],
@@ -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.1.0",
5
- "corpusTag": "v2.1.0",
4
+ "version": "2.1.2",
5
+ "corpusTag": "v2.1.2",
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": "bf40af2260e55099ac68020a51f6d931ebca05f3bf385bc76cf57ad1d65d50a8",
13
- "api/v2/asyncapi.yaml": "26a4a9a3a1f3ecf3a7e6e559941872877461b6677f2b0e0bb187332aec56d069",
14
- "api/v2/openapi.yaml": "ec983d886bf26e1fc2f52ff456ce44e145b601ac1420e8a490bcee7a23fcdd0f",
12
+ "api/seams-v2.yaml": "3f3af1a2f5c6d44746f76e2c35aafee59a0d736fd433fcda85e4b9302da8042b",
13
+ "api/v2/asyncapi.yaml": "83e2f5d6f1a9d6dcbd23394bf9eee0a546475c9c2aaaa55b06fede18f283cf0e",
14
+ "api/v2/openapi.yaml": "cee3be85439c6c11bf513b794749d450c3def16320b3c08dc39fe13199e54924",
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": "2e1962fac9303bf8c6e8c74998312548f09383a0a83043743894de360775809c",
203
+ "spec/v1/core-standard-manifest.json": "f55458ee762630fcde2255dba20e22f4d66a3667bd014f8a6d79832c44f5c0ec",
204
204
  "spec/v1/deprecations.json": "1d5acb69a9b8ccb57275a95605f74aef1d920685f8407c9d382a46b59dc803bb",
205
205
  "spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
206
206
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -212,14 +212,14 @@
212
212
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
213
213
  "spec/v1/operation-path-manifest.json": "5f5f4e3842669371730ebbd1aace3fb794192615f0018dc144f484ba5db82ac3",
214
214
  "spec/v1/spec-gaps.json": "6cc9962c6b969f632e07a78b52a4f61447ff579e2990cbae989866f584a86042",
215
- "spec/v2/README.md": "7ae852012806ffaab24a5586e5eac107f0bc7b4932f278a93e4b549fcbba56ee",
215
+ "spec/v2/README.md": "476b71755ecfd17430af5855564cd7976325ff3d56d54aab6e4e6df334994491",
216
216
  "spec/v2/core/capabilities.md": "c5107a4964e487c20bbb5254cfe1f706e0d9ba7e34da9070bc43b50b385e7948",
217
217
  "spec/v2/core/conformance.md": "622084e3c74c4549087f4d861b1bb98cec201a0c5aec23788d27f53a355fde3b",
218
218
  "spec/v2/core/connection-packs.md": "1d759681e2962f5103228c52c45f24607ed4bb9ae73e30605f690c1d5a59e9ee",
219
219
  "spec/v2/core/errors.md": "74fdb5862f432a831cf0c2eadbe0c8cca6b2660ec578180ae33db54773a1df28",
220
220
  "spec/v2/core/events.md": "defe0969d6ec7c980f415cc0d78b9d065782ac7e5638b12a44aa4895a35d6126",
221
221
  "spec/v2/core/form-content-packs.md": "6ffaae85d4550be48dd53fb6e2959af8aceb35bd87c5343b722885084f197713",
222
- "spec/v2/core/headers.md": "9e307d2c6ce8a2f91802a753adfd245fd3d10cdc9135e021e76f461f92b866fb",
222
+ "spec/v2/core/headers.md": "50d2cf642f6dce537e02b75bb8c2f0cea59872327b5b16a3d44d8c0fb0f2c7bc",
223
223
  "spec/v2/core/idempotency.md": "b86c352d964aebad532727f314e26ed2177d49de382076fa64d3162e2f56118d",
224
224
  "spec/v2/core/identity.md": "dd5299f9cdf54640870e1fffae11edb820cf6396ea298c4ebd14c649c4bd4a87",
225
225
  "spec/v2/core/interop.md": "d4c404e469e1d9d9be88bc2c939d777e7d7c1d0419b1251c18770f039838a401",
@@ -270,8 +270,8 @@
270
270
  "spec/v2/path-manifest.json": "a6f4e652e866889e8bde815874062f62929aee95dc8e58e7152c56adee262163",
271
271
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
272
272
  "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
273
- "spec/v2/release.json": "4eea9fc280f05634fe3d5343252ce511267e61a981c30aac3d9491963f06ab18",
273
+ "spec/v2/release.json": "f86b009fdb21db46d7d920bb8a4cb5b2b3c72701b1649ba46a74394fb6461e70",
274
274
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
275
275
  },
276
- "corpusCommit": "1486657cb7efe8d010cfeec54498aa547e97a981"
276
+ "corpusCommit": "b90129a9b868d1609de65693e5bc72b023ddd252"
277
277
  }
@@ -25,6 +25,26 @@
25
25
  * construction (`capabilities.md` §1). A host advertising one major MUST
26
26
  * refuse rather than ignore.
27
27
  *
28
+ * Suite 2.1.2 — the second sentence had never been implemented, and the first
29
+ * was applied to every host. On a host with no `1.x` member the header-less
30
+ * request is served major 2 itself (§1.3 row 3 with §1.1: `preferredVersion`
31
+ * MUST equal the single major served, RFC 0179 §A.1), so asking for `2.0`
32
+ * names THE SAME CONTRACT as asking for nothing and byte identity is the only
33
+ * conformant answer. This leg called that "the header was IGNORED… the host
34
+ * served v1 and called it v2" on a host holding no v1 at all. Every host
35
+ * retires into that state; the only reason it survived is that no host had
36
+ * ever been measured there. Reported by the MyndHyve session from a
37
+ * throwaway v1-retired lane, reproduced here against a stub implementing
38
+ * §1.3's table exactly, 2026-09-11.
39
+ *
40
+ * `inapplicable` would have been the wrong repair. On a retired host
41
+ * `v2-dual-stack-negotiation` gates itself off for want of a second major and
42
+ * takes its unlisted-major probe with it — so §1.3 row 2, *"a major not in
43
+ * `protocolVersions[]` MUST be answered `406 protocol_version_unsupported`
44
+ * with `details.protocolVersions[]`"*, would be measured by nothing at all.
45
+ * The property is falsifiable from that side too, so that is the side this
46
+ * leg probes when the byte comparison has nothing to compare.
47
+ *
28
48
  * @see spec/v2/core/versioning.md §1.3
29
49
  * @see RFCS/0172-v2-versioning-and-release.md §A.3
30
50
  */
@@ -33,6 +53,7 @@ import { describe, it, expect } from 'vitest';
33
53
  import { loadEnv } from '../lib/env.js';
34
54
  import { softSkip } from '../lib/soft-skip.js';
35
55
  import { req } from '../lib/requirement-ids.js';
56
+ import { readErrorCode } from '../lib/error-envelope.js';
36
57
 
37
58
  const ID = 'openwop.requirement.0172.version-header-honored';
38
59
  const DOC = 'spec/v2/core/versioning.md §1.3';
@@ -57,6 +78,19 @@ async function fetchRepresentation(header: string | null): Promise<Fetched | nul
57
78
  }
58
79
  }
59
80
 
81
+ /** `protocolVersions[]` from a discovery body; `null` when it is not a JSON object. */
82
+ function readVersions(body: string): string[] | null {
83
+ let doc: unknown;
84
+ try {
85
+ doc = JSON.parse(body);
86
+ } catch {
87
+ return null;
88
+ }
89
+ if (doc === null || typeof doc !== 'object') return null;
90
+ const v = (doc as { protocolVersions?: unknown }).protocolVersions;
91
+ return Array.isArray(v) ? v.map(String) : [];
92
+ }
93
+
60
94
  describe('v2-version-header-honored (RFC 0172 §A.3)', () => {
61
95
  it('OpenWOP-Version is honored or refused — never ignored', async () => {
62
96
  const bare = await fetchRepresentation(null);
@@ -68,6 +102,46 @@ describe('v2-version-header-honored (RFC 0172 §A.3)', () => {
68
102
  return softSkip('blocked', `the header-less GET ${PATH} answered ${bare.status}; there is no baseline representation to compare against`);
69
103
  }
70
104
 
105
+ const advertised = readVersions(bare.body);
106
+ if (advertised === null) {
107
+ return softSkip('blocked', `${PATH} did not return a JSON object, so protocolVersions[] cannot be read`);
108
+ }
109
+ const majors = new Set(advertised.map((v) => v.split('.')[0]));
110
+
111
+ // No `1.x` member: the header-less request is already served major 2, so
112
+ // the two probes name one contract and their bytes agree by construction.
113
+ // Probe the rule that still discriminates — §1.3 row 2 — using the major
114
+ // this host does not serve. On a retired host that is `1`, which is what
115
+ // its clients will actually send.
116
+ if (!majors.has('1')) {
117
+ const unserved = ['1', '9'].find((m) => !majors.has(m)) ?? '9';
118
+ const refused = await fetchRepresentation(unserved);
119
+ if (refused === null) {
120
+ return softSkip('blocked', `${PATH} unreachable under OpenWOP-Version: ${unserved}`);
121
+ }
122
+ let envelope: unknown;
123
+ try {
124
+ envelope = JSON.parse(refused.body);
125
+ } catch {
126
+ envelope = undefined;
127
+ }
128
+ // `details` sits at the ROOT of the envelope beside `error` and
129
+ // `message` (`schemas/v2/error-envelope.schema.json`), not under `error`.
130
+ const echoed = (envelope as { details?: { protocolVersions?: unknown } } | undefined)?.details?.protocolVersions;
131
+ return expect(
132
+ {
133
+ status: refused.status,
134
+ code: readErrorCode(envelope),
135
+ echoed: Array.isArray(echoed) ? [...echoed].map(String).sort() : echoed,
136
+ },
137
+ req(
138
+ ID,
139
+ DOC,
140
+ `this host advertises [${advertised.join(', ')}] — one major, so a header naming it selects the same contract as no header and their bytes MUST agree. What discriminates honored from ignored here is the refusal: OpenWOP-Version: ${unserved} names a major not in protocolVersions[], which §1.3 MUST answer 406 protocol_version_unsupported with details.protocolVersions[] echoing the list. Answering 200 means the header was ignored — the same defect this scenario exists to catch, probed from the side a retired host still has`,
141
+ ),
142
+ ).toEqual({ status: 406, code: 'protocol_version_unsupported', echoed: [...advertised].sort() });
143
+ }
144
+
71
145
  // A refusal is a correct answer and ends the check: the host has told the
72
146
  // truth about not serving major 2.
73
147
  if (asked.status === 406) {