@openwop/openwop-conformance 2.42.4 → 2.42.6
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 +10 -0
- package/README.md +1 -1
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +7 -2
- package/schemas/CORPUS-STAMP.json +9 -9
- package/src/lib/era2-seed.ts +30 -0
- package/src/scenarios/v2-era-2-append-vocabulary.test.ts +41 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.42.6] — 2026-09-27 — the era-2 writer rule is witnessable again
|
|
4
|
+
|
|
5
|
+
- **`0176.era-2-append-vocabulary` is a real witness again on a host that serves the new seam.** New OPTIONAL seams-profile seam `appendEra2Event` (`POST /conformance/seams/sample/event-log/append`, `api/seams-v2.yaml`; `host-sample-test-seams.md` §27) appends ONE v2-named event to a seeded era-2 run through the host's PRODUCTION writer; the leg appends `agent.reasoning-delta` (a codemap-renamed type, stored v1 `agent.reasoning.delta`) and requires the translated read to return the v2 name. Sabotaged: a host whose writer stores v2 names fails the read `500 event_type_unmapped` → `executed-fail`. Dispositions: seam served → a real assertion (the 2.42.5 `partial-witness-ok` annotation is gone for that path); seam served but contract broken → `blocked`; seam NOT served → the 2.42.5 partial witness, never `blocked` (an optional seam newer than the leg must not take away a certification); seams profile not claimed → `inapplicable`.
|
|
6
|
+
- **Suite `2.42.6`**. `@openwop/spec-artifacts` moves in lockstep at the same exact pin.
|
|
7
|
+
|
|
8
|
+
## [2.42.5] — 2026-09-27 — hotfix: a host can certify again
|
|
9
|
+
|
|
10
|
+
- **`0176.era-2-append-vocabulary` no longer blocks every host.** 2.42.3's unfailable-leg audit made this leg `blockedDespiteAssertions` whenever a host's appended types are spelled the same in v1 and v2. No catalogued seam lets any host make a seeded era-2 run append a type the codemap renames, so the row recorded `blocked` on every host, and one `blocked` row denies the whole bundle (RFC 0168 §E.1). **No host could certify on 2.42.3 or 2.42.4.** The leg now records a partial witness whose detail says the codemap-renaming writer rule is unwitnessed. The append, the read-back and the sequence rules are observed, and the acceptance predicate still refuses a partial-witness row. It is annotated `partial-witness-ok` for the softskip gate. openwop-1f is adding the seam that makes the rule witnessable. Verified on the v2 reference host: the row now records `executed-pass` with that detail.
|
|
11
|
+
- **Suite `2.42.5`**. `@openwop/spec-artifacts` moves in lockstep at the same exact pin.
|
|
12
|
+
|
|
3
13
|
## [2.42.4] — 2026-09-27 — unfailable-leg audit wave 2, an on-the-wire Idempotency-Key witness, and a gate so the class stops growing
|
|
4
14
|
|
|
5
15
|
- **Wave 2 of the unfailable-leg audit.** `scripts/audit-unfailable-legs.mjs --all` widened the scan from Accepted-RFC ids to every leg, and added a sixth shape: *asserts, then soft-skips*. At major 2 an `inapplicable` or `skipped` note after an assertion records `executed-pass` with a `partial-witness:` detail; at major 1 a `blocked` note does too. Certification counts both.
|
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.42.
|
|
14
|
+
npm install --legacy-peer-deps @openwop/openwop-conformance@2.42.6 @openwop/spec-artifacts@2.42.6
|
|
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.42.
|
|
3
|
+
"version": "2.42.6",
|
|
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.42.
|
|
59
|
+
"@openwop/spec-artifacts": "2.42.6"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -29109,7 +29109,7 @@
|
|
|
29109
29109
|
{
|
|
29110
29110
|
"id": "openwop.it.v2-era-2-append-vocabulary.a-host-append-to-an-open-era-2-run-keeps-the-log-in-v1-vocabulary-so-the-whole-l",
|
|
29111
29111
|
"file": "v2-era-2-append-vocabulary.test.ts",
|
|
29112
|
-
"line":
|
|
29112
|
+
"line": 50,
|
|
29113
29113
|
"title": "a host append to an open era-2 run keeps the log in v1 vocabulary, so the whole log still reads",
|
|
29114
29114
|
"explicitId": "openwop.requirement.0176.era-2-append-vocabulary",
|
|
29115
29115
|
"citations": [
|
|
@@ -29128,6 +29128,11 @@
|
|
|
29128
29128
|
"requirement": null,
|
|
29129
29129
|
"interpolated": true
|
|
29130
29130
|
},
|
|
29131
|
+
{
|
|
29132
|
+
"section": "spec/v2/core/persistence.md §The writer rule",
|
|
29133
|
+
"requirement": null,
|
|
29134
|
+
"interpolated": true
|
|
29135
|
+
},
|
|
29131
29136
|
{
|
|
29132
29137
|
"section": "spec/v2/core/persistence.md §The writer rule",
|
|
29133
29138
|
"requirement": null,
|
|
@@ -29138,7 +29143,7 @@
|
|
|
29138
29143
|
{
|
|
29139
29144
|
"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",
|
|
29140
29145
|
"file": "v2-era-2-append-vocabulary.test.ts",
|
|
29141
|
-
"line":
|
|
29146
|
+
"line": 189,
|
|
29142
29147
|
"title": "the run keeps the era it was created with; an append does not promote it to era 3",
|
|
29143
29148
|
"explicitId": "openwop.requirement.0176.era-2-append-vocabulary",
|
|
29144
29149
|
"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.42.
|
|
5
|
-
"corpusTag": "v2.42.
|
|
4
|
+
"version": "2.42.6",
|
|
5
|
+
"corpusTag": "v2.42.6",
|
|
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": "69464bc0e1e71ef3b543a73d4fa4f67202343d2603c80af8033f6c3619ef1b30",
|
|
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": "c188943dbb662ddb1d8ed26759c1daa560f63617fedd2a116b8abcc41c2f42de",
|
|
13
|
+
"api/v2/asyncapi.yaml": "94b9174faeccabea8f2d29fe3e80da7d16037b497ea02f91e61e512ff8ea8d62",
|
|
14
|
+
"api/v2/openapi.yaml": "5f2bcb438b77c250e0732f1808690bb710941a7c7ee5150a27e66537dee9fcb8",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
205
205
|
"spec/v1/alias-detectors.json": "2401fcb1c18cdd688c018b3d716ae6bca85c5e356220ee2b793bd9d81872412d",
|
|
206
206
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
207
|
-
"spec/v1/core-standard-manifest.json": "
|
|
207
|
+
"spec/v1/core-standard-manifest.json": "844a89d3bba9b850d8ae949dc7f0b0ecade689c748263d6c7360e2fe7fb7cf8f",
|
|
208
208
|
"spec/v1/deprecations.json": "2d03f4729810280147ea08c630ea65434f2dc5370567d0aac41c3595f8337d40",
|
|
209
209
|
"spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
|
|
210
210
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -294,9 +294,9 @@
|
|
|
294
294
|
"spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
|
|
295
295
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
296
296
|
"spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
|
|
297
|
-
"spec/v2/release.json": "
|
|
297
|
+
"spec/v2/release.json": "f599686983f47f146a03edec608d63f31525b3e3268036684434afcb972e1cef",
|
|
298
298
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
299
|
-
"spec/v2/surface-baseline.json": "
|
|
299
|
+
"spec/v2/surface-baseline.json": "34404cc65fb70b8a54bbee40d8e0f5047a29e7df4f1abd55741a697549ccaccf"
|
|
300
300
|
},
|
|
301
|
-
"corpusCommit": "
|
|
301
|
+
"corpusCommit": "d578116b9665df11f8d1eaad11fc45f71e826d37"
|
|
302
302
|
}
|
package/src/lib/era2-seed.ts
CHANGED
|
@@ -85,6 +85,36 @@ export function era2Gate(doc: Record<string, unknown> | null): Seeded | null {
|
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export const APPEND_PATH_V1 = '/v1/host/sample/event-log/append';
|
|
89
|
+
export const APPEND_PATH = seamPath(APPEND_PATH_V1);
|
|
90
|
+
|
|
91
|
+
export type Appended =
|
|
92
|
+
| { readonly ok: true; readonly sequence: number }
|
|
93
|
+
| { readonly ok: false; readonly unserved: true; readonly reason: string }
|
|
94
|
+
| { readonly ok: false; readonly unserved: false; readonly kind: 'blocked'; readonly reason: string };
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Append ONE v2-named event to a seeded era-2 run through the host's production
|
|
98
|
+
* writer (host-sample-test-seams.md §27, `appendEra2Event`). The seam is only
|
|
99
|
+
* the trigger: which spelling is STORED is the host writer's decision, and the
|
|
100
|
+
* production read is what the caller asserts on. UNSERVED (404/405/501) is
|
|
101
|
+
* reported separately from a served seam that breaks its contract: §27 is an
|
|
102
|
+
* OPTIONAL seam added after the leg already certified, so its absence falls back
|
|
103
|
+
* to the pre-seam partial witness rather than denying certification.
|
|
104
|
+
*/
|
|
105
|
+
export async function appendEra2Event(runId: string, type: string, payload: Record<string, unknown>): Promise<Appended> {
|
|
106
|
+
const res = await http(() => driver.post(APPEND_PATH_V1, { runId, type, payload }));
|
|
107
|
+
if (res === null) return { ok: false, unserved: false, kind: 'blocked', reason: `${APPEND_PATH} unreachable (fetch failed)` };
|
|
108
|
+
if (res.status === 404 || res.status === 405 || res.status === 501) {
|
|
109
|
+
return { ok: false, unserved: true, reason: `${APPEND_PATH} answered ${res.status} — the host does not serve the optional appendEra2Event seam (host-sample-test-seams.md §27)` };
|
|
110
|
+
}
|
|
111
|
+
const sequence = (res.json as { sequence?: unknown } | undefined)?.sequence;
|
|
112
|
+
if (res.status !== 202 || typeof sequence !== 'number') {
|
|
113
|
+
return { ok: false, unserved: false, kind: 'blocked', reason: `${APPEND_PATH} answered ${res.status} ${readErrorCode(res.json) ?? ''} without a 202 { runId, sequence } — the append contract is not honoured`.trim() };
|
|
114
|
+
}
|
|
115
|
+
return { ok: true, sequence };
|
|
116
|
+
}
|
|
117
|
+
|
|
88
118
|
/** Seed an era-2 log in v1 vocabulary; the reason names the seam when it is absent. */
|
|
89
119
|
export async function seedEra2Log(events: readonly SeedEvent[], status: 'completed' | 'running' | 'paused' = 'completed'): Promise<Seeded> {
|
|
90
120
|
const res = await http(() => driver.post(SEED_PATH_V1, { eventLogSchemaVersion: 2, status, events }));
|
|
@@ -28,13 +28,15 @@
|
|
|
28
28
|
import { describe, it, expect } from 'vitest';
|
|
29
29
|
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
30
30
|
import { v2Discovery } from '../lib/v2.js';
|
|
31
|
-
import {
|
|
31
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
32
32
|
import { req } from '../lib/requirement-ids.js';
|
|
33
33
|
import { readErrorCode } from '../lib/error-envelope.js';
|
|
34
|
-
import { codemapV1toV2, era2Gate, eventsOf, pollEvents, seedEra2Log, v1FixtureLog, type ReadEvent } from '../lib/era2-seed.js';
|
|
34
|
+
import { appendEra2Event, codemapV1toV2, era2Gate, eventsOf, pollEvents, seedEra2Log, v1FixtureLog, type ReadEvent } from '../lib/era2-seed.js';
|
|
35
35
|
|
|
36
36
|
const ID = 'openwop.requirement.0176.era-2-append-vocabulary';
|
|
37
37
|
const DOC = 'spec/v2/core/persistence.md §The writer rule';
|
|
38
|
+
/** A type the codemap renames (v1 `agent.reasoning.delta`); artifact event, no projection effect. */
|
|
39
|
+
const WITNESS_TYPE = 'agent.reasoning-delta';
|
|
38
40
|
|
|
39
41
|
async function http(fn: () => Promise<OpenWOPResponse>): Promise<OpenWOPResponse | null> {
|
|
40
42
|
try {
|
|
@@ -88,20 +90,31 @@ describe('v2-era-2-append-vocabulary (RFC 0176 §A — the writer rule)', () =>
|
|
|
88
90
|
// Neither answer is asserted: the resume transition's event set is not
|
|
89
91
|
// pinned to a renamed type, and a seeded run's pause is host latitude. If
|
|
90
92
|
// no renamed type is appended, the leg records `blocked` below, not a pass.
|
|
93
|
+
// 2.42.6: the writer-rule witness proper. `appendEra2Event` (host-sample-test-
|
|
94
|
+
// seams.md §27) hands the host ONE event of a type the codemap RENAMES
|
|
95
|
+
// (`agent.reasoning-delta`, stored v1 `agent.reasoning.delta`) and the host's
|
|
96
|
+
// production writer chooses the stored spelling; the read below is the witness.
|
|
97
|
+
// It is an artifact event, so it moves no projection. A host that does not
|
|
98
|
+
// serve the seam falls through to the pre-seam path below and ends in the
|
|
99
|
+
// annotated partial witness, never `blocked` (see the next comment); the gate
|
|
100
|
+
// above already made a host without the seams profile `inapplicable`.
|
|
101
|
+
const appended = await appendEra2Event(runId, WITNESS_TYPE, { agentId: 'conformance', delta: 'era-2 writer-rule witness', sequence: 0 });
|
|
102
|
+
// A served seam that breaks its contract is `blocked`. An UNSERVED seam is not:
|
|
103
|
+
// §27 is optional and newer than this leg, so its absence falls through to the
|
|
104
|
+
// pre-seam path and ends in the partial witness below — it never denies a
|
|
105
|
+
// host certification it held before the seam existed.
|
|
106
|
+
if (!appended.ok && !appended.unserved) return softSkip(appended.kind, appended.reason);
|
|
107
|
+
const seamServed = appended.ok;
|
|
108
|
+
|
|
91
109
|
const enc = encodeURIComponent(runId);
|
|
92
110
|
const paused = await http(() => driver.post(`/runs/${enc}:pause`, { reason: 'conformance', drainPolicy: 'immediate' }));
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const cancelOk = cancelled !== null && (cancelled.status === 200 || cancelled.status === 202 || cancelled.status === 204);
|
|
101
|
-
const pauseResumeOk = paused?.status === 202 && resumed?.status === 202;
|
|
102
|
-
if (!cancelOk && !pauseResumeOk) {
|
|
103
|
-
return softSkip('blocked', `POST /runs/{runId}/cancel answered ${cancelled?.status ?? 'no response'} on a seeded era-2 run (pause ${paused?.status ?? 'no response'}, resume ${resumed?.status ?? 'not attempted'}) — no canonical mutation drove the host's writer, so the append is unwitnessed`);
|
|
104
|
-
}
|
|
111
|
+
if (paused?.status === 202) await http(() => driver.post(`/runs/${enc}:resume`, { reason: 'conformance' }));
|
|
112
|
+
|
|
113
|
+
// A canonical mutation as well, so the host's own lifecycle writer appends
|
|
114
|
+
// beside the seam's append. The seam append above already drove the
|
|
115
|
+
// production writer, so a host that refuses cancel on a SEEDED run no longer
|
|
116
|
+
// leaves the rule unwitnessed — the answer is not asserted here.
|
|
117
|
+
await http(() => driver.post(`/runs/${enc}/cancel`, {}));
|
|
105
118
|
|
|
106
119
|
const after = await pollEvents(runId);
|
|
107
120
|
if (after === null) return softSkip('blocked', 'the event read failed after the append');
|
|
@@ -154,12 +167,23 @@ describe('v2-era-2-append-vocabulary (RFC 0176 §A — the writer rule)', () =>
|
|
|
154
167
|
// positive witness: the stored spelling was the v1 one the reader maps
|
|
155
168
|
// from. When every appended type is spelled the same in both vocabularies,
|
|
156
169
|
// the rows above hold for a v1-writing and a v2-writing host alike, so the
|
|
157
|
-
// writer rule is unobserved
|
|
170
|
+
// writer rule is unobserved.
|
|
158
171
|
const renamedV2 = new Set([...codemapV1toV2()].filter(([v1, v2]) => v1 !== v2).map(([, v2]) => v2));
|
|
159
172
|
const appendedTypes = rows.slice(seedCount).map((r) => String(r.type ?? ''));
|
|
160
|
-
if (!appendedTypes.some((t) => renamedV2.has(t))) {
|
|
161
|
-
|
|
173
|
+
if (!seamServed && !appendedTypes.some((t) => renamedV2.has(t))) {
|
|
174
|
+
// partial-witness-ok: the host does not serve the optional appendEra2Event
|
|
175
|
+
// seam (host-sample-test-seams.md §27), and none of its own appends was a
|
|
176
|
+
// codemap-RENAMED type, so the writer rule is unobserved here. The append,
|
|
177
|
+
// the read-back and the sequence rules above ARE observed. This is the
|
|
178
|
+
// 2.42.5 disposition, kept for hosts without the seam; the acceptance
|
|
179
|
+
// predicate refuses a partial-witness row. (A host without the seam whose
|
|
180
|
+
// own pause/resume appended a renamed type falls through: that IS a witness.)
|
|
181
|
+
return softSkip('inapplicable', `partial witness — the codemap-renaming writer rule is UNWITNESSED: ${appended.ok ? '' : appended.reason}; every type the host appended to the era-2 log (${appendedTypes.join(', ') || 'none'}) is spelled identically in v1 and v2 (spec/v2/event-codemap.json)`);
|
|
162
182
|
}
|
|
183
|
+
if (seamServed) expect(
|
|
184
|
+
appendedTypes.includes(WITNESS_TYPE),
|
|
185
|
+
req(ID, DOC, `the event appended through appendEra2Event (${WITNESS_TYPE}, a codemap-RENAMED type) MUST read back under its v2 name through the translated read — it read as ${appendedTypes.join(', ') || 'nothing'}: a host that stored the v2 spelling in an era-2 log makes the reader map it a second time (event_type_unmapped), and one that bypassed the storage boundary leaks the v1 spelling`),
|
|
186
|
+
).toBe(true);
|
|
163
187
|
});
|
|
164
188
|
|
|
165
189
|
it('the run keeps the era it was created with; an append does not promote it to era 3', async () => {
|