@openwop/openwop-conformance 2.46.34 → 2.46.36

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +3 -3
  3. package/coverage.md +4 -5
  4. package/dist/spec-artifacts.lock.json +2 -2
  5. package/fixtures/conformance-interrupt-form-refused.json +108 -0
  6. package/fixtures/conformance-interrupt-form-unbound.json +108 -0
  7. package/fixtures/conformance-interrupt-form.json +107 -0
  8. package/fixtures/conformance-stream-text.json +1 -2
  9. package/fixtures.md +44 -4
  10. package/package.json +2 -2
  11. package/requirement-aliases.json +98 -1
  12. package/requirements.json +663 -804
  13. package/scenario-majors.json +16 -19
  14. package/schemas/CORPUS-STAMP.json +72 -85
  15. package/src/lib/callai-attempts-witness.ts +13 -1
  16. package/src/lib/deployment-lifecycle-witness.ts +308 -0
  17. package/src/lib/family-advert-witness.ts +37 -50
  18. package/src/scenarios/stream-text-fixture.test.ts +13 -5
  19. package/src/scenarios/v2-deployment-lifecycle.test.ts +526 -0
  20. package/src/scenarios/v2-envelope-surfaces-absent.test.ts +55 -0
  21. package/src/scenarios/v2-interrupt-form-rules.test.ts +143 -0
  22. package/src/scenarios/v2-limits-clarification-rounds-advertisement.test.ts +42 -0
  23. package/src/scenarios/v2-peer-dependency-declared.test.ts +1 -1
  24. package/src/scenarios/v2-provider-output-reliability.test.ts +94 -0
  25. package/src/scenarios/v2-structured-output-advertisement.test.ts +82 -0
  26. package/src/lib/envelope-schema-static.ts +0 -167
  27. package/src/scenarios/v2-a2ui-v09-surface.test.ts +0 -323
  28. package/src/scenarios/v2-ai-envelope-advertisement.test.ts +0 -43
  29. package/src/scenarios/v2-envelope-contracts-advertisement.test.ts +0 -48
  30. package/src/scenarios/v2-envelope-reasoning-shape.test.ts +0 -81
  31. package/src/scenarios/v2-envelope-tier-one-subset-static.test.ts +0 -64
  32. package/src/scenarios/v2-envelopes-advertisement.test.ts +0 -85
  33. package/src/scenarios/v2-limits-envelope-caps-advertisement.test.ts +0 -61
@@ -1,323 +0,0 @@
1
- /**
2
- * v2-a2ui-v09-surface — RFC 0209 §A.1, §A.2, §C.9, §C.11, §C.12, the seam-gated
3
- * legs (suite 2.36.0, target major 2).
4
- *
5
- * `ui.a2ui-surface` at per-kind schema version 2 carries A2UI v0.9 messages. The
6
- * suite cannot make a model emit a chosen envelope, so every leg here drives the
7
- * v2 emit-surface seam (`POST /conformance/seams/sample/a2ui/emit-surface`,
8
- * api/seams-v2.yaml), which a host MUST route through its production envelope
9
- * admission. The server-free legs (profile ⊂ upstream, the excluded affordances,
10
- * the pinned catalog, the kind carve-out) are corpus rows in
11
- * `src/coherence/a2ui-v09-profile.test.ts`.
12
- *
13
- * Gates, all decided before any assertion so a skipped leg never records a
14
- * partial witness: the seams profile advertised; `schemaVersions.kinds` giving
15
- * the kind a floor of at least 2 (version-2 legs) or exactly 1 (the legacy leg);
16
- * the seam answering something other than 404/405. Any of these absent ⇒
17
- * `inapplicable` — a leg never passes on a non-answer.
18
- *
19
- * Every refusal leg carries an admitted control in the same run, so a host that
20
- * refuses everything fails as surely as one that refuses nothing.
21
- *
22
- * The last describe accounts for §C.9's `render-needs-root`, whose
23
- * Falsifiability verdict is `unwitnessable`: the guarantee is render-side, and
24
- * a server-oriented suite cannot observe a renderer. It records `inapplicable`
25
- * on EVERY host, before any request and before the seam gate, and names where
26
- * the real witness lives: a reference-app client probe, `tier: reference-impl`,
27
- * following the `a2ui-surface-no-code-exec` precedent. It never asserts and
28
- * cannot become a pass.
29
- *
30
- * @see RFCS/0209-v2-a2ui-surfaces-are-a2ui-v0-9.md
31
- * @see spec/v2/ext/a2uiSurface/README.md
32
- * @see spec/v2/core/events.md §"The envelope-kind catalog"
33
- */
34
-
35
- import { describe, it, expect } from 'vitest';
36
- import { readFileSync } from 'node:fs';
37
- import { join } from 'node:path';
38
- import { driver, type OpenWOPResponse } from '../lib/driver.js';
39
- import { v2Discovery } from '../lib/v2.js';
40
- import { seamsProfileAdvertised, SEAMS_PREFIX } from '../lib/seams.js';
41
- import { readErrorCode } from '../lib/error-envelope.js';
42
- import { FIXTURES_DIR } from '../lib/paths.js';
43
- import { softSkip } from '../lib/soft-skip.js';
44
- import { req } from '../lib/requirement-ids.js';
45
- import { carriesPayload } from '../lib/carries-payload.js';
46
-
47
- type Json = Record<string, unknown>;
48
- const KIND = 'ui.a2ui-surface';
49
- const SEAM = `${SEAMS_PREFIX}/sample/a2ui/emit-surface`;
50
- const FIXTURE = 'conformance-approval';
51
- const GATE_NODE = 'gate';
52
- const CATALOG = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';
53
- const clone = <T>(o: T): T => JSON.parse(JSON.stringify(o)) as T;
54
- const POSITIVE = JSON.parse(readFileSync(join(FIXTURES_DIR, 'a2ui-v09', 'positive-approve-brief.json'), 'utf8')) as Json;
55
- const V1_SURFACE: Json = { catalogVersion: '0.9.1', surface: { title: 'Kickoff', components: [{ component: 'heading', text: 'Kickoff', level: 2 }, { component: 'action.button', id: 'go', label: 'Go', action: { target: 'resume' } }] } };
56
- const V1_LATER: Json = { catalogVersion: '0.9.1', surface: { title: 'Kickoff, later', components: [{ component: 'heading', text: 'Kickoff, later', level: 2 }] } };
57
-
58
- let seq = 0;
59
- const nonce = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
60
- function envelope(schemaVersion: number, payload: unknown, opts: { trust?: 'trusted' | 'untrusted'; nodeId?: string } = {}): Json {
61
- seq += 1;
62
- const meta: Json = { source: 'ai-generation', ts: new Date().toISOString() };
63
- if (opts.trust) meta['contentTrust'] = opts.trust;
64
- return { type: KIND, schemaVersion, envelopeId: `env-0209-${nonce}-${seq}`, correlationId: `corr-0209-${nonce}-${seq}`, ...(opts.nodeId ? { nodeId: opts.nodeId } : {}), payload, meta };
65
- }
66
- /** A version-2 payload for `surfaceId` carrying `messages` (surfaceId stamped into each). */
67
- function v2(surfaceId: string, messages: Json[]): Json {
68
- return { version: 'v0.9', catalogId: CATALOG, surfaceId, messages: messages.map((m) => { const c = clone(m); const body = Object.entries(c).find(([k]) => k !== 'version')?.[1] as Json; body['surfaceId'] = surfaceId; return c; }) };
69
- }
70
- const createMsg = (): Json => ({ version: 'v0.9', createSurface: { surfaceId: '', catalogId: CATALOG } });
71
- const componentsMsg = (): Json => clone((POSITIVE['messages'] as Json[])[1]);
72
- const dataMsg = (): Json => ({ version: 'v0.9', updateDataModel: { surfaceId: '', value: { name: 'x' } } });
73
- const deleteMsg = (): Json => ({ version: 'v0.9', deleteSurface: { surfaceId: '' } });
74
- const sid = (tag: string): string => `s-${tag}-${nonce}`;
75
-
76
- async function emit(runId: string, env: Json): Promise<OpenWOPResponse> {
77
- return driver.post(SEAM, { runId, envelope: env });
78
- }
79
- const code = (r: OpenWOPResponse): string => `${r.status} ${readErrorCode(r.json) ?? ''}`.trim();
80
-
81
- async function waitStatus(runId: string, wanted: ReadonlySet<string>, timeoutMs: number): Promise<string | null> {
82
- const deadline = Date.now() + timeoutMs;
83
- for (;;) {
84
- const res = await driver.get(`/runs/${encodeURIComponent(runId)}`);
85
- const status = res.status === 200 ? String((res.json as { status?: unknown } | null)?.status ?? '') : null;
86
- if (status !== null && wanted.has(status)) return status;
87
- if (Date.now() > deadline) return status;
88
- await new Promise((r) => setTimeout(r, 250));
89
- }
90
- }
91
-
92
- type Ctx = { readonly floor: number; readonly strict: boolean; readonly runId: string };
93
- type Gate = { ok: true; ctx: Ctx } | { ok: false; kind: 'inapplicable' | 'blocked'; reason: string };
94
-
95
- /** Everything a leg needs, decided before its first assertion. `want` is the floor predicate. */
96
- async function gate(want: (floor: number) => boolean, wantText: string): Promise<Gate> {
97
- const doc = await v2Discovery();
98
- if (!doc) return { ok: false, kind: 'blocked', reason: 'v2 discovery unreachable' };
99
- if (!seamsProfileAdvertised(doc)) return { ok: false, kind: 'inapplicable', reason: 'host does not advertise conformance.seamsProfile: openwop-conformance-seams-v2 — the emit-surface seam is the only way the suite can choose an envelope (RFC 0209 Falsifiability)' };
100
- const kinds = ((doc['schemaVersions'] as Json | undefined)?.['kinds'] ?? {}) as Json;
101
- const floor = typeof kinds[KIND] === 'number' ? (kinds[KIND] as number) : 0;
102
- if (!want(floor)) return { ok: false, kind: 'inapplicable', reason: `schemaVersions.kinds["${KIND}"] is ${floor}; this leg needs ${wantText}` };
103
- const strict = ((doc['envelopeStrictness'] as Json | undefined)?.['mode']) === 'strict';
104
- const fixtures = Array.isArray(doc['fixtures']) ? (doc['fixtures'] as unknown[]) : [];
105
- if (!fixtures.includes(FIXTURE)) return { ok: false, kind: 'inapplicable', reason: `the host does not advertise the ${FIXTURE} fixture the legs run inside` };
106
- const create = await driver.post('/runs', { workflowId: FIXTURE });
107
- if (create.status !== 201) return { ok: false, kind: 'blocked', reason: `POST /runs {workflowId: ${FIXTURE}} answered ${code(create)}` };
108
- const runId = (create.json as { runId: string }).runId;
109
- const status = await waitStatus(runId, new Set(['waiting-approval', 'completed', 'failed', 'cancelled']), 10_000);
110
- if (status !== 'waiting-approval') return { ok: false, kind: 'blocked', reason: `${FIXTURE} did not suspend (status ${status})` };
111
- // Probe the seam with a payload every host that has it must refuse (no createSurface first), so the probe records nothing.
112
- const probe = await emit(runId, envelope(floor, floor >= 2 ? v2(sid('probe'), [dataMsg()]) : { catalogVersion: '9.9.9', surface: { components: [] } }));
113
- if (probe.status === 404 || probe.status === 405) {
114
- await driver.post(`/runs/${encodeURIComponent(runId)}/cancel`, {});
115
- return { ok: false, kind: 'inapplicable', reason: `${SEAM} answered ${probe.status} — the emit-surface seam is not wired` };
116
- }
117
- return { ok: true, ctx: { floor, strict, runId } };
118
- }
119
- async function done(runId: string): Promise<void> { await driver.post(`/runs/${encodeURIComponent(runId)}/cancel`, {}); }
120
-
121
- // RFC 0209 §C.11 compares as JSON, not as text: member order carries no meaning
122
- // (RFC 8259 §4), and a JSONB host re-sorts it. See lib/carries-payload.ts.
123
- const carries = carriesPayload;
124
- async function eventsOf(runId: string): Promise<unknown[]> {
125
- const r = await driver.get(`/runs/${encodeURIComponent(runId)}/events/poll?timeout=1`);
126
- const ev = (r.json as { events?: unknown } | null)?.events;
127
- return Array.isArray(ev) ? ev : [];
128
- }
129
-
130
- describe('RFC 0209 §A.1 — the schema version selects the branch (seam-gated)', () => {
131
- it('a version-2 envelope carrying a version-1 body is refused; the positive is admitted; above the floor is refused; below it follows envelopeStrictness', async () => {
132
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
133
- if (!g.ok) return softSkip(g.kind, g.reason);
134
- const { runId, floor, strict } = g.ctx;
135
- try {
136
- const good = await emit(runId, envelope(2, v2(sid('vsb-good'), (POSITIVE['messages'] as Json[]))));
137
- expect(good.status, req('openwop.requirement.0209.version-selects-branch', 'RFC 0209 §A.1', `the positive version-2 surface MUST be admitted (201), got ${code(good)}`)).toBe(201);
138
- expect(typeof (good.json as { sequence?: unknown } | null)?.sequence, req('openwop.requirement.0209.version-selects-branch', 'api/seams-v2.yaml emitA2uiSurface', 'a 201 names the recording event sequence')).toBe('number');
139
- const mixed = await emit(runId, envelope(2, V1_SURFACE));
140
- expect(mixed.status, req('openwop.requirement.0209.version-selects-branch', 'RFC 0209 §A.1', `a schemaVersion 2 envelope with a payloadV1 body MUST be refused — validating against the anyOf union would admit it (got ${code(mixed)})`)).toBe(422);
141
- expect(readErrorCode(mixed.json), req('openwop.requirement.0209.version-selects-branch', 'spec/v2/ext/a2uiSurface/README.md §Versions', 'the refusal is envelope_invalid')).toBe('envelope_invalid');
142
- const above = await emit(runId, envelope(floor + 1, v2(sid('vsb-above'), [createMsg()])));
143
- expect(code(above), req('openwop.requirement.0209.version-selects-branch', 'events.md §"The envelope-kind catalog"', 'a schemaVersion above the floor MUST be refused with unknown_schema_version whatever the strictness')).toBe('422 unknown_schema_version');
144
- const below = await emit(runId, envelope(1, v2(sid('vsb-below'), [createMsg()])));
145
- if (strict) {
146
- expect(code(below), req('openwop.requirement.0209.version-selects-branch', 'events.md §"The envelope-kind catalog"', 'under strict, a schemaVersion 1 envelope below floor 2 MUST be refused with unknown_schema_version')).toBe('422 unknown_schema_version');
147
- } else {
148
- expect(below.status, req('openwop.requirement.0209.version-selects-branch', 'events.md §"The envelope-kind catalog"', `under warn, a schemaVersion 1 envelope is validated against the advertised version 2; a payloadV2 body is therefore admitted with a drift log (got ${code(below)})`)).toBe(201);
149
- const belowV1 = await emit(runId, envelope(1, V1_SURFACE));
150
- expect(code(belowV1), req('openwop.requirement.0209.version-selects-branch', 'RFC 0209 §A.1', 'under warn, a schemaVersion 1 envelope with a payloadV1 body is validated against version 2 and MUST be refused')).toBe('422 envelope_invalid');
151
- }
152
- } finally { await done(runId); }
153
- });
154
- });
155
-
156
- describe('RFC 0209 §C.9 — the fold is guarded at record time (seam-gated)', () => {
157
- it('no first createSurface, a message after deleteSurface, and a second createSurface are each refused; the valid steps are admitted', async () => {
158
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
159
- if (!g.ok) return softSkip(g.kind, g.reason);
160
- const { runId } = g.ctx;
161
- const R = (why: string) => req('openwop.requirement.0209.fold-guarded', 'RFC 0209 §C.9', why);
162
- try {
163
- const orphan = await emit(runId, envelope(2, v2(sid('fold-a'), [componentsMsg()])));
164
- expect(code(orphan), R('the first envelope for a surfaceId MUST begin with createSurface — updateComponents alone is refused, not recorded')).toBe('422 envelope_invalid');
165
- const bCreate = await emit(runId, envelope(2, v2(sid('fold-b'), [createMsg(), componentsMsg()])));
166
- expect(bCreate.status, R(`createSurface + updateComponents for a new surfaceId MUST be admitted (control), got ${code(bCreate)}`)).toBe(201);
167
- const bDelete = await emit(runId, envelope(2, v2(sid('fold-b'), [deleteMsg()])));
168
- expect(bDelete.status, R(`deleteSurface on a live surface MUST be admitted (control), got ${code(bDelete)}`)).toBe(201);
169
- const bAfter = await emit(runId, envelope(2, v2(sid('fold-b'), [dataMsg()])));
170
- expect(code(bAfter), R('a message after deleteSurface without a new createSurface MUST be refused')).toBe('422 envelope_invalid');
171
- const cCreate = await emit(runId, envelope(2, v2(sid('fold-c'), [createMsg()])));
172
- expect(cCreate.status, R(`createSurface for a new surfaceId MUST be admitted (control), got ${code(cCreate)}`)).toBe(201);
173
- const cAgain = await emit(runId, envelope(2, v2(sid('fold-c'), [createMsg()])));
174
- expect(code(cAgain), R('createSurface for a live surfaceId MUST be refused')).toBe('422 envelope_invalid');
175
- const bRecreate = await emit(runId, envelope(2, v2(sid('fold-b'), [createMsg(), dataMsg()])));
176
- expect(bRecreate.status, R(`a new createSurface after deleteSurface MUST be admitted (control), got ${code(bRecreate)}`)).toBe(201);
177
- } finally { await done(runId); }
178
- });
179
- });
180
-
181
- describe('RFC 0209 §A.2 — the cross-field rules are checked at admission (seam-gated)', () => {
182
- it('a createSurface.catalogId differing from the payload catalogId is refused', async () => {
183
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
184
- if (!g.ok) return softSkip(g.kind, g.reason);
185
- const { runId } = g.ctx;
186
- try {
187
- const p = v2(sid('cat'), [createMsg()]);
188
- ((p['messages'] as Json[])[0]['createSurface'] as Json)['catalogId'] = 'https://a2ui.org/specification/v0_9/catalogs/other/catalog.json';
189
- const r = await emit(runId, envelope(2, p));
190
- expect(code(r), req('openwop.requirement.0209.catalog-equality', 'RFC 0209 §A.2', 'a createSurface.catalogId MUST equal the payload catalogId; a differing one is refused, not recorded')).toBe('422 envelope_invalid');
191
- const ok = await emit(runId, envelope(2, v2(sid('cat-ok'), [createMsg()])));
192
- expect(ok.status, req('openwop.requirement.0209.catalog-equality', 'RFC 0209 §A.2', `the same payload with equal catalogIds MUST be admitted (control), got ${code(ok)}`)).toBe(201);
193
- } finally { await done(runId); }
194
- });
195
-
196
- it('a message whose surfaceId differs from the payload surfaceId is refused', async () => {
197
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
198
- if (!g.ok) return softSkip(g.kind, g.reason);
199
- const { runId } = g.ctx;
200
- try {
201
- const p = v2(sid('sid'), [createMsg(), componentsMsg()]);
202
- ((p['messages'] as Json[])[1]['updateComponents'] as Json)['surfaceId'] = sid('sid-other');
203
- const r = await emit(runId, envelope(2, p));
204
- expect(code(r), req('openwop.it.v2-a2ui-v09-surface.surface-id-equality', 'RFC 0209 §A.2', 'every message surfaceId MUST equal the payload surfaceId — the schema cannot express it, so the engine MUST refuse a mismatch')).toBe('422 envelope_invalid');
205
- const ok = await emit(runId, envelope(2, v2(sid('sid'), [createMsg(), componentsMsg()])));
206
- expect(ok.status, req('openwop.it.v2-a2ui-v09-surface.surface-id-equality', 'RFC 0209 §A.2', `the refused envelope left no trace: the same surfaceId is still new and admits createSurface (got ${code(ok)})`)).toBe(201);
207
- } finally { await done(runId); }
208
- });
209
- });
210
-
211
- describe('RFC 0209 §C.11 — recorded surfaces are returned as recorded (seam-gated)', () => {
212
- // "byte-equal" in these two titles means JCS-equal (lib/carries-payload.ts). The titles
213
- // are kept because they mint the rows' title ids.
214
- it('a recorded version-2 surface is returned byte-equal on poll and carried by a fork at a later fromSeq', async () => {
215
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
216
- if (!g.ok) return softSkip(g.kind, g.reason);
217
- const { runId } = g.ctx;
218
- try {
219
- const payload = v2(sid('rec'), (POSITIVE['messages'] as Json[]));
220
- const r = await emit(runId, envelope(2, payload));
221
- expect(r.status, req('openwop.it.v2-a2ui-v09-surface.recorded-as-recorded', 'RFC 0209 §C.11', `the positive surface MUST be admitted, got ${code(r)}`)).toBe(201);
222
- const sequence = (r.json as { sequence: number }).sequence;
223
- const events = await eventsOf(runId);
224
- expect(carries(events, payload), req('openwop.it.v2-a2ui-v09-surface.recorded-as-recorded', 'RFC 0209 §C.11', 'the run\'s poll MUST return the recorded surface payload unchanged as JSON (JCS-equal; member order carries no meaning) — no surface is regenerated')).toBe(true);
225
- // The surface is the last event of the suspended run, so `sequence + 1`
226
- // names no event and runs.md §Fork REQUIRES 422 fork_point_invalid for it.
227
- // Record one more envelope on the same surface and fork AT that event:
228
- // the surface (sequence < fromSeq) is fixed history, the later one re-executes.
229
- const later = await emit(runId, envelope(2, v2(sid('rec'), [dataMsg()])));
230
- expect(later.status, req('openwop.it.v2-a2ui-v09-surface.recorded-as-recorded', 'RFC 0209 §C.9', `an updateDataModel on the live surface MUST be admitted (it supplies the fork point), got ${code(later)}`)).toBe(201);
231
- const fromSeq = (later.json as { sequence: number }).sequence;
232
- expect(fromSeq > sequence, req('openwop.it.v2-a2ui-v09-surface.recorded-as-recorded', 'api/seams-v2.yaml emitA2uiSurface', `a later recording names a later sequence (surface ${sequence}, update ${fromSeq})`)).toBe(true);
233
- const fork = await driver.post(`/runs/${encodeURIComponent(runId)}:fork`, { mode: 'replay', fromSeq });
234
- expect(fork.status, req('openwop.it.v2-a2ui-v09-surface.recorded-as-recorded', 'RFC 0209 §C.11', `POST :fork at fromSeq ${fromSeq} (a recorded event after the surface) MUST answer 201, got ${code(fork)}`)).toBe(201);
235
- const forkId = (fork.json as { runId: string }).runId;
236
- expect(carries(await eventsOf(forkId), payload), req('openwop.it.v2-a2ui-v09-surface.recorded-as-recorded', 'RFC 0209 §C.11', 'a fork at a fromSeq after the surface MUST carry it unchanged')).toBe(true);
237
- await done(forkId);
238
- } finally { await done(runId); }
239
- });
240
-
241
- it('a version-1 surface recorded at floor 1 is returned byte-equal on poll and carried by a fork', async () => {
242
- const g = await gate((f) => f === 1, 'a floor of exactly 1 — a host whose floor is 2 has no admission path for a version-1 surface, and the suite cannot seed one (RFC 0209 §C.11 holds for what it recorded before raising the floor)');
243
- if (!g.ok) return softSkip(g.kind, g.reason);
244
- const { runId } = g.ctx;
245
- try {
246
- const r = await emit(runId, envelope(1, V1_SURFACE));
247
- expect(r.status, req('openwop.requirement.0209.legacy-readable', 'RFC 0209 §C.11', `a version-1 surface at floor 1 MUST be admitted, got ${code(r)}`)).toBe(201);
248
- const sequence = (r.json as { sequence: number }).sequence;
249
- expect(carries(await eventsOf(runId), V1_SURFACE), req('openwop.requirement.0209.legacy-readable', 'RFC 0209 §C.11', 'the poll MUST return the version-1 surface unchanged as JSON (JCS-equal)')).toBe(true);
250
- // As in the version-2 leg: fork at a recorded event after the surface, never at `sequence + 1` (no such event; 422 fork_point_invalid).
251
- const later = await emit(runId, envelope(1, V1_LATER));
252
- expect(later.status, req('openwop.requirement.0209.legacy-readable', 'RFC 0209 §C.11', `a second version-1 surface at floor 1 MUST be admitted (it supplies the fork point), got ${code(later)}`)).toBe(201);
253
- const fromSeq = (later.json as { sequence: number }).sequence;
254
- expect(fromSeq > sequence, req('openwop.requirement.0209.legacy-readable', 'api/seams-v2.yaml emitA2uiSurface', `a later recording names a later sequence (surface ${sequence}, next ${fromSeq})`)).toBe(true);
255
- const fork = await driver.post(`/runs/${encodeURIComponent(runId)}:fork`, { mode: 'replay', fromSeq });
256
- expect(fork.status, req('openwop.requirement.0209.legacy-readable', 'RFC 0209 §C.11', `POST :fork at fromSeq ${fromSeq} (a recorded event after the surface) MUST answer 201, got ${code(fork)}`)).toBe(201);
257
- const forkId = (fork.json as { runId: string }).runId;
258
- expect(carries(await eventsOf(forkId), V1_SURFACE), req('openwop.requirement.0209.legacy-readable', 'RFC 0209 §C.11', 'a fork at a later fromSeq MUST carry the version-1 surface unchanged')).toBe(true);
259
- await done(forkId);
260
- } finally { await done(runId); }
261
- });
262
- });
263
-
264
- describe('RFC 0209 §C.12 — trust is sticky across the fold (seam-gated)', () => {
265
- it('one untrusted update taints a trusted surface, a later trusted update does not launder it, and the approval does not advance; an all-trusted surface does', async () => {
266
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
267
- if (!g.ok) return softSkip(g.kind, g.reason);
268
- const { runId } = g.ctx;
269
- const R = (why: string) => req('openwop.requirement.0209.taint-sticky', 'RFC 0209 §C.12', why);
270
- let controlRun: string | null = null;
271
- try {
272
- const s = sid('taint');
273
- const steps = [
274
- await emit(runId, envelope(2, v2(s, [createMsg(), componentsMsg()]), { nodeId: GATE_NODE })),
275
- await emit(runId, envelope(2, v2(s, [componentsMsg()]), { nodeId: GATE_NODE, trust: 'untrusted' })),
276
- await emit(runId, envelope(2, v2(s, [componentsMsg()]), { nodeId: GATE_NODE, trust: 'trusted' })),
277
- ];
278
- steps.forEach((r, i) => expect(r.status, R(`surface step ${i} MUST be recorded (201) — an untrusted envelope is recorded and tainted, not refused; got ${code(r)}`)).toBe(201));
279
- const resolve = await driver.post(`/runs/${encodeURIComponent(runId)}/interrupts/${GATE_NODE}`, { resumeValue: { action: 'accept' } });
280
- expect(resolve.status >= 400 && resolve.status < 500, R(`resolving the approval bound to a tainted surface MUST be refused (4xx), got ${code(resolve)}`)).toBe(true);
281
- const text = JSON.stringify(resolve.json ?? {});
282
- expect(text.includes('untrusted_content_blocks_approval'), R(`the refusal MUST name untrusted_content_blocks_approval (in error.details or message): ${text.slice(0, 300)}`)).toBe(true);
283
- expect(await waitStatus(runId, new Set(['waiting-approval']), 1_000), R('a refused resolve MUST NOT advance the run — the approval stays pending')).toBe('waiting-approval');
284
-
285
- // Control: the same fold with no untrusted envelope MUST let the approval advance, or the refusal above proves nothing.
286
- const ctl = await driver.post('/runs', { workflowId: FIXTURE });
287
- expect(ctl.status, R(`control run MUST start, got ${code(ctl)}`)).toBe(201);
288
- controlRun = (ctl.json as { runId: string }).runId;
289
- expect(await waitStatus(controlRun, new Set(['waiting-approval', 'completed', 'failed', 'cancelled']), 10_000), R('the control run MUST suspend on its approval')).toBe('waiting-approval');
290
- const c = sid('trusted');
291
- const c1 = await emit(controlRun, envelope(2, v2(c, [createMsg(), componentsMsg()]), { nodeId: GATE_NODE }));
292
- const c2 = await emit(controlRun, envelope(2, v2(c, [componentsMsg()]), { nodeId: GATE_NODE, trust: 'trusted' }));
293
- expect([c1.status, c2.status], R('the control surface MUST be recorded')).toEqual([201, 201]);
294
- const ok = await driver.post(`/runs/${encodeURIComponent(controlRun)}/interrupts/${GATE_NODE}`, { resumeValue: { action: 'accept' } });
295
- expect(ok.status, R(`resolving the approval bound to an all-trusted surface MUST succeed (control), got ${code(ok)}`)).toBe(200);
296
- } finally {
297
- await done(runId);
298
- if (controlRun) await done(controlRun);
299
- }
300
- });
301
- });
302
-
303
- describe('RFC 0209 §C.9 — no render before root (declared unwitnessable here; reference-impl witness)', () => {
304
- it('records why render-needs-root has no server-side witness, and where its witness lives', () => {
305
- req('openwop.requirement.0209.render-needs-root', 'RFC 0209 §C.9', 'a renderer does not render a surface before its root component exists');
306
- return softSkip('inapplicable', 'unwitnessable by this suite (RFC 0209 §C.9 Falsifiability verdict): a render-side guarantee, and a server-oriented suite cannot observe a renderer. The fold guard the server CAN observe is openwop.requirement.0209.fold-guarded, above. The render half is witnessed as a reference-app client probe (tier: reference-impl, the a2ui-surface-no-code-exec precedent): openwop/openwop-app frontend/react/src/chat/a2ui/__tests__/a2ui-v09-render-needs-root.test.tsx (openwop-app#4121, ADR 0749). Removing the `if (!state.renderable)` guard in frontend/react/src/chat/a2ui/v09/A2uiV09Surface.tsx turns 2 of its 4 cases red. This row records that the suite reached the requirement, and it is not a pass.');
307
- });
308
- });
309
-
310
- describe('RFC 0220 §C — the a2uiSurface family witness (seam-gated)', () => {
311
- it('a version-2 surface is admitted and a version-1 body under schema version 2 is refused, in the same run', async () => {
312
- const g = await gate((f) => f >= 2, 'a floor of at least 2');
313
- if (!g.ok) return softSkip(g.kind, g.reason);
314
- const { runId } = g.ctx;
315
- const R = (why: string) => req('openwop.family.a2uiSurface', 'spec/v2/ext/a2uiSurface/README.md §Conformance', why);
316
- try {
317
- const good = await emit(runId, envelope(2, v2(sid('fam-good'), (POSITIVE['messages'] as Json[]))));
318
- expect(good.status, R(`the positive version-2 surface MUST be admitted (201), got ${code(good)}`)).toBe(201);
319
- const mixed = await emit(runId, envelope(2, V1_SURFACE));
320
- expect(code(mixed), R('a version-1 body under schema version 2 MUST be refused with envelope_invalid (control: a host that admits everything fails here)')).toBe('422 envelope_invalid');
321
- } finally { await done(runId); }
322
- });
323
- });
@@ -1,43 +0,0 @@
1
- /**
2
- * v2 — the `aiEnvelope` advertisement (`spec/v2/core/host-services.md`
3
- * §aiEnvelope; RFC 0144). The v1 twin is the record shape `aiEnvelope` carried
4
- * at v1; the legs live in `lib/family-advert-witness.ts`.
5
- *
6
- * record the record (with its optional `await` boolean) validates against
7
- * its capabilities-schema seat.
8
- *
9
- * Not here: exposing `ctx.aiEnvelope.generate` / `await` to pack code is a
10
- * pack-runtime surface with no wire witness.
11
- *
12
- * Dispositions: discovery unreadable ⇒ `blocked`; `aiEnvelope` absent, or the
13
- * facet a leg reads absent ⇒ `inapplicable`.
14
- *
15
- * @see spec/v2/core/host-services.md §aiEnvelope
16
- */
17
-
18
- import { describe, it, expect } from 'vitest';
19
- import { v2Discovery, familyAdvertised } from '../lib/v2.js';
20
- import { softSkip } from '../lib/soft-skip.js';
21
- import { req } from '../lib/requirement-ids.js';
22
- import { majorProfile } from '../lib/major-profile.js';
23
- import { recordSchemaLeg } from '../lib/family-advert-witness.js';
24
-
25
- const PROFILE = majorProfile(2);
26
- const DOC = 'host-services.md §aiEnvelope';
27
- const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
28
- const ID_RECORD = 'openwop.requirement.ai-envelope.advert-record-schema';
29
-
30
- async function discovery(): Promise<Record<string, unknown> | null> {
31
- try { return await v2Discovery(); } catch { return null; }
32
- }
33
-
34
- describe('v2 aiEnvelope advertisement (host-services.md §aiEnvelope)', () => {
35
- it('the aiEnvelope record validates against its capabilities-schema seat', async () => {
36
- const doc = await discovery();
37
- if (!doc) return softSkip('blocked', UNREADABLE);
38
- if (!(await familyAdvertised('aiEnvelope'))) return softSkip('inapplicable', 'the host does not advertise aiEnvelope');
39
- const out = recordSchemaLeg(PROFILE, doc, 'aiEnvelope', DOC);
40
- if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
41
- for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
42
- });
43
- });
@@ -1,48 +0,0 @@
1
- /**
2
- * v2 — the `envelopeContracts` advertisement (`spec/v2/core/events.md` §Envelope contracts): the envelope contract claim. It had no v1 witness;
3
- * the legs live in `lib/family-advert-witness.ts` (witness wave 3).
4
- *
5
- * record the record validates against its capabilities-schema seat;
6
- *
7
- * Not here: the refusal leg needs an error code in the prose and a fixture pack.
8
- *
9
- * Dispositions: discovery unreadable ⇒ `blocked`; `envelopeContracts` absent, or the
10
- * facet a leg reads absent ⇒ `inapplicable`.
11
- *
12
- * @see spec/v2/core/events.md §Envelope contracts
13
- */
14
-
15
- import { describe, it, expect } from 'vitest';
16
- import { v2Discovery, familyAdvertised } from '../lib/v2.js';
17
- import { softSkip } from '../lib/soft-skip.js';
18
- import { req } from '../lib/requirement-ids.js';
19
- import { majorProfile } from '../lib/major-profile.js';
20
- import { recordSchemaLeg } from '../lib/family-advert-witness.js';
21
-
22
- const PROFILE = majorProfile(2);
23
- const DOC = 'events.md §Envelope contracts';
24
- const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
25
- const ID_RECORD = 'openwop.requirement.envelope-contracts.advert-record-schema';
26
-
27
- async function discovery(): Promise<Record<string, unknown> | null> {
28
- try { return await v2Discovery(); } catch { return null; }
29
- }
30
-
31
- describe('v2 envelopeContracts advertisement (events.md §Envelope contracts)', () => {
32
- it('the envelopeContracts record validates against its capabilities-schema seat', async () => {
33
- const doc = await discovery();
34
- if (!doc) return softSkip('blocked', UNREADABLE);
35
- if (!(await familyAdvertised('envelopeContracts'))) return softSkip('inapplicable', 'the host does not advertise envelopeContracts');
36
- const out = recordSchemaLeg(PROFILE, doc, 'envelopeContracts', DOC);
37
- if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
38
- for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
39
- });
40
-
41
- it('envelopeContracts.advertised is not false: a host that does not offer the family omits it', async () => {
42
- const doc = await discovery();
43
- if (!doc) return softSkip('blocked', UNREADABLE);
44
- const rec = await familyAdvertised('envelopeContracts');
45
- if (!rec) return softSkip('inapplicable', 'the host does not advertise envelopeContracts');
46
- expect(rec['advertised'], req('openwop.requirement.0237.no-false-advertisement', 'RFC 0237 §C; capabilities.md §2', 'envelopeContracts.advertised: false is not a v2 state; a host that does not offer the family MUST omit it')).not.toBe(false);
47
- });
48
- });
@@ -1,81 +0,0 @@
1
- /**
2
- * v2 — the optional `reasoning` payload field (`spec/v2/core/events.md`
3
- * §`envelopes`; RFC 0030 §A). Static: reads `schemas/v2/envelopes/` only. The
4
- * v1 twin is `envelope-reasoning-shape`; its two advertisement legs
5
- * (`envelopes.reasoning`, `envelopes.tierOneSubsetCompliance`) are ported to
6
- * `v2-envelopes-advertisement`, not repeated here.
7
- *
8
- * declared `clarification.request`, `schema.request` and `error` declare an
9
- * OPTIONAL `reasoning: string` ("A vendor kind whose payload needs
10
- * multi-step reasoning SHOULD declare it as optional");
11
- * omitted `schema.response` (a side-channel ack) does not declare it;
12
- * absent ok each of the three validates WITHOUT `reasoning` ("A host MUST NOT
13
- * reject an envelope without it, whatever `promptDirective` says");
14
- * present ok each validates WITH a string `reasoning`;
15
- * typed a non-string `reasoning` is rejected.
16
- *
17
- * Host-free: every leg runs with no OPENWOP_BASE_URL.
18
- *
19
- * @see spec/v2/core/events.md §envelopes
20
- * @see schemas/v2/envelopes/
21
- */
22
-
23
- import { describe, it, expect } from 'vitest';
24
- import { v2Validator } from '../lib/v2.js';
25
- import { req } from '../lib/requirement-ids.js';
26
- import { loadV2EnvelopeSchema } from '../lib/envelope-schema-static.js';
27
-
28
- const DOC = 'events.md §envelopes';
29
- const ID_DECLARED = 'openwop.requirement.envelopes.reasoning-declared-optional-string';
30
- const ID_OMITTED = 'openwop.requirement.envelopes.reasoning-omitted-from-schema-response';
31
- const ID_ABSENT_OK = 'openwop.requirement.envelopes.reasoning-absent-accepted';
32
- const ID_PRESENT_OK = 'openwop.requirement.envelopes.reasoning-present-accepted';
33
- const ID_TYPED = 'openwop.requirement.envelopes.reasoning-non-string-rejected';
34
-
35
- /** A valid payload per kind, without `reasoning`. */
36
- const BASE: Readonly<Record<string, Record<string, unknown>>> = {
37
- 'clarification.request': { questions: [{ id: 'q1', question: 'Which provider?' }] },
38
- 'schema.request': { envelopeType: 'acme.prd.create' },
39
- error: { code: 'validation_failed', message: 'Could not match the schema.' },
40
- };
41
- const KINDS = Object.keys(BASE);
42
-
43
- describe('v2 envelope reasoning: payload schemas (events.md §envelopes)', () => {
44
- for (const kind of KINDS) {
45
- it(`${kind} declares an optional reasoning of type string`, () => {
46
- const schema = loadV2EnvelopeSchema(kind);
47
- const props = (schema?.['properties'] ?? {}) as Record<string, Record<string, unknown>>;
48
- const required = (schema?.['required'] ?? []) as unknown[];
49
- expect(schema !== null, req(ID_DECLARED, DOC, `schemas/v2/envelopes/${kind}.schema.json MUST exist`)).toBe(true);
50
- expect(props['reasoning']?.['type'], req(ID_DECLARED, DOC, `${kind} MUST declare reasoning as type string`)).toBe('string');
51
- expect(required.includes('reasoning'), req(ID_DECLARED, DOC, `${kind} MUST NOT require reasoning — it is optional`)).toBe(false);
52
- });
53
- }
54
-
55
- it('schema.response does not declare reasoning (a side-channel ack)', () => {
56
- const schema = loadV2EnvelopeSchema('schema.response');
57
- const props = (schema?.['properties'] ?? {}) as Record<string, unknown>;
58
- expect(schema !== null, req(ID_OMITTED, 'RFC 0030 §A', 'schemas/v2/envelopes/schema.response.schema.json MUST exist')).toBe(true);
59
- expect('reasoning' in props, req(ID_OMITTED, 'RFC 0030 §A', 'schema.response MUST NOT declare reasoning')).toBe(false);
60
- });
61
- });
62
-
63
- describe('v2 envelope reasoning: round-trip (events.md §envelopes)', () => {
64
- for (const kind of KINDS) {
65
- const validate = v2Validator(`envelopes/${kind}`);
66
-
67
- it(`${kind} validates without reasoning`, () => {
68
- const r = validate(BASE[kind]);
69
- expect(r.ok, req(ID_ABSENT_OK, DOC, `a ${kind} payload without reasoning MUST validate — a host MUST NOT reject it (${r.errors})`)).toBe(true);
70
- });
71
-
72
- it(`${kind} validates with a string reasoning`, () => {
73
- const r = validate({ reasoning: 'The input named X twice; I asked which one.', ...BASE[kind] });
74
- expect(r.ok, req(ID_PRESENT_OK, DOC, `a ${kind} payload with a string reasoning MUST validate (${r.errors})`)).toBe(true);
75
- });
76
-
77
- it(`${kind} rejects a non-string reasoning`, () => {
78
- expect(validate({ reasoning: 42, ...BASE[kind] }).ok, req(ID_TYPED, DOC, `${kind}: a numeric reasoning MUST be rejected — reasoning is a plain string`)).toBe(false);
79
- });
80
- }
81
- });
@@ -1,64 +0,0 @@
1
- /**
2
- * v2 — the Tier-1 structured-output subset, statically (RFC 0030 §B;
3
- * `envelopes.tierOneSubsetCompliance` in `schemas/v2/capabilities.schema.json`).
4
- * The v1 twin is `envelope-tier-one-subset-static`; the walker lives in
5
- * `lib/envelope-schema-static.ts`.
6
- *
7
- * The always-on load-bearing walk over the universal kinds reads only the
8
- * corpus, so it lives in `src/coherence/v2-envelope-tier-one-universal-static`
9
- * (moved in 2.45.18; `conformance.md` §Two products). This file keeps the one
10
- * leg that reads host output:
11
- *
12
- * strict LIVE: a host advertising `tierOneSubsetCompliance: "strict"`
13
- * has, for every kind it lists in `supportedEnvelopes.kinds`
14
- * that resolves to a corpus schema, a schema meeting the whole
15
- * intersection (closed objects, every property required, no
16
- * string/number/array bounds). Host-served vendor schemas are
17
- * not fetched, as at v1.
18
- *
19
- * The strict rule is stated by the schema seat's description ("`strict`: every
20
- * host-served envelope schema passes the static subset-compliance check"). The
21
- * v2 prose (`events.md` §envelopes) states a different, behavioural rule — the
22
- * host MUST accept an envelope restricted to the subset from any advertised
23
- * provider — and does not distinguish strict, warn and off.
24
- *
25
- * Dispositions: discovery unreadable ⇒ `blocked`; `envelopes` not
26
- * advertised, or `tierOneSubsetCompliance` other than "strict" ⇒
27
- * `inapplicable`; no advertised kind resolving to a corpus schema ⇒
28
- * `inapplicable`.
29
- *
30
- * @see RFCS/0030-envelope-reasoning-and-tier-one-subset.md §B
31
- * @see spec/v2/core/events.md §envelopes
32
- */
33
-
34
- import { describe, it, expect } from 'vitest';
35
- import { familyAdvertised, v2Discovery } from '../lib/v2.js';
36
- import { softSkip } from '../lib/soft-skip.js';
37
- import { req } from '../lib/requirement-ids.js';
38
- import { loadV2EnvelopeSchema, tierOneViolations, type SchemaViolation } from '../lib/envelope-schema-static.js';
39
-
40
- const ID_STRICT = 'openwop.requirement.envelopes.tier-one-strict-advertised-kinds';
41
-
42
- describe('v2 Tier-1 subset: a strict host\'s advertised kinds meet the whole intersection', () => {
43
- it('a host advertising tierOneSubsetCompliance strict has advertised kinds whose corpus schemas satisfy the Tier-1 intersection', async () => {
44
- let doc: Record<string, unknown> | null;
45
- try { doc = await v2Discovery(); } catch { doc = null; }
46
- if (!doc) return softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0');
47
- const envelopes = await familyAdvertised('envelopes');
48
- if (envelopes === null) return softSkip('inapplicable', 'the host does not advertise envelopes');
49
- if (envelopes['tierOneSubsetCompliance'] !== 'strict') return softSkip('inapplicable', `envelopes.tierOneSubsetCompliance is ${JSON.stringify(envelopes['tierOneSubsetCompliance'])}, not "strict" — the strict walk binds only a strict host`);
50
- const catalog = await familyAdvertised('supportedEnvelopes');
51
- const kinds = Array.isArray(catalog?.['kinds']) ? (catalog['kinds'] as unknown[]).filter((k): k is string => typeof k === 'string') : [];
52
- const walked: Record<string, SchemaViolation[]> = {};
53
- let resolved = 0;
54
- for (const kind of kinds) {
55
- const schema = loadV2EnvelopeSchema(kind);
56
- if (schema === null) continue;
57
- resolved++;
58
- const v = tierOneViolations(schema, 'strict');
59
- if (v.length > 0) walked[kind] = v;
60
- }
61
- if (resolved === 0) return softSkip('inapplicable', `none of the ${kinds.length} kinds in supportedEnvelopes.kinds resolves to a corpus schema; host-served vendor schemas are not fetched`);
62
- expect(walked, req(ID_STRICT, 'capabilities.schema.json §envelopes.tierOneSubsetCompliance', `a strict host's advertised kinds MUST satisfy the Tier-1 intersection: ${JSON.stringify(walked)}`)).toEqual({});
63
- });
64
- });