@openwop/openwop-conformance 2.0.0-rc.12 → 2.0.0-rc.14
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/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +4 -18
- package/schemas/CORPUS-STAMP.json +7 -7
- package/src/global-setup.ts +11 -0
- package/src/lib/corpus-stamp.ts +24 -2
- package/src/scenarios/v2-advertised-fixtures-exist.test.ts +25 -43
- package/src/scenarios/v2-era-2-append-vocabulary.test.ts +9 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@openwop/spec-artifacts",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
4
|
-
"stampSha256": "
|
|
3
|
+
"version": "2.0.0-rc.14",
|
|
4
|
+
"stampSha256": "d79a223d634538161fa12071ac4ba8ef581b7fa776181aa630ffd7ff28aadeb5"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.14",
|
|
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.0.0-rc.
|
|
59
|
+
"@openwop/spec-artifacts": "2.0.0-rc.14"
|
|
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": 544,
|
|
6
|
-
"tests":
|
|
7
|
-
"withStableId":
|
|
6
|
+
"tests": 2118,
|
|
7
|
+
"withStableId": 2118,
|
|
8
8
|
"interpolatedTitles": 0,
|
|
9
9
|
"explicitIds": 2057
|
|
10
10
|
},
|
|
@@ -25741,24 +25741,10 @@
|
|
|
25741
25741
|
}
|
|
25742
25742
|
]
|
|
25743
25743
|
},
|
|
25744
|
-
{
|
|
25745
|
-
"id": "openwop.it.v2-advertised-fixtures-exist.every-advertised-fixture-id-is-one-the-corpus-defines",
|
|
25746
|
-
"file": "v2-advertised-fixtures-exist.test.ts",
|
|
25747
|
-
"line": 60,
|
|
25748
|
-
"title": "every advertised fixture id is one the corpus defines",
|
|
25749
|
-
"explicitId": null,
|
|
25750
|
-
"citations": [
|
|
25751
|
-
{
|
|
25752
|
-
"section": null,
|
|
25753
|
-
"requirement": null,
|
|
25754
|
-
"interpolated": true
|
|
25755
|
-
}
|
|
25756
|
-
]
|
|
25757
|
-
},
|
|
25758
25744
|
{
|
|
25759
25745
|
"id": "openwop.it.v2-advertised-fixtures-exist.a-sampled-advertised-fixture-is-actually-creatable-not-just-listed",
|
|
25760
25746
|
"file": "v2-advertised-fixtures-exist.test.ts",
|
|
25761
|
-
"line":
|
|
25747
|
+
"line": 62,
|
|
25762
25748
|
"title": "a sampled advertised fixture is actually creatable, not just listed",
|
|
25763
25749
|
"explicitId": null,
|
|
25764
25750
|
"citations": [
|
|
@@ -26561,7 +26547,7 @@
|
|
|
26561
26547
|
{
|
|
26562
26548
|
"id": "openwop.it.v2-era-2-append-vocabulary.the-run-keeps-the-era-it-was-created-with-an-append-does-not-promote-it-to-era-3",
|
|
26563
26549
|
"file": "v2-era-2-append-vocabulary.test.ts",
|
|
26564
|
-
"line":
|
|
26550
|
+
"line": 128,
|
|
26565
26551
|
"title": "the run keeps the era it was created with; an append does not promote it to era 3",
|
|
26566
26552
|
"explicitId": null,
|
|
26567
26553
|
"citations": [
|
|
@@ -1,7 +1,7 @@
|
|
|
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.0.0-rc.
|
|
4
|
+
"version": "2.0.0-rc.14",
|
|
5
5
|
"corpusTag": null,
|
|
6
6
|
"files": {
|
|
7
7
|
"api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
|
|
@@ -9,9 +9,9 @@
|
|
|
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": "5c9741ace7a24e7cb9475cec28585b6888809181fe19beffe1d1866a2882531e",
|
|
13
|
+
"api/v2/asyncapi.yaml": "de67cb65409e07291264d3059647a9b558c5b72186c1b6d49b0e1e292c1b048c",
|
|
14
|
+
"api/v2/openapi.yaml": "76c003262b413b7226948c7fddb8a5457fafed95f332213660ecbaf6b15a3bbf",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
199
199
|
"spec/v1/alias-detectors.json": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
|
|
200
200
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
201
|
-
"spec/v1/core-standard-manifest.json": "
|
|
201
|
+
"spec/v1/core-standard-manifest.json": "2e4f6c617a7166181c67851b9d7c336bb06f2b9a5e805dae5d3315f06102db00",
|
|
202
202
|
"spec/v1/deprecations.json": "520d79927a8ed43ccf62c207a7cf6cf7b19b16e00bbbbdf0410667e939c19d85",
|
|
203
203
|
"spec/v1/deprecations.schema.json": "4cdbbb8eb9c0092913a5704c943880f0c8581bcf0f640a7df5a84105d53c8aee",
|
|
204
204
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
"spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
|
|
229
229
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
230
230
|
"spec/v2/profiles.json": "1f0ee40491131cf561ef19a0394c47ba476b4567514fe16befb6cada4a10a5a4",
|
|
231
|
-
"spec/v2/release.json": "
|
|
231
|
+
"spec/v2/release.json": "71cf76db2a446b9d8363c3211dd4d46b7dc1e2ed8199c101c3bbd09b7579a915"
|
|
232
232
|
},
|
|
233
|
-
"corpusCommit": "
|
|
233
|
+
"corpusCommit": "b52f0c2d11e77e728463f1d397e57bf640ece6e4"
|
|
234
234
|
}
|
package/src/global-setup.ts
CHANGED
|
@@ -75,6 +75,17 @@ export function setup(): void {
|
|
|
75
75
|
// Suite 2.0.0: in the published layout the contract is the spec-artifacts peer (RFC 0168 §D.2).
|
|
76
76
|
const stamp = LAYOUT === 'published' ? verifyPeerContract(PKG_ROOT_PATH) : verifyCorpusStamp(PKG_ROOT_PATH, LAYOUT);
|
|
77
77
|
process.stderr.write(`${describeVerdict(stamp)}\n`);
|
|
78
|
+
// A version skew and a digest mismatch both have to refuse, but they are
|
|
79
|
+
// different faults with different fixes, and reporting them in the same words
|
|
80
|
+
// sends the reader to debug a corrupt install when nothing is corrupt.
|
|
81
|
+
if (stamp.kind === 'peer-version') {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`openwop-conformance: refusing to run — this suite was packed against @openwop/spec-artifacts@${stamp.lockVersion}, ` +
|
|
84
|
+
`but @openwop/spec-artifacts@${stamp.peerVersion} is installed. They are declared EXACT peers. ` +
|
|
85
|
+
`Install both at the same explicit version; do NOT install at a dist-tag such as \`next\`, which moves per package ` +
|
|
86
|
+
`and can name a pair that was never published together.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
78
89
|
if (stamp.kind === 'mismatch') {
|
|
79
90
|
throw new Error('openwop-conformance: refusing to run — schemas/CORPUS-STAMP.json digests do not match the vendored api/ + schemas/ files. Reinstall the package; do not hand-patch vendored contract files.');
|
|
80
91
|
}
|
package/src/lib/corpus-stamp.ts
CHANGED
|
@@ -39,7 +39,17 @@ export interface CorpusStamp {
|
|
|
39
39
|
export type StampVerdict =
|
|
40
40
|
| { readonly kind: 'verified'; readonly files: number }
|
|
41
41
|
| { readonly kind: 'not-applicable'; readonly reason: string }
|
|
42
|
-
| { readonly kind: 'mismatch'; readonly missing: readonly string[]; readonly altered: readonly string[]; readonly extra: readonly string[] }
|
|
42
|
+
| { readonly kind: 'mismatch'; readonly missing: readonly string[]; readonly altered: readonly string[]; readonly extra: readonly string[] }
|
|
43
|
+
/**
|
|
44
|
+
* The peer is INSTALLED and INTACT but is a different version than the suite
|
|
45
|
+
* was packed against. Its own kind because the remedy is completely different
|
|
46
|
+
* from a digest mismatch — nothing is corrupt, two versions are simply out of
|
|
47
|
+
* step — and because the generic message sends readers to debug a broken
|
|
48
|
+
* install. Reported by a tier-2 host that hit it through the `next` dist-tag:
|
|
49
|
+
* the tag moves per package, so `@next` can name an exact-peer PAIR that was
|
|
50
|
+
* never published together.
|
|
51
|
+
*/
|
|
52
|
+
| { readonly kind: 'peer-version'; readonly peerVersion: string; readonly lockVersion: string };
|
|
43
53
|
|
|
44
54
|
export const STAMP_RELATIVE_PATH = join('schemas', 'CORPUS-STAMP.json');
|
|
45
55
|
|
|
@@ -94,7 +104,11 @@ export function verifyPeerContract(pkgRoot: string): StampVerdict {
|
|
|
94
104
|
if (!existsSync(stampPath)) return { kind: 'mismatch', missing: ['@openwop/spec-artifacts/CORPUS-STAMP.json'], altered: [], extra: [] };
|
|
95
105
|
const stamp = JSON.parse(readFileSync(stampPath, 'utf8')) as { package: string; version: string; files: Record<string, string> };
|
|
96
106
|
const digest = createHash('sha256').update(JSON.stringify({ package: stamp.package, version: stamp.version, files: stamp.files })).digest('hex');
|
|
97
|
-
|
|
107
|
+
// A plain version difference is NOT corruption; report it as itself so the
|
|
108
|
+
// message names the two versions and the fix, instead of sending the reader
|
|
109
|
+
// to hunt a damaged install.
|
|
110
|
+
if (stamp.version !== lock.version) return { kind: 'peer-version', peerVersion: stamp.version, lockVersion: lock.version };
|
|
111
|
+
if (digest !== lock.stampSha256) return { kind: 'mismatch', missing: [], altered: [`@openwop/spec-artifacts ${stamp.version} stamp digest ${digest.slice(0, 12)} ≠ the suite's lock ${lock.stampSha256.slice(0, 12)} — same version, different contents`], extra: [] };
|
|
98
112
|
const missing: string[] = []; const altered: string[] = [];
|
|
99
113
|
for (const [rel, d] of Object.entries(stamp.files)) { const p = join(peerRoot, ...rel.split('/')); if (!existsSync(p)) missing.push(rel); else if (sha256File(p) !== d) altered.push(rel); }
|
|
100
114
|
if (missing.length || altered.length) return { kind: 'mismatch', missing, altered, extra: [] };
|
|
@@ -143,6 +157,14 @@ export function describeVerdict(v: StampVerdict): string {
|
|
|
143
157
|
return `[openwop-conformance] corpus stamp VERIFIED — ${v.files} vendored api/ + schemas/ files match their SHA-256 digests`;
|
|
144
158
|
case 'not-applicable':
|
|
145
159
|
return `[openwop-conformance] corpus stamp not checked — ${v.reason}`;
|
|
160
|
+
case 'peer-version':
|
|
161
|
+
return (
|
|
162
|
+
`[openwop-conformance] peer version MISMATCH — this suite was packed against ` +
|
|
163
|
+
`@openwop/spec-artifacts@${v.lockVersion} but @openwop/spec-artifacts@${v.peerVersion} is installed. ` +
|
|
164
|
+
`Nothing is corrupt: the two are declared EXACT peers and are simply out of step. ` +
|
|
165
|
+
`Install both at the same explicit version — never at a dist-tag such as \`next\`, which moves per package ` +
|
|
166
|
+
`and can therefore name a pair that was never published together.`
|
|
167
|
+
);
|
|
146
168
|
case 'mismatch':
|
|
147
169
|
return (
|
|
148
170
|
`[openwop-conformance] corpus stamp MISMATCH — the vendored contract is not the one this suite shipped ` +
|
|
@@ -3,40 +3,42 @@
|
|
|
3
3
|
* the host must be able to honour (suite 2.0.0, target major 2; unaided).
|
|
4
4
|
*
|
|
5
5
|
* `fixtures[]` in discovery gates scenarios: `isFixtureAdvertised(id)` decides
|
|
6
|
-
* whether a scenario runs at all.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* scenario's requirement rather than to the advertisement that was wrong.
|
|
6
|
+
* whether a scenario runs at all. So a host whose advertised list and seeded set
|
|
7
|
+
* drift apart fails somewhere else entirely — the scenario gated on the missing
|
|
8
|
+
* fixture attempts, fails on a run that cannot be created, and the failure is
|
|
9
|
+
* attributed to that scenario's requirement rather than to the advertisement
|
|
10
|
+
* that was wrong. That misattribution is what this scenario exists to catch.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
12
|
+
* **This scenario shipped with a second leg that was wrong, and the correction
|
|
13
|
+
* matters more than the check.** That leg asserted the advertised ids are a
|
|
14
|
+
* SUBSET of `conformance/fixtures/` — "the vocabulary is closed, so an id the
|
|
15
|
+
* corpus does not define is a typo or an invention". The vocabulary is not
|
|
16
|
+
* closed. Host-supplied fixtures are the normal case: dozens of ids the
|
|
17
|
+
* scenarios gate on are deliberately not shipped, and `v2-approver-enforced`
|
|
18
|
+
* says so in its own docstring — it needs an approval fixture whose
|
|
19
|
+
* `approversList` names a principal the suite is not, and records `blocked`
|
|
20
|
+
* naming it precisely because "no such fixture ships in `conformance/fixtures/`".
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* So the leg failed a host for doing exactly what the corpus asks. It was found
|
|
23
|
+
* by running the suite against the reference host, which advertised two
|
|
24
|
+
* host-supplied fixtures and was marked non-conformant for it. Set membership
|
|
25
|
+
* cannot distinguish a typo from a legitimate host fixture, and a check that
|
|
26
|
+
* cannot tell those apart is not a check — it is a coin flip that happens to
|
|
27
|
+
* land on "fail" for correct hosts.
|
|
28
|
+
*
|
|
29
|
+
* What survives is the leg that was always sound: an advertised fixture MUST be
|
|
30
|
+
* creatable. That holds whoever defines it, and it is the one that catches the
|
|
31
|
+
* drift the misattribution comes from.
|
|
28
32
|
*
|
|
29
33
|
* @see spec/v2/core/conformance.md
|
|
30
|
-
* @see conformance/
|
|
34
|
+
* @see conformance/src/scenarios/v2-approver-enforced.test.ts (a host-supplied fixture, by design)
|
|
31
35
|
*/
|
|
32
36
|
|
|
33
37
|
import { describe, it, expect } from 'vitest';
|
|
34
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
35
38
|
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
36
39
|
import { v2Discovery } from '../lib/v2.js';
|
|
37
40
|
import { softSkip } from '../lib/soft-skip.js';
|
|
38
41
|
import { req } from '../lib/requirement-ids.js';
|
|
39
|
-
import { FIXTURES_DIR } from '../lib/paths.js';
|
|
40
42
|
|
|
41
43
|
const ID = 'openwop.requirement.0168.advertised-fixtures-exist';
|
|
42
44
|
const DOC = 'spec/v2/core/conformance.md §Fixtures';
|
|
@@ -57,26 +59,6 @@ function advertisedIds(doc: Record<string, unknown>): string[] {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
describe('v2-advertised-fixtures-exist (conformance.md §Fixtures)', () => {
|
|
60
|
-
it('every advertised fixture id is one the corpus defines', async () => {
|
|
61
|
-
const doc = await v2Discovery().catch(() => null);
|
|
62
|
-
if (!doc) return softSkip('blocked', 'v2 discovery unreachable');
|
|
63
|
-
const ids = advertisedIds(doc);
|
|
64
|
-
if (ids.length === 0) return softSkip('inapplicable', 'the host advertises no fixtures[] — there is no claim to falsify');
|
|
65
|
-
if (FIXTURES_DIR === null || !existsSync(FIXTURES_DIR)) {
|
|
66
|
-
return softSkip('blocked', 'the fixture catalog is absent from this layout, so an advertised id cannot be checked against it');
|
|
67
|
-
}
|
|
68
|
-
const catalog = new Set(
|
|
69
|
-
readdirSync(FIXTURES_DIR)
|
|
70
|
-
.filter((f) => f.endsWith('.json'))
|
|
71
|
-
.map((f) => f.replace(/\.json$/, '')),
|
|
72
|
-
);
|
|
73
|
-
const unknown = ids.filter((id) => !catalog.has(id));
|
|
74
|
-
expect(
|
|
75
|
-
unknown,
|
|
76
|
-
req(ID, DOC, `every id in fixtures[] MUST name a fixture the corpus defines — the vocabulary is closed, so an id the catalog does not carry is a typo or an invention rather than a capability (${unknown.length} unknown of ${ids.length}: ${unknown.slice(0, 5).join(', ')})`),
|
|
77
|
-
).toEqual([]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
62
|
it('a sampled advertised fixture is actually creatable, not just listed', async () => {
|
|
81
63
|
const doc = await v2Discovery().catch(() => null);
|
|
82
64
|
if (!doc) return softSkip('blocked', 'v2 discovery unreachable');
|
|
@@ -61,6 +61,15 @@ describe('v2-era-2-append-vocabulary (RFC 0176 §A — the writer rule)', () =>
|
|
|
61
61
|
return softSkip('blocked', `GET /runs/${runId}/events/poll answered ${before?.status ?? 'no response'} on the seeded era-2 run — the log cannot be read back`);
|
|
62
62
|
}
|
|
63
63
|
const seedCount = eventsOf(before).length;
|
|
64
|
+
// The seam returning ok is a WRAPPER claim; the readable log is the artifact.
|
|
65
|
+
// A seam that reports success and seeds nothing leaves no era-2 log to append
|
|
66
|
+
// to, so there is nothing here to witness the writer rule with — that is
|
|
67
|
+
// `blocked`, not a writer-rule failure. Asserting against an empty log would
|
|
68
|
+
// charge this requirement for a seam defect, which is the misattribution the
|
|
69
|
+
// suite exists to avoid.
|
|
70
|
+
if (seedCount === 0) {
|
|
71
|
+
return softSkip('blocked', `seedEra2Log reported success but the log reads back empty (0 events) — the seam's return value is not evidence that a log exists, and without a seeded era-2 log the writer rule is unwitnessed here`);
|
|
72
|
+
}
|
|
64
73
|
|
|
65
74
|
// One canonical mutation so the HOST's own writer appends. Cancel is the
|
|
66
75
|
// universally available terminal transition; a host that refuses it on a
|