@openwop/openwop-conformance 1.123.0 → 1.125.0
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/README.md +2 -2
- package/dist/lib/scenario-disposition.js +14 -1
- package/package.json +1 -1
- package/schemas/CORPUS-STAMP.json +2 -2
- package/src/lib/error-envelope.ts +77 -0
- package/src/lib/scenario-disposition.ts +15 -1
- package/src/scenarios/conformance-execution-witness.test.ts +12 -0
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +3 -2
- package/src/scenarios/cross-engine-append-behavior.test.ts +26 -5
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +8 -4
- package/src/scenarios/cross-host-causation-shape.test.ts +4 -2
- package/src/scenarios/data-residency-admission.test.ts +6 -6
- package/src/scenarios/envelope-recovery-applied.test.ts +3 -2
- package/src/scenarios/envelope-refusal-shape.test.ts +4 -3
- package/src/scenarios/error-envelope-canonical-shape.test.ts +64 -0
- package/src/scenarios/fs-path-traversal.test.ts +3 -2
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +8 -4
- package/src/scenarios/multi-region-idempotency-behavior.test.ts +29 -7
- package/src/scenarios/provider-usage.test.ts +2 -2
- package/src/scenarios/replay-divergence-at-refusal.test.ts +12 -6
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +4 -2
- package/src/scenarios/sandbox-mvp-behavior.test.ts +20 -10
- package/src/scenarios/table-schema-enforcement.test.ts +2 -2
- package/src/scenarios/voice-streamref-tenant-bound.test.ts +2 -1
- package/src/scenarios/voice-transcription-streaming.test.ts +2 -1
- package/src/scenarios/voice-transcription-unadvertised.test.ts +2 -1
- package/src/scenarios/workload-identity-behavior.test.ts +7 -4
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { describe, it, expect } from 'vitest';
|
|
34
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
34
|
+
import { softSkip, seamAbsent } from '../lib/soft-skip.js';
|
|
35
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
35
36
|
import { driver } from '../lib/driver.js';
|
|
36
37
|
|
|
37
38
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
@@ -56,6 +57,21 @@ async function simulatePartition(claims: ConflictClaim[]): Promise<{ status: num
|
|
|
56
57
|
return { status: res.status, body: (res.json as ConvergenceResult) ?? {} };
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The multi-region simulator seam answered 404. `blocked` when the host advertises
|
|
62
|
+
* a cross-region posture (`idempotency.crossRegion` present) it has made
|
|
63
|
+
* unobservable, `inapplicable` when it advertises none (RFC 0148 §A).
|
|
64
|
+
*/
|
|
65
|
+
async function noteSimulatorAbsent(): Promise<void> {
|
|
66
|
+
const disco = await driver.get('/.well-known/openwop');
|
|
67
|
+
const idem = capabilityFamily<{ crossRegion?: unknown }>(disco.json, 'idempotency');
|
|
68
|
+
if (idem?.crossRegion !== undefined) {
|
|
69
|
+
seamAbsent(`host advertises \`idempotency.crossRegion: ${String(idem.crossRegion)}\` but \`POST /v1/host/sample/test/multi-region/simulate-partition\` answered 404 — the convergence rule is unobservable (host-sample-test-seams.md §6)`);
|
|
70
|
+
} else {
|
|
71
|
+
softSkip('inapplicable', 'optional advertisement — `idempotency.crossRegion` not advertised by this host, and the multi-region simulator seam is absent (RFC 0036 §C)');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule (RFC 0036 §C)', () => {
|
|
60
76
|
it('two-region conflict resolves to the lex-min runId per annex §"Convergence rule"', async (ctx) => {
|
|
61
77
|
const probe = await simulatePartition([
|
|
@@ -63,8 +79,9 @@ describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule
|
|
|
63
79
|
{ runId: 'run-a-west', tenantId: 't1', endpoint: 'POST /v1/runs', key: 'idem-1', region: 'eu-west-1' },
|
|
64
80
|
]);
|
|
65
81
|
if (probe.status === 404) {
|
|
82
|
+
await noteSimulatorAbsent();
|
|
66
83
|
ctx.skip(); // host doesn't expose the simulator seam
|
|
67
|
-
return
|
|
84
|
+
return;
|
|
68
85
|
}
|
|
69
86
|
expect(
|
|
70
87
|
probe.status,
|
|
@@ -89,8 +106,9 @@ describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule
|
|
|
89
106
|
{ runId: 'mmm-2', tenantId: 't1', endpoint: 'POST /v1/runs', key: 'idem-2', region: 'r3' },
|
|
90
107
|
]);
|
|
91
108
|
if (probe.status === 404) {
|
|
109
|
+
await noteSimulatorAbsent();
|
|
92
110
|
ctx.skip();
|
|
93
|
-
return
|
|
111
|
+
return;
|
|
94
112
|
}
|
|
95
113
|
expect(probe.status).toBe(200);
|
|
96
114
|
expect(
|
|
@@ -115,8 +133,9 @@ describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule
|
|
|
115
133
|
{ runId: 'run-a', tenantId: 't1', endpoint: 'POST /v1/runs', key: 'idem-3', region: 'r2' },
|
|
116
134
|
]);
|
|
117
135
|
if (probe.status === 404) {
|
|
136
|
+
await noteSimulatorAbsent();
|
|
118
137
|
ctx.skip();
|
|
119
|
-
return
|
|
138
|
+
return;
|
|
120
139
|
}
|
|
121
140
|
expect(probe.status).toBe(200);
|
|
122
141
|
const redirects = probe.body.cacheRedirects ?? [];
|
|
@@ -144,8 +163,9 @@ describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule
|
|
|
144
163
|
{ runId: 'run-a', tenantId: 't1', endpoint: 'POST /v1/runs', key: 'idem-4', region: 'r2' },
|
|
145
164
|
]);
|
|
146
165
|
if (probe.status === 404) {
|
|
166
|
+
await noteSimulatorAbsent();
|
|
147
167
|
ctx.skip();
|
|
148
|
-
return
|
|
168
|
+
return;
|
|
149
169
|
}
|
|
150
170
|
expect(probe.status).toBe(200);
|
|
151
171
|
expect(
|
|
@@ -165,8 +185,9 @@ describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule
|
|
|
165
185
|
];
|
|
166
186
|
const p1 = await simulatePartition(claims);
|
|
167
187
|
if (p1.status === 404) {
|
|
188
|
+
await noteSimulatorAbsent();
|
|
168
189
|
ctx.skip();
|
|
169
|
-
return
|
|
190
|
+
return;
|
|
170
191
|
}
|
|
171
192
|
expect(p1.status).toBe(200);
|
|
172
193
|
const p2 = await simulatePartition([claims[2]!, claims[0]!, claims[1]!]);
|
|
@@ -190,8 +211,9 @@ describe.skipIf(HTTP_SKIP)('multi-region-idempotency-behavior: convergence rule
|
|
|
190
211
|
{ runId: 'r2', tenantId: 't2', endpoint: 'POST /v1/runs', key: 'idem-6', region: 'r2' },
|
|
191
212
|
]);
|
|
192
213
|
if (probe.status === 404) {
|
|
214
|
+
await noteSimulatorAbsent();
|
|
193
215
|
ctx.skip();
|
|
194
|
-
return
|
|
216
|
+
return;
|
|
195
217
|
}
|
|
196
218
|
expect(
|
|
197
219
|
probe.status,
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { describe, it, expect } from 'vitest';
|
|
23
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
23
24
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
24
25
|
import { readFileSync } from 'node:fs';
|
|
25
26
|
import { join } from 'node:path';
|
|
@@ -179,8 +180,7 @@ describe('provider-usage: event presence via emit-seam + event-log query (RFC 00
|
|
|
179
180
|
res.status,
|
|
180
181
|
driver.describe('SECURITY/invariants.yaml provider-usage-no-credential-leak', 'payload with credentialRef-shaped content MUST be refused'),
|
|
181
182
|
).toBe(400);
|
|
182
|
-
|
|
183
|
-
expect(body.error?.code).toBe('provider_usage_credential_leak');
|
|
183
|
+
expect(readErrorCode(res.json)).toBe('provider_usage_credential_leak');
|
|
184
184
|
await resetTestSeam();
|
|
185
185
|
});
|
|
186
186
|
});
|
|
@@ -78,13 +78,15 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: advertisement shape (R
|
|
|
78
78
|
it('replayDeterminism (when present) conforms to RFC 0041 §D', async (ctx) => {
|
|
79
79
|
const d = await readDiscovery();
|
|
80
80
|
if (d === null) {
|
|
81
|
+
softSkip('blocked', 'precondition not met — `d === null` returned early (seam, prior step, or fixture unavailable)');
|
|
81
82
|
ctx.skip();
|
|
82
|
-
return
|
|
83
|
+
return;
|
|
83
84
|
}
|
|
84
85
|
const rd = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.replayDeterminism;
|
|
85
86
|
if (rd === undefined) {
|
|
87
|
+
softSkip('inapplicable', 'optional advertisement — `multiAgent.executionModel.replayDeterminism` not advertised by this host (RFC 0041 §D)');
|
|
86
88
|
ctx.skip(); // optional advertisement — host hasn't opted in
|
|
87
|
-
return
|
|
89
|
+
return;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
expect(
|
|
@@ -192,8 +194,9 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: behavioral (RFC 0041
|
|
|
192
194
|
{ content: validEnv, stopReason: 'end_turn' as const },
|
|
193
195
|
]);
|
|
194
196
|
if (programStatus === 404) {
|
|
197
|
+
softSkip('blocked', 'precondition not met — `programStatus === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
195
198
|
ctx.skip(); // mock-AI program seam not exposed — soft-skip
|
|
196
|
-
return
|
|
199
|
+
return;
|
|
197
200
|
}
|
|
198
201
|
expect(programStatus).toBe(200);
|
|
199
202
|
|
|
@@ -201,8 +204,9 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: behavioral (RFC 0041
|
|
|
201
204
|
workflowId: 'conformance-phase4-replay-divergence',
|
|
202
205
|
});
|
|
203
206
|
if (createRes.status === 404 || createRes.status === 422) {
|
|
207
|
+
softSkip('blocked', 'precondition not met — `createRes.status === 404 || createRes.status === 422` returned early (seam, prior step, or fixture unavailable)');
|
|
204
208
|
ctx.skip(); // fixture not advertised
|
|
205
|
-
return
|
|
209
|
+
return;
|
|
206
210
|
}
|
|
207
211
|
expect(createRes.status).toBe(201);
|
|
208
212
|
const sourceRunId = (createRes.json as { runId: string }).runId;
|
|
@@ -273,8 +277,9 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: behavioral (RFC 0041
|
|
|
273
277
|
{ content: 'safety-refused-for-conformance', stopReason: 'safety' as const, refusalText: 'safety-refused-for-conformance' },
|
|
274
278
|
]);
|
|
275
279
|
if (programStatus === 404) {
|
|
280
|
+
softSkip('blocked', 'precondition not met — `programStatus === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
276
281
|
ctx.skip();
|
|
277
|
-
return
|
|
282
|
+
return;
|
|
278
283
|
}
|
|
279
284
|
expect(programStatus).toBe(200);
|
|
280
285
|
|
|
@@ -282,8 +287,9 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: behavioral (RFC 0041
|
|
|
282
287
|
workflowId: 'conformance-phase4-replay-divergence',
|
|
283
288
|
});
|
|
284
289
|
if (createRes.status === 404 || createRes.status === 422) {
|
|
290
|
+
softSkip('blocked', 'precondition not met — `createRes.status === 404 || createRes.status === 422` returned early (seam, prior step, or fixture unavailable)');
|
|
285
291
|
ctx.skip();
|
|
286
|
-
return
|
|
292
|
+
return;
|
|
287
293
|
}
|
|
288
294
|
expect(createRes.status).toBe(201);
|
|
289
295
|
const sourceRunId = (createRes.json as { runId: string }).runId;
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import { describe, it, expect } from 'vitest';
|
|
44
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
44
|
+
import { softSkip, seamAbsent } from '../lib/soft-skip.js';
|
|
45
45
|
import { driver } from '../lib/driver.js';
|
|
46
46
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
47
47
|
|
|
@@ -85,6 +85,7 @@ async function gateOnPhase4(ctx: { skip: () => void }): Promise<boolean> {
|
|
|
85
85
|
const em = capabilityFamily<{ executionModel?: ExecutionModelCaps }>(d, 'multiAgent')?.executionModel;
|
|
86
86
|
const version = typeof em?.version === 'number' ? em.version : 0;
|
|
87
87
|
if (em?.replayDeterminism?.supported !== true || version < 4) {
|
|
88
|
+
softSkip('inapplicable', 'optional advertisement — `multiAgent.executionModel.replayDeterminism.supported` with `version >= 4` not advertised by this host (RFC 0041 §C)');
|
|
88
89
|
ctx.skip();
|
|
89
90
|
return false;
|
|
90
91
|
}
|
|
@@ -152,7 +153,8 @@ function stripVolatile(ev: RunEventDoc): unknown {
|
|
|
152
153
|
async function startFixtureRun(ctx: { skip: () => void }): Promise<string | null> {
|
|
153
154
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
154
155
|
if (create.status === 404 || create.status === 422) {
|
|
155
|
-
|
|
156
|
+
seamAbsent(`fixture \`${FIXTURE}\` not registered on this host (POST /v1/runs → ${create.status}) while replayDeterminism is advertised`);
|
|
157
|
+
ctx.skip();
|
|
156
158
|
return null;
|
|
157
159
|
}
|
|
158
160
|
expect(create.status).toBe(201);
|
|
@@ -82,8 +82,9 @@ async function invoke(typeId: string, args: Record<string, unknown> = {}, allowe
|
|
|
82
82
|
describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode invariants (node:vm MVP)', () => {
|
|
83
83
|
it('host-fs-escape — fs access from sandboxed code fails closed', async (ctx) => {
|
|
84
84
|
if (!(await isSandboxAdvertised())) {
|
|
85
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
85
86
|
ctx.skip();
|
|
86
|
-
return
|
|
87
|
+
return;
|
|
87
88
|
}
|
|
88
89
|
const probe = await invoke('misbehave.fs-escape-read');
|
|
89
90
|
expect(probe.status).toBe(200);
|
|
@@ -105,8 +106,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
105
106
|
|
|
106
107
|
it('host-env-leak — process.env access from sandboxed code fails closed', async (ctx) => {
|
|
107
108
|
if (!(await isSandboxAdvertised())) {
|
|
109
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
108
110
|
ctx.skip();
|
|
109
|
-
return
|
|
111
|
+
return;
|
|
110
112
|
}
|
|
111
113
|
const probe = await invoke('misbehave.env-leak');
|
|
112
114
|
expect(probe.status).toBe(200);
|
|
@@ -122,8 +124,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
122
124
|
|
|
123
125
|
it('network-escape — http/net access from sandboxed code fails closed', async (ctx) => {
|
|
124
126
|
if (!(await isSandboxAdvertised())) {
|
|
127
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
125
128
|
ctx.skip();
|
|
126
|
-
return
|
|
129
|
+
return;
|
|
127
130
|
}
|
|
128
131
|
const probe = await invoke('misbehave.network-escape');
|
|
129
132
|
expect(probe.status).toBe(200);
|
|
@@ -139,8 +142,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
139
142
|
|
|
140
143
|
it('host-process-escape — child_process access from sandboxed code fails closed', async (ctx) => {
|
|
141
144
|
if (!(await isSandboxAdvertised())) {
|
|
145
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
142
146
|
ctx.skip();
|
|
143
|
-
return
|
|
147
|
+
return;
|
|
144
148
|
}
|
|
145
149
|
const probe = await invoke('misbehave.process-escape');
|
|
146
150
|
expect(probe.status).toBe(200);
|
|
@@ -153,8 +157,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
153
157
|
|
|
154
158
|
it('sandbox-timeout — runaway loop terminated by wallClockLimitMs', async (ctx) => {
|
|
155
159
|
if (!(await isSandboxAdvertised())) {
|
|
160
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
156
161
|
ctx.skip();
|
|
157
|
-
return
|
|
162
|
+
return;
|
|
158
163
|
}
|
|
159
164
|
const start = Date.now();
|
|
160
165
|
const probe = await invoke('misbehave.timeout');
|
|
@@ -179,8 +184,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
179
184
|
|
|
180
185
|
it('cross-pack-mutation — fresh vm context per invocation, no state leaks', async (ctx) => {
|
|
181
186
|
if (!(await isSandboxAdvertised())) {
|
|
187
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
182
188
|
ctx.skip();
|
|
183
|
-
return
|
|
189
|
+
return;
|
|
184
190
|
}
|
|
185
191
|
const r1 = await invoke('misbehave.cross-pack-mutate');
|
|
186
192
|
const r2 = await invoke('misbehave.cross-pack-mutate');
|
|
@@ -205,8 +211,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
205
211
|
|
|
206
212
|
it('capability-gate-respected — host call NOT in allowedHostCalls fails with sandbox_capability_denied', async (ctx) => {
|
|
207
213
|
if (!(await isSandboxAdvertised())) {
|
|
214
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
208
215
|
ctx.skip();
|
|
209
|
-
return
|
|
216
|
+
return;
|
|
210
217
|
}
|
|
211
218
|
const probe = await invoke('misbehave.capability-gate-violation', {}, []);
|
|
212
219
|
expect(probe.status).toBe(200);
|
|
@@ -228,8 +235,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
228
235
|
|
|
229
236
|
it('memory-exceeded — runaway allocation fails with sandbox_memory_exceeded', async (ctx) => {
|
|
230
237
|
if (!(await isSandboxAdvertised())) {
|
|
238
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
231
239
|
ctx.skip();
|
|
232
|
-
return
|
|
240
|
+
return;
|
|
233
241
|
}
|
|
234
242
|
const probe = await invoke('misbehave.memory-bomb');
|
|
235
243
|
expect(probe.status).toBe(200);
|
|
@@ -255,8 +263,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
255
263
|
|
|
256
264
|
it('well-behaved.host-fetch — allowedHostCalls=[fetch] permits the host call', async (ctx) => {
|
|
257
265
|
if (!(await isSandboxAdvertised())) {
|
|
266
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
258
267
|
ctx.skip();
|
|
259
|
-
return
|
|
268
|
+
return;
|
|
260
269
|
}
|
|
261
270
|
const probe = await invoke('well-behaved.host-fetch', {}, ['fetch']);
|
|
262
271
|
expect(probe.status).toBe(200);
|
|
@@ -271,8 +280,9 @@ describe.skipIf(HTTP_SKIP)('sandbox-mvp-behavior: RFC 0035 §B failure-mode inva
|
|
|
271
280
|
|
|
272
281
|
it('well-behaved.echo — sandboxed code returns args round-trip when no escape attempt', async (ctx) => {
|
|
273
282
|
if (!(await isSandboxAdvertised())) {
|
|
283
|
+
softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isSandboxAdvertised())` returned early');
|
|
274
284
|
ctx.skip();
|
|
275
|
-
return
|
|
285
|
+
return;
|
|
276
286
|
}
|
|
277
287
|
const probe = await invoke('well-behaved.echo', { input: 'hello-sandbox' });
|
|
278
288
|
expect(probe.status).toBe(200);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
17
18
|
import { driver } from '../lib/driver.js';
|
|
18
19
|
|
|
19
20
|
interface DiscoveryDoc {
|
|
@@ -74,8 +75,7 @@ describe('table-schema-enforcement: behavioral (RFC 0016 §B point 2)', () => {
|
|
|
74
75
|
bad.status >= 400 && bad.status < 500,
|
|
75
76
|
driver.describe('RFC 0016 §B point 2', 'type-divergent insert MUST be rejected with 4xx'),
|
|
76
77
|
).toBe(true);
|
|
77
|
-
const
|
|
78
|
-
const code = typeof body.error === 'string' ? body.error : body.error?.code;
|
|
78
|
+
const code = readErrorCode(bad.json);
|
|
79
79
|
expect(
|
|
80
80
|
code,
|
|
81
81
|
driver.describe('RFC 0016 §B point 2', 'rejection MUST carry the table_schema_violation error code'),
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { describe, it, expect } from 'vitest';
|
|
21
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
21
22
|
import { driver } from '../lib/driver.js';
|
|
22
23
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
23
24
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
@@ -29,7 +30,7 @@ function realtimeVoiceOf(ai: Record<string, unknown> | undefined): Record<string
|
|
|
29
30
|
return rv && typeof rv === 'object' ? (rv as Record<string, unknown>) : undefined;
|
|
30
31
|
}
|
|
31
32
|
function errCode(json: unknown): string | undefined {
|
|
32
|
-
return (json
|
|
33
|
+
return readErrorCode(json);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
describe('voice-streamref-tenant-bound (RFC 0106 §F INV-4)', () => {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { describe, it, expect } from 'vitest';
|
|
22
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
22
23
|
import { driver } from '../lib/driver.js';
|
|
23
24
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
24
25
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
@@ -30,7 +31,7 @@ function realtimeVoiceOf(ai: Record<string, unknown> | undefined): Record<string
|
|
|
30
31
|
return rv && typeof rv === 'object' ? (rv as Record<string, unknown>) : undefined;
|
|
31
32
|
}
|
|
32
33
|
function errCode(json: unknown): string | undefined {
|
|
33
|
-
return (json
|
|
34
|
+
return readErrorCode(json);
|
|
34
35
|
}
|
|
35
36
|
function eventsOf(json: unknown): Array<{ type?: string; payload?: Record<string, unknown> }> {
|
|
36
37
|
const e = (json as { events?: unknown })?.events;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
17
18
|
import { driver } from '../lib/driver.js';
|
|
18
19
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
19
20
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
@@ -25,7 +26,7 @@ function realtimeVoiceOf(ai: Record<string, unknown> | undefined): Record<string
|
|
|
25
26
|
return rv && typeof rv === 'object' ? (rv as Record<string, unknown>) : undefined;
|
|
26
27
|
}
|
|
27
28
|
function errCode(json: unknown): string | undefined {
|
|
28
|
-
return (json
|
|
29
|
+
return readErrorCode(json);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
describe('voice-transcription-unadvertised (RFC 0106 §B)', () => {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import { describe, it, expect } from 'vitest';
|
|
36
|
+
import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
36
37
|
import { driver } from '../lib/driver.js';
|
|
37
38
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
39
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
@@ -76,7 +77,7 @@ async function resolve(body: Record<string, unknown>): Promise<{ status: number;
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function reasonOf(json: unknown): string | undefined {
|
|
79
|
-
return (json
|
|
80
|
+
return readErrorCode(json);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const IDENTITY = { scheme: 'spiffe', subject: 'spiffe://example/dispatcher', issuer: 'spiffe://example' };
|
|
@@ -154,9 +155,11 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
154
155
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
155
156
|
const r = await resolve({ identity: { scheme: 'spiffe', subject: 'spiffe://example/unknown' } });
|
|
156
157
|
if (r === null || r.status < 400) return;
|
|
157
|
-
|
|
158
|
+
// canonical flat envelope: code = `error`, retriable = `details.retriable`
|
|
159
|
+
// (the legacy nested `error.{code,retriable}` the §20 catalog prescribed until
|
|
160
|
+
// 2026-08-16 is tolerated by the helpers for the deprecation window)
|
|
158
161
|
expect(
|
|
159
|
-
|
|
162
|
+
readRetriable(r.json),
|
|
160
163
|
driver.describe(
|
|
161
164
|
'spec/v1/host-sample-test-seams.md §20',
|
|
162
165
|
'an identity that does not resolve will not resolve on retry. Marking it retriable invites ' +
|
|
@@ -166,7 +169,7 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
166
169
|
expect(
|
|
167
170
|
['identity_unverified', 'identity_unresolvable', 'audience_mismatch', 'delegation_expired', 'sender_constraint_missing'],
|
|
168
171
|
driver.describe('RFCS/0154 §A', 'failures use a closed reason vocabulary'),
|
|
169
|
-
).toContain(
|
|
172
|
+
).toContain(readErrorCode(r.json));
|
|
170
173
|
});
|
|
171
174
|
|
|
172
175
|
it('a resolution response carries no credential material', async () => {
|