@lumencast/protocol 0.1.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/LICENSE +201 -0
- package/README.md +55 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +162 -0
- package/dist/cli.js.map +1 -0
- package/dist/codec.d.ts +15 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +328 -0
- package/dist/codec.js.map +1 -0
- package/dist/conformance/bundle-hash.d.ts +4 -0
- package/dist/conformance/bundle-hash.d.ts.map +1 -0
- package/dist/conformance/bundle-hash.js +49 -0
- package/dist/conformance/bundle-hash.js.map +1 -0
- package/dist/conformance/control-client.d.ts +42 -0
- package/dist/conformance/control-client.d.ts.map +1 -0
- package/dist/conformance/control-client.js +63 -0
- package/dist/conformance/control-client.js.map +1 -0
- package/dist/conformance/harness.d.ts +41 -0
- package/dist/conformance/harness.d.ts.map +1 -0
- package/dist/conformance/harness.js +441 -0
- package/dist/conformance/harness.js.map +1 -0
- package/dist/conformance/index.d.ts +8 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +12 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/loader.d.ts +9 -0
- package/dist/conformance/loader.d.ts.map +1 -0
- package/dist/conformance/loader.js +27 -0
- package/dist/conformance/loader.js.map +1 -0
- package/dist/conformance/match.d.ts +7 -0
- package/dist/conformance/match.d.ts.map +1 -0
- package/dist/conformance/match.js +82 -0
- package/dist/conformance/match.js.map +1 -0
- package/dist/conformance/placeholders.d.ts +2 -0
- package/dist/conformance/placeholders.d.ts.map +1 -0
- package/dist/conformance/placeholders.js +40 -0
- package/dist/conformance/placeholders.js.map +1 -0
- package/dist/conformance/scenario.d.ts +33 -0
- package/dist/conformance/scenario.d.ts.map +1 -0
- package/dist/conformance/scenario.js +26 -0
- package/dist/conformance/scenario.js.map +1 -0
- package/dist/envelope.d.ts +66 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +111 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/leaf-path.d.ts +21 -0
- package/dist/leaf-path.d.ts.map +1 -0
- package/dist/leaf-path.js +51 -0
- package/dist/leaf-path.js.map +1 -0
- package/dist/sequence.d.ts +25 -0
- package/dist/sequence.d.ts.map +1 -0
- package/dist/sequence.js +51 -0
- package/dist/sequence.js.map +1 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
- package/src/cli.ts +176 -0
- package/src/codec.ts +374 -0
- package/src/conformance/bundle-hash.ts +54 -0
- package/src/conformance/control-client.ts +93 -0
- package/src/conformance/harness.ts +492 -0
- package/src/conformance/index.ts +34 -0
- package/src/conformance/loader.ts +39 -0
- package/src/conformance/match.ts +92 -0
- package/src/conformance/placeholders.ts +45 -0
- package/src/conformance/scenario.ts +71 -0
- package/src/envelope.ts +180 -0
- package/src/errors.ts +55 -0
- package/src/index.ts +63 -0
- package/src/leaf-path.ts +58 -0
- package/src/sequence.ts +60 -0
- package/src/types.ts +201 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// Conformance harness — drives a server through a YAML scenario via the
|
|
2
|
+
// LSDP/1 wire protocol + the HTTP test control plane.
|
|
3
|
+
//
|
|
4
|
+
// One harness per run; each scenario is fully isolated:
|
|
5
|
+
// 1. Hash inline bundles
|
|
6
|
+
// 2. POST /test/setup with tokens + bundles + initial_state
|
|
7
|
+
// 3. Open WebSocket to the URL returned by setup (subprotocol lsdp.v1)
|
|
8
|
+
// 4. Walk steps, sending/expecting frames + control-plane introspection
|
|
9
|
+
// 5. POST /test/reset before the next scenario
|
|
10
|
+
import { WebSocket } from "ws";
|
|
11
|
+
import { WS_SUBPROTOCOL } from "../types.js";
|
|
12
|
+
import { hashInlineBundle } from "./bundle-hash.js";
|
|
13
|
+
import { ControlClient } from "./control-client.js";
|
|
14
|
+
import { matchFrame } from "./match.js";
|
|
15
|
+
import { substitute } from "./placeholders.js";
|
|
16
|
+
const DEFAULT_TOKENS = {
|
|
17
|
+
$TOKEN_OPERATOR: "interop-tok-operator-7f3a",
|
|
18
|
+
$TOKEN_VIEWER: "interop-tok-viewer-7f3a",
|
|
19
|
+
$TOKEN_SERVICE: "interop-tok-service-7f3a",
|
|
20
|
+
$TOKEN_TEST: "interop-tok-test-7f3a",
|
|
21
|
+
$TOKEN_INVALID: "interop-tok-invalid-7f3a",
|
|
22
|
+
};
|
|
23
|
+
export class Harness {
|
|
24
|
+
opts;
|
|
25
|
+
control;
|
|
26
|
+
tokens;
|
|
27
|
+
stepTimeoutMs;
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
this.opts = opts;
|
|
30
|
+
this.control = new ControlClient(opts.controlUrl);
|
|
31
|
+
this.tokens = opts.tokens ?? DEFAULT_TOKENS;
|
|
32
|
+
this.stepTimeoutMs = opts.stepTimeoutMs ?? 2000;
|
|
33
|
+
}
|
|
34
|
+
/** Run a single scenario. */
|
|
35
|
+
async runScenario(scenario) {
|
|
36
|
+
const base = { name: scenario.name, tag: scenario.tag, target: scenario.target };
|
|
37
|
+
// Skip runtime-targeted scenarios — this harness drives a server.
|
|
38
|
+
if (scenario.target === "runtime") {
|
|
39
|
+
return {
|
|
40
|
+
...base,
|
|
41
|
+
outcome: "SKIP",
|
|
42
|
+
reason: "runtime-targeted scenario, harness drives a server",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let bundleHashes = {};
|
|
46
|
+
if (scenario.bundles && scenario.bundles.length > 0) {
|
|
47
|
+
bundleHashes = await this.computeBundleHashes(scenario.bundles);
|
|
48
|
+
}
|
|
49
|
+
// Build the /test/setup payload. The control plane requires at least one
|
|
50
|
+
// bundle; if the scenario doesn't declare any, infer scene_id +
|
|
51
|
+
// scene_version + initial state from the first server-sends snapshot step.
|
|
52
|
+
const setupBundles = buildSetupBundles(scenario, bundleHashes);
|
|
53
|
+
const initialState = setupBundles.initialState;
|
|
54
|
+
let setupResponse;
|
|
55
|
+
try {
|
|
56
|
+
setupResponse = await this.control.setup({
|
|
57
|
+
scenario: scenario.name,
|
|
58
|
+
tokens: this.tokens,
|
|
59
|
+
bundles: setupBundles.bundles,
|
|
60
|
+
initial_state: initialState,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
return { ...base, outcome: "FAIL", reason: `setup: ${err.message}` };
|
|
65
|
+
}
|
|
66
|
+
void setupResponse;
|
|
67
|
+
const wsUrl = this.opts.serverUrl ?? setupResponse.ws_url;
|
|
68
|
+
let ws;
|
|
69
|
+
try {
|
|
70
|
+
ws = await openSocket(wsUrl);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
await this.control.reset().catch(() => undefined);
|
|
74
|
+
return { ...base, outcome: "FAIL", reason: `dial: ${err.message}` };
|
|
75
|
+
}
|
|
76
|
+
const exec = new Exec(ws, this.tokens, bundleHashes, this.control, this.stepTimeoutMs);
|
|
77
|
+
let result = { ...base, outcome: "PASS" };
|
|
78
|
+
for (let i = 0; i < scenario.steps.length; i++) {
|
|
79
|
+
const step = scenario.steps[i];
|
|
80
|
+
try {
|
|
81
|
+
await exec.runStep(step);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
result = {
|
|
85
|
+
...base,
|
|
86
|
+
outcome: "FAIL",
|
|
87
|
+
reason: `step ${i + 1} (${step.kind}): ${err.message}`,
|
|
88
|
+
};
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
ws.close(1000, "scenario done");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
await this.control.reset().catch(() => undefined);
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
/** Run every scenario in `scenarios` matching the tag filter (default required). */
|
|
102
|
+
async runAll(scenarios, tagFilter = "required") {
|
|
103
|
+
const rep = { total: 0, passed: 0, failed: 0, skipped: 0, results: [] };
|
|
104
|
+
for (const sc of scenarios) {
|
|
105
|
+
rep.total++;
|
|
106
|
+
if (sc.tag !== tagFilter) {
|
|
107
|
+
rep.skipped++;
|
|
108
|
+
rep.results.push({
|
|
109
|
+
name: sc.name,
|
|
110
|
+
tag: sc.tag,
|
|
111
|
+
target: sc.target,
|
|
112
|
+
outcome: "SKIP",
|
|
113
|
+
reason: `tag ${sc.tag} != filter ${tagFilter}`,
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const r = await this.runScenario(sc);
|
|
118
|
+
rep.results.push(r);
|
|
119
|
+
if (r.outcome === "PASS")
|
|
120
|
+
rep.passed++;
|
|
121
|
+
else if (r.outcome === "FAIL")
|
|
122
|
+
rep.failed++;
|
|
123
|
+
else
|
|
124
|
+
rep.skipped++;
|
|
125
|
+
}
|
|
126
|
+
return rep;
|
|
127
|
+
}
|
|
128
|
+
async computeBundleHashes(bundles) {
|
|
129
|
+
const out = {};
|
|
130
|
+
for (const b of bundles) {
|
|
131
|
+
const h = await hashInlineBundle(b.inline);
|
|
132
|
+
b.hash = h;
|
|
133
|
+
out[b.id] = h;
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
class Exec {
|
|
139
|
+
ws;
|
|
140
|
+
tokens;
|
|
141
|
+
bundleHashes;
|
|
142
|
+
control;
|
|
143
|
+
stepTimeoutMs;
|
|
144
|
+
shadowState = {};
|
|
145
|
+
inbox = [];
|
|
146
|
+
waiters = [];
|
|
147
|
+
closed = false;
|
|
148
|
+
/** WebSocket close code (RFC 6455). Set on the `close` event ; used
|
|
149
|
+
* by `expect-no-frame-for` to distinguish clean shutdown (1000/1001/
|
|
150
|
+
* 1005) from abnormal closures. */
|
|
151
|
+
closeCode = null;
|
|
152
|
+
constructor(ws, tokens, bundleHashes, control, stepTimeoutMs) {
|
|
153
|
+
this.ws = ws;
|
|
154
|
+
this.tokens = tokens;
|
|
155
|
+
this.bundleHashes = bundleHashes;
|
|
156
|
+
this.control = control;
|
|
157
|
+
this.stepTimeoutMs = stepTimeoutMs;
|
|
158
|
+
// Queue messages so frames that arrive between recv() calls aren't lost.
|
|
159
|
+
this.ws.on("message", (data) => {
|
|
160
|
+
const s = String(data);
|
|
161
|
+
const w = this.waiters.shift();
|
|
162
|
+
if (w)
|
|
163
|
+
w(s);
|
|
164
|
+
else
|
|
165
|
+
this.inbox.push(s);
|
|
166
|
+
});
|
|
167
|
+
this.ws.on("close", (code) => {
|
|
168
|
+
this.closed = true;
|
|
169
|
+
this.closeCode = code;
|
|
170
|
+
// Wake every waiter so they reject promptly.
|
|
171
|
+
while (this.waiters.length > 0)
|
|
172
|
+
this.waiters.shift()(""); // signal close
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async runStep(step) {
|
|
176
|
+
switch (step.kind) {
|
|
177
|
+
case "client-sends":
|
|
178
|
+
return this.send(step.frame ?? {});
|
|
179
|
+
case "server-sends":
|
|
180
|
+
return this.expectServerFrame(step.frame ?? {});
|
|
181
|
+
case "server-emits":
|
|
182
|
+
return this.serverEmits(step.frame ?? {});
|
|
183
|
+
case "expect-runtime-state":
|
|
184
|
+
return this.expectRuntimeState(step.state ?? {});
|
|
185
|
+
case "expect-server-state":
|
|
186
|
+
return this.expectServerState(step.state ?? {});
|
|
187
|
+
case "expect-no-frame-for":
|
|
188
|
+
return this.expectQuiet(step.duration_ms ?? 0);
|
|
189
|
+
case "expect-client-action":
|
|
190
|
+
return this.expectClientAction(step.action, step.reason);
|
|
191
|
+
default:
|
|
192
|
+
throw new Error(`unsupported step kind ${step.kind}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* `server-emits` (SCENARIO-FORMAT.md) — harness orchestrates a
|
|
197
|
+
* server-driven frame via the test control plane, then validates
|
|
198
|
+
* the wire form. Only `frame.type === "delta"` is currently
|
|
199
|
+
* supported (POST /test/emit).
|
|
200
|
+
*/
|
|
201
|
+
async serverEmits(expected) {
|
|
202
|
+
if (expected["type"] !== "delta" || !Array.isArray(expected["patches"])) {
|
|
203
|
+
throw new Error(`server-emits only supports type=delta today, got ${String(expected["type"])}`);
|
|
204
|
+
}
|
|
205
|
+
const patches = expected["patches"];
|
|
206
|
+
const resolved = patches.map((p) => ({
|
|
207
|
+
path: p.path,
|
|
208
|
+
value: substitute(p.value, this.tokens, this.bundleHashes),
|
|
209
|
+
}));
|
|
210
|
+
await this.control.emit(resolved);
|
|
211
|
+
return this.expectServerFrame(expected);
|
|
212
|
+
}
|
|
213
|
+
send(frame) {
|
|
214
|
+
const resolved = substitute(frame, this.tokens, this.bundleHashes);
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
this.ws.send(JSON.stringify(resolved), (err) => {
|
|
217
|
+
if (err)
|
|
218
|
+
reject(err);
|
|
219
|
+
else
|
|
220
|
+
resolve();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async expectServerFrame(expected) {
|
|
225
|
+
// Try to read what's already in flight first (snapshots, error frames, and
|
|
226
|
+
// operator-input echoes arrive without prompting). Only fall back to
|
|
227
|
+
// /test/emit when nothing arrives within a short window AND the expected
|
|
228
|
+
// frame is a delta — that's the case where the server needs nudging.
|
|
229
|
+
const want = substitute(expected, this.tokens, this.bundleHashes);
|
|
230
|
+
let actual = null;
|
|
231
|
+
try {
|
|
232
|
+
const raw = await this.recv(150);
|
|
233
|
+
actual = JSON.parse(raw);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// No frame yet — if the expected is a delta, drive it via /test/emit.
|
|
237
|
+
if (expected["type"] === "delta" && Array.isArray(expected["patches"])) {
|
|
238
|
+
const patches = expected["patches"];
|
|
239
|
+
const resolved = patches.map((p) => ({
|
|
240
|
+
path: p.path,
|
|
241
|
+
value: substitute(p.value, this.tokens, this.bundleHashes),
|
|
242
|
+
}));
|
|
243
|
+
try {
|
|
244
|
+
await this.control.emit(resolved);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Swallow — the matcher will report any actual mismatch.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const raw = await this.recv(this.stepTimeoutMs);
|
|
251
|
+
actual = JSON.parse(raw);
|
|
252
|
+
}
|
|
253
|
+
const err = matchFrame(want, actual);
|
|
254
|
+
if (err) {
|
|
255
|
+
throw new Error(`frame mismatch at ${err.path}: ${err.reason} (got ${JSON.stringify(actual)})`);
|
|
256
|
+
}
|
|
257
|
+
this.absorb(actual);
|
|
258
|
+
}
|
|
259
|
+
absorb(frame) {
|
|
260
|
+
if (frame["type"] === "snapshot") {
|
|
261
|
+
const state = frame["state"] ?? {};
|
|
262
|
+
this.shadowState = { ...state };
|
|
263
|
+
}
|
|
264
|
+
else if (frame["type"] === "delta") {
|
|
265
|
+
const patches = frame["patches"] ?? [];
|
|
266
|
+
for (const p of patches)
|
|
267
|
+
this.shadowState[p.path] = p.value;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
expectRuntimeState(want) {
|
|
271
|
+
for (const [k, v] of Object.entries(want)) {
|
|
272
|
+
if (!(k in this.shadowState)) {
|
|
273
|
+
throw new Error(`runtime state missing ${k}`);
|
|
274
|
+
}
|
|
275
|
+
const got = this.shadowState[k];
|
|
276
|
+
if (JSON.stringify(got) !== JSON.stringify(v)) {
|
|
277
|
+
throw new Error(`runtime state ${k}: want ${JSON.stringify(v)}, got ${JSON.stringify(got)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async expectServerState(want) {
|
|
282
|
+
const snap = await this.control.state();
|
|
283
|
+
for (const [k, v] of Object.entries(want)) {
|
|
284
|
+
if (!(k in snap.state)) {
|
|
285
|
+
throw new Error(`server state missing ${k}`);
|
|
286
|
+
}
|
|
287
|
+
const got = snap.state[k];
|
|
288
|
+
if (JSON.stringify(got) !== JSON.stringify(v)) {
|
|
289
|
+
throw new Error(`server state ${k}: want ${JSON.stringify(v)}, got ${JSON.stringify(got)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async expectQuiet(durationMs) {
|
|
294
|
+
try {
|
|
295
|
+
const raw = await this.recv(durationMs);
|
|
296
|
+
throw new Error(`expected silence for ${durationMs}ms, got frame: ${raw}`);
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
const msg = err.message;
|
|
300
|
+
if (msg === "recv timeout")
|
|
301
|
+
return;
|
|
302
|
+
// SCENARIO-FORMAT.md `expect-no-frame-for` § Connection-close
|
|
303
|
+
// semantics : clean WS close (1000/1001/1005) within the duration
|
|
304
|
+
// counts as success — no data flowed. Abnormal closures still
|
|
305
|
+
// surface as errors.
|
|
306
|
+
if (msg === "connection closed") {
|
|
307
|
+
const code = this.closeCode;
|
|
308
|
+
if (code === 1000 || code === 1001 || code === 1005)
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async expectClientAction(action, _reason) {
|
|
315
|
+
if (!action)
|
|
316
|
+
throw new Error("expect-client-action: action required");
|
|
317
|
+
if (action === "close-with-reason" || action === "reconnect") {
|
|
318
|
+
// Read should reject (connection closed or no further frames).
|
|
319
|
+
try {
|
|
320
|
+
await this.recv(this.stepTimeoutMs);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
if (err.message === "recv timeout")
|
|
324
|
+
return;
|
|
325
|
+
// close → recv rejects, that's the success path.
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
throw new Error(`expected connection close or no frame, got data`);
|
|
329
|
+
}
|
|
330
|
+
throw new Error(`unknown client action ${action}`);
|
|
331
|
+
}
|
|
332
|
+
recv(timeoutMs) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
const queued = this.inbox.shift();
|
|
335
|
+
if (queued !== undefined) {
|
|
336
|
+
resolve(queued);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (this.closed) {
|
|
340
|
+
reject(new Error("connection closed"));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const t = setTimeout(() => {
|
|
344
|
+
const i = this.waiters.indexOf(resolveAndDispatch);
|
|
345
|
+
if (i >= 0)
|
|
346
|
+
this.waiters.splice(i, 1);
|
|
347
|
+
reject(new Error("recv timeout"));
|
|
348
|
+
}, timeoutMs);
|
|
349
|
+
const resolveAndDispatch = (v) => {
|
|
350
|
+
clearTimeout(t);
|
|
351
|
+
if (v === "" && this.closed)
|
|
352
|
+
reject(new Error("connection closed"));
|
|
353
|
+
else
|
|
354
|
+
resolve(v);
|
|
355
|
+
};
|
|
356
|
+
this.waiters.push(resolveAndDispatch);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function openSocket(url) {
|
|
361
|
+
return new Promise((resolve, reject) => {
|
|
362
|
+
const ws = new WebSocket(url, [WS_SUBPROTOCOL]);
|
|
363
|
+
ws.once("open", () => resolve(ws));
|
|
364
|
+
ws.once("error", reject);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function extractInitialState(bundles) {
|
|
368
|
+
// The Go reference reads `initial_state` from the scenario top-level when
|
|
369
|
+
// present. Our scenarios encode it inside each bundle's `defaults` field
|
|
370
|
+
// (LSML 1.0 §10). Pull from the first bundle's defaults if any.
|
|
371
|
+
const primary = bundles[0];
|
|
372
|
+
if (primary && typeof primary.inline === "object" && primary.inline !== null) {
|
|
373
|
+
const defaults = primary.inline.defaults;
|
|
374
|
+
if (defaults && typeof defaults === "object")
|
|
375
|
+
return defaults;
|
|
376
|
+
}
|
|
377
|
+
return {};
|
|
378
|
+
}
|
|
379
|
+
/** Build the bundles + initial_state payload for /test/setup.
|
|
380
|
+
* When the scenario declares bundles, use them verbatim. Otherwise, infer
|
|
381
|
+
* scene_id + scene_version + initial state from the first server-sends
|
|
382
|
+
* snapshot step in the scenario — the same shape the harness will later
|
|
383
|
+
* expect on the wire. */
|
|
384
|
+
function buildSetupBundles(scenario, bundleHashes) {
|
|
385
|
+
if (scenario.bundles && scenario.bundles.length > 0) {
|
|
386
|
+
return {
|
|
387
|
+
bundles: scenario.bundles.map((b) => ({
|
|
388
|
+
id: b.id,
|
|
389
|
+
hash: bundleHashes[b.id] ?? "",
|
|
390
|
+
inline: b.inline,
|
|
391
|
+
})),
|
|
392
|
+
initialState: extractInitialState(scenario.bundles),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
// Look for the first `server-sends snapshot` step.
|
|
396
|
+
let sceneId = "t";
|
|
397
|
+
let sceneVersion = "sha256:" + "f".repeat(64);
|
|
398
|
+
let initialState = {};
|
|
399
|
+
for (const step of scenario.steps) {
|
|
400
|
+
if (step.kind !== "server-sends" || !step.frame || step.frame["type"] !== "snapshot") {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const snap = step.frame;
|
|
404
|
+
if (typeof snap["scene_id"] === "string" && snap["scene_id"] !== "$ANY") {
|
|
405
|
+
sceneId = snap["scene_id"];
|
|
406
|
+
}
|
|
407
|
+
if (typeof snap["scene_version"] === "string" && snap["scene_version"] !== "$ANY_HASH") {
|
|
408
|
+
sceneVersion = snap["scene_version"];
|
|
409
|
+
}
|
|
410
|
+
if (snap["state"] && typeof snap["state"] === "object") {
|
|
411
|
+
initialState = snap["state"];
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
// Synthesize an `operator_inputs` schema declaring every __inputs.* path
|
|
416
|
+
// present in the initial state. This makes the server reject UNKNOWN_PATH
|
|
417
|
+
// for inputs on undeclared paths, mirroring real-server semantics.
|
|
418
|
+
const operatorInputs = Object.keys(initialState)
|
|
419
|
+
.filter((p) => p.startsWith("__inputs."))
|
|
420
|
+
.map((p) => ({
|
|
421
|
+
path: p,
|
|
422
|
+
label: p,
|
|
423
|
+
type: typeof initialState[p] === "number" ? "number" : "string",
|
|
424
|
+
writable_by: ["operator"],
|
|
425
|
+
}));
|
|
426
|
+
return {
|
|
427
|
+
bundles: [
|
|
428
|
+
{
|
|
429
|
+
id: sceneId,
|
|
430
|
+
hash: sceneVersion,
|
|
431
|
+
inline: {
|
|
432
|
+
scene_id: sceneId,
|
|
433
|
+
synthetic: true,
|
|
434
|
+
...(operatorInputs.length > 0 ? { operator_inputs: operatorInputs } : {}),
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
initialState,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/conformance/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { parseScenario, type Scenario, type Step, type StepKind, type Tag, type Target, type ClientAction, type BundleDecl, } from "./scenario.js";
|
|
2
|
+
export { loadScenarios, type LoadOptions } from "./loader.js";
|
|
3
|
+
export { matchFrame, matchValue, type MatchError } from "./match.js";
|
|
4
|
+
export { substitute } from "./placeholders.js";
|
|
5
|
+
export { canonicalize, hashInlineBundle } from "./bundle-hash.js";
|
|
6
|
+
export { ControlClient, type SetupRequest, type SetupResponse, type StateResponse, type HealthResponse, } from "./control-client.js";
|
|
7
|
+
export { Harness, type HarnessOptions, type Outcome, type ScenarioResult, type Report, } from "./harness.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conformance/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,QAAQ,EACb,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EACL,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,MAAM,GACZ,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public surface of @lumencast/protocol/conformance.
|
|
2
|
+
//
|
|
3
|
+
// Loaders + harness for the LSDP/1 conformance scenarios. Used by the
|
|
4
|
+
// `lumencast-js conformance` CLI and by interop matrix runs.
|
|
5
|
+
export { parseScenario, } from "./scenario.js";
|
|
6
|
+
export { loadScenarios } from "./loader.js";
|
|
7
|
+
export { matchFrame, matchValue } from "./match.js";
|
|
8
|
+
export { substitute } from "./placeholders.js";
|
|
9
|
+
export { canonicalize, hashInlineBundle } from "./bundle-hash.js";
|
|
10
|
+
export { ControlClient, } from "./control-client.js";
|
|
11
|
+
export { Harness, } from "./harness.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conformance/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,EAAE;AACF,sEAAsE;AACtE,6DAA6D;AAE7D,OAAO,EACL,aAAa,GAQd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,aAAa,EAAoB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EACL,aAAa,GAKd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,OAAO,GAKR,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Scenario } from "./scenario.js";
|
|
2
|
+
export interface LoadOptions {
|
|
3
|
+
/** Directory containing the *.yaml scenarios. Required. */
|
|
4
|
+
scenariosDir: string;
|
|
5
|
+
/** When set, only load this single scenario (basename without .yaml). */
|
|
6
|
+
scenarioName?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function loadScenarios(opts: LoadOptions): Scenario[];
|
|
9
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/conformance/loader.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,EAAE,CAwB3D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Filesystem loader for scenarios. Reads YAML files from disk by path. The
|
|
2
|
+
// CLI uses this to find scenarios under `lumencast-protocol/conformance/v1/scenarios/`.
|
|
3
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { parseScenario } from "./scenario.js";
|
|
6
|
+
export function loadScenarios(opts) {
|
|
7
|
+
const dir = resolve(opts.scenariosDir);
|
|
8
|
+
const stat = statSync(dir, { throwIfNoEntry: false });
|
|
9
|
+
if (!stat || !stat.isDirectory()) {
|
|
10
|
+
throw new Error(`scenarios directory not found: ${dir}`);
|
|
11
|
+
}
|
|
12
|
+
let files = readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
13
|
+
if (opts.scenarioName) {
|
|
14
|
+
files = files.filter((f) => f === `${opts.scenarioName}.yaml` || f === `${opts.scenarioName}.yml`);
|
|
15
|
+
if (files.length === 0) {
|
|
16
|
+
throw new Error(`scenario not found in ${dir}: ${opts.scenarioName}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
files.sort();
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const f of files) {
|
|
22
|
+
const raw = readFileSync(join(dir, f), "utf8");
|
|
23
|
+
out.push(parseScenario(raw));
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/conformance/loader.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wFAAwF;AAExF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAiB,MAAM,eAAe,CAAC;AAS7D,MAAM,UAAU,aAAa,CAAC,IAAiB;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACtF,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,KAAK,GAAG,KAAK,CAAC,MAAM,CAClB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,MAAM,CAC7E,CAAC;QACF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/C,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface MatchError {
|
|
2
|
+
path: string;
|
|
3
|
+
reason: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function matchFrame(expected: Record<string, unknown>, actual: Record<string, unknown>): MatchError | null;
|
|
6
|
+
export declare function matchValue(expected: unknown, actual: unknown, path: string): MatchError | null;
|
|
7
|
+
//# sourceMappingURL=match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/conformance/match.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,UAAU,GAAG,IAAI,CASnB;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAyC9F"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Frame matcher with $ANY / $ANY_HASH sentinels. Mirrors
|
|
2
|
+
// lumencast-go/conformance/match.go.
|
|
3
|
+
//
|
|
4
|
+
// matchValue rules :
|
|
5
|
+
// - "$ANY" → matches any present value
|
|
6
|
+
// - "$ANY_HASH" → matches a sha256:<hex64> string
|
|
7
|
+
// - object → recurse on each key in the expected template; extra fields in
|
|
8
|
+
// the actual object are tolerated (forward-compat)
|
|
9
|
+
// - array → length and element-wise match
|
|
10
|
+
// - scalar → strict equality (numeric tower normalized)
|
|
11
|
+
const SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
12
|
+
export function matchFrame(expected, actual) {
|
|
13
|
+
for (const [k, want] of Object.entries(expected)) {
|
|
14
|
+
if (!(k in actual)) {
|
|
15
|
+
return { path: k, reason: `missing field` };
|
|
16
|
+
}
|
|
17
|
+
const err = matchValue(want, actual[k], k);
|
|
18
|
+
if (err)
|
|
19
|
+
return err;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export function matchValue(expected, actual, path) {
|
|
24
|
+
if (typeof expected === "string") {
|
|
25
|
+
if (expected === "$ANY")
|
|
26
|
+
return null;
|
|
27
|
+
if (expected === "$ANY_HASH") {
|
|
28
|
+
if (typeof actual !== "string" || !SHA256_RE.test(actual)) {
|
|
29
|
+
return { path, reason: `not a sha256 hash: ${JSON.stringify(actual)}` };
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(expected)) {
|
|
35
|
+
if (!Array.isArray(actual)) {
|
|
36
|
+
return { path, reason: `want array, got ${typeOf(actual)}` };
|
|
37
|
+
}
|
|
38
|
+
if (expected.length !== actual.length) {
|
|
39
|
+
return { path, reason: `length ${expected.length} != ${actual.length}` };
|
|
40
|
+
}
|
|
41
|
+
for (let i = 0; i < expected.length; i++) {
|
|
42
|
+
const err = matchValue(expected[i], actual[i], `${path}[${i}]`);
|
|
43
|
+
if (err)
|
|
44
|
+
return err;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof expected === "object" && expected !== null) {
|
|
49
|
+
if (typeof actual !== "object" || actual === null || Array.isArray(actual)) {
|
|
50
|
+
return { path, reason: `want object, got ${typeOf(actual)}` };
|
|
51
|
+
}
|
|
52
|
+
return matchFrame(expected, actual)
|
|
53
|
+
? prefix(path, matchFrame(expected, actual))
|
|
54
|
+
: null;
|
|
55
|
+
}
|
|
56
|
+
if (!equalScalar(expected, actual)) {
|
|
57
|
+
return {
|
|
58
|
+
path,
|
|
59
|
+
reason: `want ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function prefix(p, err) {
|
|
65
|
+
return { path: `${p}.${err.path}`, reason: err.reason };
|
|
66
|
+
}
|
|
67
|
+
function equalScalar(a, b) {
|
|
68
|
+
if (a === b)
|
|
69
|
+
return true;
|
|
70
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
71
|
+
return a === b || (Number.isNaN(a) && Number.isNaN(b));
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function typeOf(v) {
|
|
76
|
+
if (v === null)
|
|
77
|
+
return "null";
|
|
78
|
+
if (Array.isArray(v))
|
|
79
|
+
return "array";
|
|
80
|
+
return typeof v;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/conformance/match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/conformance/placeholders.ts"],"names":[],"mappings":"AAgBA,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAwBT"}
|