@hraness/direct 0.7.5
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 +21 -0
- package/README.md +436 -0
- package/dist/core/index.js +162 -0
- package/dist/index-1csg00w4.js +1167 -0
- package/dist/index-6mdfd2ey.js +464 -0
- package/dist/index-7n1h75n6.js +616 -0
- package/dist/index.js +232 -0
- package/dist/react.js +32 -0
- package/dist/testing/index.js +1069 -0
- package/dist/tooling/bombadil.js +2117 -0
- package/dist/tooling/browser-verification-entry.js +1499 -0
- package/dist/tooling/bundle-boundary.js +119 -0
- package/dist/web.js +605 -0
- package/package.json +179 -0
- package/skills/direct/AGENTS.md +13 -0
- package/skills/direct/SKILL.md +49 -0
- package/skills/direct/agents/openai.yaml +4 -0
- package/skills/direct/references/adoption.md +131 -0
- package/skills/direct/references/install.md +91 -0
- package/skills/direct/references/verification.md +247 -0
- package/src/core/coverage.ts +336 -0
- package/src/core/definition.ts +378 -0
- package/src/core/effects.ts +88 -0
- package/src/core/fixture.ts +185 -0
- package/src/core/ids.ts +77 -0
- package/src/core/index.ts +13 -0
- package/src/core/json-value.ts +7 -0
- package/src/core/json.ts +593 -0
- package/src/core/query.ts +230 -0
- package/src/core/reason.ts +16 -0
- package/src/core/resource.ts +10 -0
- package/src/core/result.ts +19 -0
- package/src/core/runtime.ts +229 -0
- package/src/core/scenario.ts +149 -0
- package/src/core/store.ts +784 -0
- package/src/index.ts +51 -0
- package/src/react.ts +54 -0
- package/src/testing/activity.ts +228 -0
- package/src/testing/coverage-binding.ts +99 -0
- package/src/testing/evidence.ts +59 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/manifest.ts +559 -0
- package/src/testing/probe.ts +446 -0
- package/src/testing/scripted-transport.ts +775 -0
- package/src/testing/session.ts +525 -0
- package/src/tooling/bombadil-campaign.ts +288 -0
- package/src/tooling/bombadil-internal.d.ts +46 -0
- package/src/tooling/bombadil-runner.ts +1424 -0
- package/src/tooling/bombadil.ts +27 -0
- package/src/tooling/browser-verification-entry.ts +32 -0
- package/src/tooling/browser-verification.ts +916 -0
- package/src/tooling/bundle-boundary.ts +159 -0
- package/src/web/browser-bridge.ts +296 -0
- package/src/web/browser.ts +277 -0
- package/src/web/fetch-firewall.ts +251 -0
- package/src/web.ts +27 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
// src/core/result.ts
|
|
2
|
+
function ok(value) {
|
|
3
|
+
return { ok: true, value };
|
|
4
|
+
}
|
|
5
|
+
function err(error) {
|
|
6
|
+
return { ok: false, error };
|
|
7
|
+
}
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/core/ids.ts
|
|
13
|
+
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9]*(?:[._/-][a-z0-9]+)*$/u;
|
|
14
|
+
var MAX_IDENTIFIER_LENGTH = 120;
|
|
15
|
+
function parseIdentifier(input, kind) {
|
|
16
|
+
if (typeof input !== "string" || input.length === 0 || input.length > MAX_IDENTIFIER_LENGTH || !IDENTIFIER_PATTERN.test(input)) {
|
|
17
|
+
return err({
|
|
18
|
+
code: "invalid-identifier",
|
|
19
|
+
kind,
|
|
20
|
+
value: input,
|
|
21
|
+
message: `${kind} identifiers must be 1-${MAX_IDENTIFIER_LENGTH} lowercase ASCII characters with separated alphanumeric segments`
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return ok(input);
|
|
25
|
+
}
|
|
26
|
+
function parseScenarioId(input) {
|
|
27
|
+
const parsed = parseIdentifier(input, "scenario");
|
|
28
|
+
return parsed.ok ? ok(parsed.value) : parsed;
|
|
29
|
+
}
|
|
30
|
+
function parseOperationId(input) {
|
|
31
|
+
const parsed = parseIdentifier(input, "operation");
|
|
32
|
+
return parsed.ok ? ok(parsed.value) : parsed;
|
|
33
|
+
}
|
|
34
|
+
function parseCoverageKey(input) {
|
|
35
|
+
const parsed = parseIdentifier(input, "coverage");
|
|
36
|
+
return parsed.ok ? ok(parsed.value) : parsed;
|
|
37
|
+
}
|
|
38
|
+
function scenarioId(input) {
|
|
39
|
+
const parsed = parseScenarioId(input);
|
|
40
|
+
if (!parsed.ok) {
|
|
41
|
+
throw new Error(parsed.error.message);
|
|
42
|
+
}
|
|
43
|
+
return parsed.value;
|
|
44
|
+
}
|
|
45
|
+
function operationId(input) {
|
|
46
|
+
const parsed = parseOperationId(input);
|
|
47
|
+
if (!parsed.ok) {
|
|
48
|
+
throw new Error(parsed.error.message);
|
|
49
|
+
}
|
|
50
|
+
return parsed.value;
|
|
51
|
+
}
|
|
52
|
+
function coverageKey(input) {
|
|
53
|
+
const parsed = parseCoverageKey(input);
|
|
54
|
+
if (!parsed.ok) {
|
|
55
|
+
throw new Error(parsed.error.message);
|
|
56
|
+
}
|
|
57
|
+
return parsed.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/core/reason.ts
|
|
61
|
+
function renderUnknownReason(reason, fallback = "Unknown failure") {
|
|
62
|
+
try {
|
|
63
|
+
if (typeof reason === "object" && reason !== null || typeof reason === "function") {
|
|
64
|
+
const message = Reflect.get(reason, "message");
|
|
65
|
+
if (typeof message === "string")
|
|
66
|
+
return message;
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
try {
|
|
70
|
+
return String(reason);
|
|
71
|
+
} catch {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/core/json.ts
|
|
77
|
+
var DEFAULT_JSON_LIMITS = Object.freeze({
|
|
78
|
+
maxDepth: 64,
|
|
79
|
+
maxNodes: 1e5,
|
|
80
|
+
maxStringBytes: 1048576
|
|
81
|
+
});
|
|
82
|
+
var PARSED_JSON_OPTIONS = Object.freeze({
|
|
83
|
+
freeze: false,
|
|
84
|
+
normalizeNegativeZero: false,
|
|
85
|
+
objectPrototype: "null",
|
|
86
|
+
sortObjectKeys: false
|
|
87
|
+
});
|
|
88
|
+
var CLONED_JSON_OPTIONS = Object.freeze({
|
|
89
|
+
freeze: false,
|
|
90
|
+
normalizeNegativeZero: true,
|
|
91
|
+
objectPrototype: "ordinary",
|
|
92
|
+
sortObjectKeys: true
|
|
93
|
+
});
|
|
94
|
+
var FROZEN_CLONED_JSON_OPTIONS = Object.freeze({
|
|
95
|
+
freeze: true,
|
|
96
|
+
normalizeNegativeZero: true,
|
|
97
|
+
objectPrototype: "ordinary",
|
|
98
|
+
sortObjectKeys: true
|
|
99
|
+
});
|
|
100
|
+
function jsonError(code, path, message) {
|
|
101
|
+
return { code, path, message };
|
|
102
|
+
}
|
|
103
|
+
function exactJsonSourceError(code, path, message) {
|
|
104
|
+
return { code, path, message };
|
|
105
|
+
}
|
|
106
|
+
function utf8ByteLength(value) {
|
|
107
|
+
let bytes = 0;
|
|
108
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
109
|
+
const code = value.charCodeAt(index);
|
|
110
|
+
if (code <= 127) {
|
|
111
|
+
bytes += 1;
|
|
112
|
+
} else if (code <= 2047) {
|
|
113
|
+
bytes += 2;
|
|
114
|
+
} else if (code >= 55296 && code <= 56319 && index + 1 < value.length) {
|
|
115
|
+
const next = value.charCodeAt(index + 1);
|
|
116
|
+
if (next >= 56320 && next <= 57343) {
|
|
117
|
+
bytes += 4;
|
|
118
|
+
index += 1;
|
|
119
|
+
} else {
|
|
120
|
+
bytes += 3;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
bytes += 3;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return bytes;
|
|
127
|
+
}
|
|
128
|
+
function childJsonPath(path, key) {
|
|
129
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
|
|
130
|
+
}
|
|
131
|
+
function findDuplicateJsonKey(source) {
|
|
132
|
+
let index = 0;
|
|
133
|
+
let duplicate = null;
|
|
134
|
+
const skipWhitespace = () => {
|
|
135
|
+
while (source[index] === " " || source[index] === `
|
|
136
|
+
` || source[index] === "\r" || source[index] === "\t") {
|
|
137
|
+
index += 1;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const readString = () => {
|
|
141
|
+
const start = index;
|
|
142
|
+
index += 1;
|
|
143
|
+
while (index < source.length) {
|
|
144
|
+
const character = source[index];
|
|
145
|
+
if (character === "\\") {
|
|
146
|
+
index += 2;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
index += 1;
|
|
150
|
+
if (character === '"') {
|
|
151
|
+
return JSON.parse(source.slice(start, index));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new Error("Unterminated JSON string");
|
|
155
|
+
};
|
|
156
|
+
const scanValue = (path) => {
|
|
157
|
+
skipWhitespace();
|
|
158
|
+
const character = source[index];
|
|
159
|
+
if (character === "{") {
|
|
160
|
+
index += 1;
|
|
161
|
+
skipWhitespace();
|
|
162
|
+
if (source[index] === "}") {
|
|
163
|
+
index += 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const keys = new Set;
|
|
167
|
+
while (index < source.length) {
|
|
168
|
+
skipWhitespace();
|
|
169
|
+
const key = readString();
|
|
170
|
+
const keyPath = childJsonPath(path, key);
|
|
171
|
+
if (keys.has(key) && duplicate === null) {
|
|
172
|
+
duplicate = { key, path: keyPath };
|
|
173
|
+
}
|
|
174
|
+
keys.add(key);
|
|
175
|
+
skipWhitespace();
|
|
176
|
+
index += 1;
|
|
177
|
+
scanValue(keyPath);
|
|
178
|
+
skipWhitespace();
|
|
179
|
+
if (source[index] === "}") {
|
|
180
|
+
index += 1;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
index += 1;
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (character === "[") {
|
|
188
|
+
index += 1;
|
|
189
|
+
skipWhitespace();
|
|
190
|
+
if (source[index] === "]") {
|
|
191
|
+
index += 1;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
let itemIndex = 0;
|
|
195
|
+
while (index < source.length) {
|
|
196
|
+
scanValue(`${path}[${String(itemIndex)}]`);
|
|
197
|
+
itemIndex += 1;
|
|
198
|
+
skipWhitespace();
|
|
199
|
+
if (source[index] === "]") {
|
|
200
|
+
index += 1;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
index += 1;
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (character === '"') {
|
|
208
|
+
readString();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
while (index < source.length) {
|
|
212
|
+
const next = source[index];
|
|
213
|
+
if (next === "," || next === "]" || next === "}" || /\s/u.test(next ?? ""))
|
|
214
|
+
return;
|
|
215
|
+
index += 1;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
skipWhitespace();
|
|
219
|
+
scanValue("$");
|
|
220
|
+
return duplicate;
|
|
221
|
+
}
|
|
222
|
+
function parseExactJsonSource(source) {
|
|
223
|
+
if (typeof source !== "string") {
|
|
224
|
+
return err(exactJsonSourceError("invalid-json", "$", "JSON source must be a string"));
|
|
225
|
+
}
|
|
226
|
+
let parsed;
|
|
227
|
+
try {
|
|
228
|
+
parsed = JSON.parse(source);
|
|
229
|
+
} catch {
|
|
230
|
+
return err(exactJsonSourceError("invalid-json", "$", "Source is not valid JSON"));
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const duplicate = findDuplicateJsonKey(source);
|
|
234
|
+
return duplicate === null ? ok(parsed) : err(exactJsonSourceError("duplicate-key", duplicate.path, `Duplicate JSON object key at ${duplicate.path}: ${duplicate.key}`));
|
|
235
|
+
} catch (reason) {
|
|
236
|
+
return err(exactJsonSourceError("invalid-json", "$", renderUnknownReason(reason, "JSON source inspection failed")));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function parseJsonAt(input, path, depth, limits, budget, ancestors, options) {
|
|
240
|
+
budget.nodes += 1;
|
|
241
|
+
if (budget.nodes > limits.maxNodes) {
|
|
242
|
+
return err(jsonError("node-limit-exceeded", path, `JSON value exceeds ${limits.maxNodes} nodes`));
|
|
243
|
+
}
|
|
244
|
+
if (depth > limits.maxDepth) {
|
|
245
|
+
return err(jsonError("depth-exceeded", path, `JSON value exceeds depth ${limits.maxDepth}`));
|
|
246
|
+
}
|
|
247
|
+
if (input === null || typeof input === "boolean") {
|
|
248
|
+
return ok(input);
|
|
249
|
+
}
|
|
250
|
+
if (typeof input === "string") {
|
|
251
|
+
budget.stringBytes += utf8ByteLength(input);
|
|
252
|
+
if (budget.stringBytes > limits.maxStringBytes) {
|
|
253
|
+
return err(jsonError("string-limit-exceeded", path, `JSON strings exceed ${limits.maxStringBytes} UTF-8 bytes`));
|
|
254
|
+
}
|
|
255
|
+
return ok(input);
|
|
256
|
+
}
|
|
257
|
+
if (typeof input === "number") {
|
|
258
|
+
return Number.isFinite(input) ? ok(options.normalizeNegativeZero && Object.is(input, -0) ? 0 : input) : err(jsonError("invalid-number", path, "JSON numbers must be finite"));
|
|
259
|
+
}
|
|
260
|
+
if (typeof input !== "object") {
|
|
261
|
+
return err(jsonError("invalid-type", path, `${typeof input} is not a JSON value`));
|
|
262
|
+
}
|
|
263
|
+
if (ancestors.has(input)) {
|
|
264
|
+
return err(jsonError("cycle", path, "JSON values cannot contain cycles"));
|
|
265
|
+
}
|
|
266
|
+
const nextAncestors = new Set(ancestors);
|
|
267
|
+
nextAncestors.add(input);
|
|
268
|
+
if (Array.isArray(input)) {
|
|
269
|
+
if (Object.getPrototypeOf(input) !== Array.prototype) {
|
|
270
|
+
return err(jsonError("invalid-object", path, "JSON arrays must have the standard Array prototype"));
|
|
271
|
+
}
|
|
272
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(input, "length");
|
|
273
|
+
if (lengthDescriptor === undefined || lengthDescriptor.get !== undefined || lengthDescriptor.set !== undefined || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) {
|
|
274
|
+
return err(jsonError("invalid-object", path, "JSON arrays must have a valid data length"));
|
|
275
|
+
}
|
|
276
|
+
const length = lengthDescriptor.value;
|
|
277
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
278
|
+
if (typeof key === "symbol") {
|
|
279
|
+
return err(jsonError("symbol-key", path, "JSON arrays cannot have symbol keys"));
|
|
280
|
+
}
|
|
281
|
+
if (key === "length")
|
|
282
|
+
continue;
|
|
283
|
+
const index = Number(key);
|
|
284
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= length || String(index) !== key) {
|
|
285
|
+
return err(jsonError("invalid-object", `${path}.${key}`, "JSON arrays cannot have extra properties"));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const output2 = [];
|
|
289
|
+
for (let index = 0;index < length; index += 1) {
|
|
290
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, index);
|
|
291
|
+
if (descriptor === undefined) {
|
|
292
|
+
return err(jsonError("invalid-object", `${path}[${index}]`, "Sparse arrays are not exact JSON values"));
|
|
293
|
+
}
|
|
294
|
+
if (descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
295
|
+
return err(jsonError("accessor-property", `${path}[${index}]`, "JSON arrays must use data elements"));
|
|
296
|
+
}
|
|
297
|
+
if (!descriptor.enumerable) {
|
|
298
|
+
return err(jsonError("invalid-object", `${path}[${index}]`, "JSON array elements must be enumerable"));
|
|
299
|
+
}
|
|
300
|
+
const item = parseJsonAt(descriptor.value, `${path}[${index}]`, depth + 1, limits, budget, nextAncestors, options);
|
|
301
|
+
if (!item.ok) {
|
|
302
|
+
return item;
|
|
303
|
+
}
|
|
304
|
+
output2.push(item.value);
|
|
305
|
+
}
|
|
306
|
+
return ok(options.freeze ? Object.freeze(output2) : output2);
|
|
307
|
+
}
|
|
308
|
+
const prototype = Object.getPrototypeOf(input);
|
|
309
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
310
|
+
return err(jsonError("invalid-object", path, "JSON objects must have Object or null prototypes"));
|
|
311
|
+
}
|
|
312
|
+
const output = options.objectPrototype === "ordinary" ? {} : Object.create(null);
|
|
313
|
+
const entries = options.sortObjectKeys ? [] : null;
|
|
314
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
315
|
+
if (typeof key === "symbol") {
|
|
316
|
+
return err(jsonError("symbol-key", path, "JSON objects cannot have symbol keys"));
|
|
317
|
+
}
|
|
318
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
319
|
+
if (descriptor === undefined || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
320
|
+
return err(jsonError("accessor-property", `${path}.${key}`, "JSON objects must use data properties"));
|
|
321
|
+
}
|
|
322
|
+
if (!descriptor.enumerable) {
|
|
323
|
+
return err(jsonError("invalid-object", `${path}.${key}`, "JSON object properties must be enumerable"));
|
|
324
|
+
}
|
|
325
|
+
budget.stringBytes += utf8ByteLength(key);
|
|
326
|
+
if (budget.stringBytes > limits.maxStringBytes) {
|
|
327
|
+
return err(jsonError("string-limit-exceeded", `${path}.${key}`, `JSON strings exceed ${limits.maxStringBytes} UTF-8 bytes`));
|
|
328
|
+
}
|
|
329
|
+
const child = parseJsonAt(descriptor.value, `${path}.${key}`, depth + 1, limits, budget, nextAncestors, options);
|
|
330
|
+
if (!child.ok) {
|
|
331
|
+
return child;
|
|
332
|
+
}
|
|
333
|
+
if (entries === null) {
|
|
334
|
+
output[key] = child.value;
|
|
335
|
+
} else {
|
|
336
|
+
entries.push([key, child.value]);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (entries !== null) {
|
|
340
|
+
entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
341
|
+
for (const [key, value] of entries) {
|
|
342
|
+
Object.defineProperty(output, key, {
|
|
343
|
+
configurable: true,
|
|
344
|
+
enumerable: true,
|
|
345
|
+
value,
|
|
346
|
+
writable: true
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return ok(options.freeze ? Object.freeze(output) : output);
|
|
351
|
+
}
|
|
352
|
+
function validateAndCloneJson(input, limits, options) {
|
|
353
|
+
if (!Number.isSafeInteger(limits.maxDepth) || limits.maxDepth < 0 || !Number.isSafeInteger(limits.maxNodes) || limits.maxNodes < 1 || !Number.isSafeInteger(limits.maxStringBytes) || limits.maxStringBytes < 0) {
|
|
354
|
+
throw new Error("JSON limits must be non-negative safe integers and allow at least one node");
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
return parseJsonAt(input, "$", 0, limits, { nodes: 0, stringBytes: 0 }, new Set, options);
|
|
358
|
+
} catch (reason) {
|
|
359
|
+
return err(jsonError("invalid-object", "$", renderUnknownReason(reason, "JSON object inspection failed")));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function parseJsonValue(input, limits = DEFAULT_JSON_LIMITS) {
|
|
363
|
+
return validateAndCloneJson(input, limits, PARSED_JSON_OPTIONS);
|
|
364
|
+
}
|
|
365
|
+
function canonicalize(value) {
|
|
366
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
367
|
+
return JSON.stringify(value);
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(value)) {
|
|
370
|
+
return `[${value.map(canonicalize).join(",")}]`;
|
|
371
|
+
}
|
|
372
|
+
const entries = Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => `${JSON.stringify(key)}:${canonicalize(child)}`);
|
|
373
|
+
return `{${entries.join(",")}}`;
|
|
374
|
+
}
|
|
375
|
+
function canonicalJson(input, limits = DEFAULT_JSON_LIMITS) {
|
|
376
|
+
const parsed = parseJsonValue(input, limits);
|
|
377
|
+
return parsed.ok ? ok(canonicalize(parsed.value)) : parsed;
|
|
378
|
+
}
|
|
379
|
+
function cloneJson(input, limits = DEFAULT_JSON_LIMITS) {
|
|
380
|
+
return validateAndCloneJson(input, limits, CLONED_JSON_OPTIONS);
|
|
381
|
+
}
|
|
382
|
+
function freezeJson(value) {
|
|
383
|
+
if (value !== null && typeof value === "object") {
|
|
384
|
+
for (const child of Array.isArray(value) ? value : Object.values(value)) {
|
|
385
|
+
freezeJson(child);
|
|
386
|
+
}
|
|
387
|
+
Object.freeze(value);
|
|
388
|
+
}
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
var STABLE_HASH_ALGORITHM = "fnv1a-64";
|
|
392
|
+
var TAGGED_STABLE_HASH_PATTERN = /^fnv1a-64:[0-9a-f]{16}$/u;
|
|
393
|
+
function tagStableHash(hash) {
|
|
394
|
+
return `${hash.algorithm}:${hash.value}`;
|
|
395
|
+
}
|
|
396
|
+
function parseTaggedStableHash(input) {
|
|
397
|
+
return typeof input === "string" && TAGGED_STABLE_HASH_PATTERN.test(input) ? ok(input) : err({
|
|
398
|
+
code: "invalid-stable-hash",
|
|
399
|
+
message: `Stable hashes must use ${STABLE_HASH_ALGORITHM} with 16 lowercase hexadecimal digits`
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function updateFnvByte(hash, byte) {
|
|
403
|
+
return BigInt.asUintN(64, (hash ^ BigInt(byte)) * 0x100000001b3n);
|
|
404
|
+
}
|
|
405
|
+
function stableHash(input, limits = DEFAULT_JSON_LIMITS) {
|
|
406
|
+
const serialized = canonicalJson(input, limits);
|
|
407
|
+
if (!serialized.ok) {
|
|
408
|
+
return serialized;
|
|
409
|
+
}
|
|
410
|
+
let hash = 0xcbf29ce484222325n;
|
|
411
|
+
for (let index = 0;index < serialized.value.length; index += 1) {
|
|
412
|
+
const code = serialized.value.charCodeAt(index);
|
|
413
|
+
if (code <= 127) {
|
|
414
|
+
hash = updateFnvByte(hash, code);
|
|
415
|
+
} else if (code <= 2047) {
|
|
416
|
+
hash = updateFnvByte(hash, 192 | code >> 6);
|
|
417
|
+
hash = updateFnvByte(hash, 128 | code & 63);
|
|
418
|
+
} else if (code >= 55296 && code <= 56319 && index + 1 < serialized.value.length) {
|
|
419
|
+
const next = serialized.value.charCodeAt(index + 1);
|
|
420
|
+
if (next >= 56320 && next <= 57343) {
|
|
421
|
+
const point = 65536 + (code - 55296 << 10) + (next - 56320);
|
|
422
|
+
hash = updateFnvByte(hash, 240 | point >> 18);
|
|
423
|
+
hash = updateFnvByte(hash, 128 | point >> 12 & 63);
|
|
424
|
+
hash = updateFnvByte(hash, 128 | point >> 6 & 63);
|
|
425
|
+
hash = updateFnvByte(hash, 128 | point & 63);
|
|
426
|
+
index += 1;
|
|
427
|
+
} else {
|
|
428
|
+
hash = updateFnvByte(hash, 239);
|
|
429
|
+
hash = updateFnvByte(hash, 191);
|
|
430
|
+
hash = updateFnvByte(hash, 189);
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
hash = updateFnvByte(hash, 224 | code >> 12);
|
|
434
|
+
hash = updateFnvByte(hash, 128 | code >> 6 & 63);
|
|
435
|
+
hash = updateFnvByte(hash, 128 | code & 63);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return ok({
|
|
439
|
+
algorithm: STABLE_HASH_ALGORITHM,
|
|
440
|
+
value: hash.toString(16).padStart(16, "0")
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function parseAndCloneWorld(input, parseWorld) {
|
|
444
|
+
const cloned = cloneJson(input);
|
|
445
|
+
if (!cloned.ok) {
|
|
446
|
+
return cloned;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
const world = parseWorld(cloned.value);
|
|
450
|
+
const verified = validateAndCloneJson(world, DEFAULT_JSON_LIMITS, FROZEN_CLONED_JSON_OPTIONS);
|
|
451
|
+
if (!verified.ok) {
|
|
452
|
+
return verified;
|
|
453
|
+
}
|
|
454
|
+
return ok(verified.value);
|
|
455
|
+
} catch (reason) {
|
|
456
|
+
return err({ code: "invalid-world", message: renderUnknownReason(reason) });
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/core/coverage.ts
|
|
461
|
+
var DIRECT_COVERAGE_SCHEMA = "direct.coverage/v2";
|
|
462
|
+
var MAX_DIRECT_COVERAGE_ENTRIES = 256;
|
|
463
|
+
var DIRECT_COVERAGE_JSON_LIMITS = Object.freeze({
|
|
464
|
+
...DEFAULT_JSON_LIMITS,
|
|
465
|
+
maxStringBytes: 16777216
|
|
466
|
+
});
|
|
467
|
+
var EMPTY_COVERAGE_CATALOG_SNAPSHOT = Object.freeze({
|
|
468
|
+
schema: DIRECT_COVERAGE_SCHEMA,
|
|
469
|
+
entries: Object.freeze([])
|
|
470
|
+
});
|
|
471
|
+
function coverageError(code, message, keys = []) {
|
|
472
|
+
return { code, message, keys };
|
|
473
|
+
}
|
|
474
|
+
function hasControlCharacters(value) {
|
|
475
|
+
for (const character of value) {
|
|
476
|
+
const code = character.charCodeAt(0);
|
|
477
|
+
if (code < 32 && code !== 9 && code !== 10 && code !== 13 || code === 127) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
var COVERAGE_ENTRY_KEYS = new Set(["key", "mode", "claim", "scenarios"]);
|
|
484
|
+
var COVERAGE_SNAPSHOT_KEYS = new Set(["schema", "entries"]);
|
|
485
|
+
function isStringArray(value) {
|
|
486
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
487
|
+
}
|
|
488
|
+
function createCoverageCatalogSnapshot(catalog) {
|
|
489
|
+
return Object.freeze({
|
|
490
|
+
schema: DIRECT_COVERAGE_SCHEMA,
|
|
491
|
+
entries: catalog.list()
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
function parseCoverageCatalogSnapshot(input, limits = DIRECT_COVERAGE_JSON_LIMITS) {
|
|
495
|
+
const parsed = parseJsonValue(input, limits);
|
|
496
|
+
if (!parsed.ok || !isRecord(parsed.value)) {
|
|
497
|
+
return err(coverageError("invalid-coverage", parsed.ok ? "Coverage snapshot must be an object" : parsed.error.message));
|
|
498
|
+
}
|
|
499
|
+
for (const key of Object.keys(parsed.value)) {
|
|
500
|
+
if (!COVERAGE_SNAPSHOT_KEYS.has(key)) {
|
|
501
|
+
return err(coverageError("invalid-coverage", `Unknown coverage snapshot key: ${key}`));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (parsed.value.schema !== DIRECT_COVERAGE_SCHEMA) {
|
|
505
|
+
return err(coverageError("invalid-coverage", `Coverage snapshot schema must be ${DIRECT_COVERAGE_SCHEMA}`));
|
|
506
|
+
}
|
|
507
|
+
if (!Array.isArray(parsed.value.entries)) {
|
|
508
|
+
return err(coverageError("invalid-coverage", "Coverage snapshot entries must be an array"));
|
|
509
|
+
}
|
|
510
|
+
const entries = [];
|
|
511
|
+
for (const [index, candidate] of parsed.value.entries.entries()) {
|
|
512
|
+
if (!isRecord(candidate)) {
|
|
513
|
+
return err(coverageError("invalid-coverage", `Coverage entry ${String(index)} must be an object`));
|
|
514
|
+
}
|
|
515
|
+
for (const key of Object.keys(candidate)) {
|
|
516
|
+
if (!COVERAGE_ENTRY_KEYS.has(key)) {
|
|
517
|
+
return err(coverageError("invalid-coverage", `Unknown coverage entry key at ${String(index)}: ${key}`));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (typeof candidate.key !== "string" || typeof candidate.claim !== "string" || candidate.mode !== "fixture" && candidate.mode !== "mixed" && candidate.mode !== "direct" || !isStringArray(candidate.scenarios)) {
|
|
521
|
+
return err(coverageError("invalid-coverage", `Coverage entry ${String(index)} has an invalid wire shape`));
|
|
522
|
+
}
|
|
523
|
+
if (candidate.mode === "direct") {
|
|
524
|
+
if (candidate.scenarios.length > 0) {
|
|
525
|
+
return err(coverageError("invalid-mode", `Direct coverage ${candidate.key} cannot cite fixture scenarios`, [candidate.key]));
|
|
526
|
+
}
|
|
527
|
+
entries.push({
|
|
528
|
+
key: candidate.key,
|
|
529
|
+
mode: candidate.mode,
|
|
530
|
+
claim: candidate.claim,
|
|
531
|
+
scenarios: []
|
|
532
|
+
});
|
|
533
|
+
} else {
|
|
534
|
+
const firstScenario = candidate.scenarios[0];
|
|
535
|
+
if (typeof firstScenario !== "string") {
|
|
536
|
+
return err(coverageError("invalid-mode", `${candidate.mode} coverage ${candidate.key} must cite at least one scenario`, [candidate.key]));
|
|
537
|
+
}
|
|
538
|
+
entries.push({
|
|
539
|
+
key: candidate.key,
|
|
540
|
+
mode: candidate.mode,
|
|
541
|
+
claim: candidate.claim,
|
|
542
|
+
scenarios: [firstScenario, ...candidate.scenarios.slice(1)]
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const catalog = createCoverageCatalog(entries);
|
|
547
|
+
return catalog.ok ? ok(createCoverageCatalogSnapshot(catalog.value)) : catalog;
|
|
548
|
+
}
|
|
549
|
+
function createCoverageCatalog(inputs, scenarios) {
|
|
550
|
+
if (inputs.length > MAX_DIRECT_COVERAGE_ENTRIES) {
|
|
551
|
+
return err(coverageError("too-many-coverage-entries", `Direct definitions support at most ${String(MAX_DIRECT_COVERAGE_ENTRIES)} coverage entries`));
|
|
552
|
+
}
|
|
553
|
+
const entries = [];
|
|
554
|
+
const byKey = new Map;
|
|
555
|
+
for (const input of inputs) {
|
|
556
|
+
const key = parseCoverageKey(input.key);
|
|
557
|
+
if (!key.ok) {
|
|
558
|
+
return err(coverageError("invalid-coverage", key.error.message, [String(input.key)]));
|
|
559
|
+
}
|
|
560
|
+
if (byKey.has(key.value)) {
|
|
561
|
+
return err(coverageError("duplicate-coverage", `Duplicate coverage key: ${key.value}`, [key.value]));
|
|
562
|
+
}
|
|
563
|
+
if (input.claim.trim().length === 0 || input.claim.length > 1000 || hasControlCharacters(input.claim)) {
|
|
564
|
+
return err(coverageError("invalid-claim", `Coverage ${key.value} needs a 1-1000 character claim`, [key.value]));
|
|
565
|
+
}
|
|
566
|
+
if (input.mode !== "fixture" && input.mode !== "mixed" && input.mode !== "direct") {
|
|
567
|
+
return err(coverageError("invalid-mode", `Coverage ${key.value} has an unknown proof mode`, [key.value]));
|
|
568
|
+
}
|
|
569
|
+
if (input.mode === "direct" && input.scenarios.length > 0) {
|
|
570
|
+
return err(coverageError("invalid-mode", `Direct coverage ${key.value} cannot cite fixture scenarios`, [key.value]));
|
|
571
|
+
}
|
|
572
|
+
if (input.mode !== "direct" && input.scenarios.length === 0) {
|
|
573
|
+
return err(coverageError("invalid-mode", `${input.mode} coverage ${key.value} must cite at least one scenario`, [key.value]));
|
|
574
|
+
}
|
|
575
|
+
const scenarioIds = [];
|
|
576
|
+
const seenScenarios = new Set;
|
|
577
|
+
for (const candidate of input.scenarios) {
|
|
578
|
+
const id = parseScenarioId(candidate);
|
|
579
|
+
if (!id.ok) {
|
|
580
|
+
return err(coverageError("invalid-scenario", id.error.message, [String(candidate)]));
|
|
581
|
+
}
|
|
582
|
+
if (seenScenarios.has(id.value)) {
|
|
583
|
+
return err(coverageError("invalid-scenario", `Coverage ${key.value} repeats scenario ${id.value}`, [id.value]));
|
|
584
|
+
}
|
|
585
|
+
if (scenarios !== undefined && scenarios.get(id.value) === undefined) {
|
|
586
|
+
return err(coverageError("unknown-scenario", `Coverage ${key.value} cites unknown scenario ${id.value}`, [id.value]));
|
|
587
|
+
}
|
|
588
|
+
seenScenarios.add(id.value);
|
|
589
|
+
scenarioIds.push(id.value);
|
|
590
|
+
}
|
|
591
|
+
let entry;
|
|
592
|
+
if (input.mode === "direct") {
|
|
593
|
+
const scenarios2 = Object.freeze([]);
|
|
594
|
+
entry = Object.freeze({
|
|
595
|
+
key: key.value,
|
|
596
|
+
mode: input.mode,
|
|
597
|
+
claim: input.claim,
|
|
598
|
+
scenarios: scenarios2
|
|
599
|
+
});
|
|
600
|
+
} else {
|
|
601
|
+
const firstScenarioId = scenarioIds[0];
|
|
602
|
+
if (firstScenarioId === undefined) {
|
|
603
|
+
return err(coverageError("invalid-mode", `${input.mode} coverage ${key.value} must cite at least one scenario`, [key.value]));
|
|
604
|
+
}
|
|
605
|
+
const scenarios2 = Object.freeze([
|
|
606
|
+
firstScenarioId,
|
|
607
|
+
...scenarioIds.slice(1)
|
|
608
|
+
]);
|
|
609
|
+
entry = Object.freeze({
|
|
610
|
+
key: key.value,
|
|
611
|
+
mode: input.mode,
|
|
612
|
+
claim: input.claim,
|
|
613
|
+
scenarios: scenarios2
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
entries.push(entry);
|
|
617
|
+
byKey.set(key.value, entry);
|
|
618
|
+
}
|
|
619
|
+
const frozenEntries = Object.freeze(entries);
|
|
620
|
+
const keys = Object.freeze(frozenEntries.map((entry) => entry.key));
|
|
621
|
+
const catalog = {
|
|
622
|
+
size: frozenEntries.length,
|
|
623
|
+
keys: () => keys,
|
|
624
|
+
list: () => frozenEntries,
|
|
625
|
+
get: (key) => byKey.get(key),
|
|
626
|
+
resolve: (input) => {
|
|
627
|
+
const key = parseCoverageKey(input);
|
|
628
|
+
if (!key.ok) {
|
|
629
|
+
return err(coverageError("invalid-coverage", key.error.message, [String(input)]));
|
|
630
|
+
}
|
|
631
|
+
const entry = byKey.get(key.value);
|
|
632
|
+
return entry === undefined ? err(coverageError("unknown-coverage", `Unknown coverage key: ${key.value}`, [key.value])) : ok(entry);
|
|
633
|
+
},
|
|
634
|
+
requireExactKeys: (expected) => {
|
|
635
|
+
const expectedKeys = [];
|
|
636
|
+
const seen = new Set;
|
|
637
|
+
for (const candidate of expected) {
|
|
638
|
+
const parsed = parseCoverageKey(candidate);
|
|
639
|
+
if (!parsed.ok) {
|
|
640
|
+
return err(coverageError("invalid-coverage", parsed.error.message, [String(candidate)]));
|
|
641
|
+
}
|
|
642
|
+
if (seen.has(parsed.value)) {
|
|
643
|
+
return err(coverageError("duplicate-expected-key", `Expected coverage repeats ${parsed.value}`, [parsed.value]));
|
|
644
|
+
}
|
|
645
|
+
seen.add(parsed.value);
|
|
646
|
+
expectedKeys.push(parsed.value);
|
|
647
|
+
}
|
|
648
|
+
const missing = expectedKeys.filter((key) => !byKey.has(key));
|
|
649
|
+
if (missing.length > 0) {
|
|
650
|
+
return err(coverageError("missing-coverage", `Missing coverage keys: ${missing.join(", ")}`, missing));
|
|
651
|
+
}
|
|
652
|
+
const unexpected = keys.filter((key) => !seen.has(key));
|
|
653
|
+
if (unexpected.length > 0) {
|
|
654
|
+
return err(coverageError("unexpected-coverage", `Unexpected coverage keys: ${unexpected.join(", ")}`, unexpected));
|
|
655
|
+
}
|
|
656
|
+
return ok(true);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
return ok(Object.freeze(catalog));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/core/runtime.ts
|
|
663
|
+
var LOGICAL_RUNTIME_SCHEMA = "direct.runtime/v1";
|
|
664
|
+
var MAX_HOST_TIMER_MILLISECONDS = 2147483647;
|
|
665
|
+
var DEFAULT_LOGICAL_RUNTIME_SNAPSHOT = Object.freeze({
|
|
666
|
+
schema: LOGICAL_RUNTIME_SCHEMA,
|
|
667
|
+
nowMs: 0,
|
|
668
|
+
nextOperation: 1,
|
|
669
|
+
acceleration: 100
|
|
670
|
+
});
|
|
671
|
+
var RUNTIME_KEYS = new Set(["schema", "nowMs", "nextOperation", "acceleration"]);
|
|
672
|
+
var NAMESPACE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
673
|
+
function parseLogicalRuntimeSnapshot(input) {
|
|
674
|
+
const parsedJson = parseJsonValue(input);
|
|
675
|
+
if (!parsedJson.ok || !isRecord(parsedJson.value)) {
|
|
676
|
+
return err({ code: "invalid-runtime", message: "Logical runtime must be an object" });
|
|
677
|
+
}
|
|
678
|
+
for (const key of Object.keys(parsedJson.value)) {
|
|
679
|
+
if (!RUNTIME_KEYS.has(key)) {
|
|
680
|
+
return err({ code: "invalid-runtime", message: `Unknown logical runtime key: ${key}` });
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const record = parsedJson.value;
|
|
684
|
+
if (record.schema !== LOGICAL_RUNTIME_SCHEMA) {
|
|
685
|
+
return err({ code: "invalid-runtime", message: `Logical runtime schema must be ${LOGICAL_RUNTIME_SCHEMA}` });
|
|
686
|
+
}
|
|
687
|
+
if (typeof record.nowMs !== "number" || !Number.isSafeInteger(record.nowMs) || record.nowMs < 0) {
|
|
688
|
+
return err({ code: "invalid-runtime", message: "Logical nowMs must be a non-negative safe integer" });
|
|
689
|
+
}
|
|
690
|
+
if (typeof record.nextOperation !== "number" || !Number.isSafeInteger(record.nextOperation) || record.nextOperation < 1) {
|
|
691
|
+
return err({ code: "invalid-runtime", message: "Logical nextOperation must be a positive safe integer" });
|
|
692
|
+
}
|
|
693
|
+
if (typeof record.acceleration !== "number" || !Number.isFinite(record.acceleration) || record.acceleration < 1 || record.acceleration > 1e6) {
|
|
694
|
+
return err({ code: "invalid-runtime", message: "Logical acceleration must be in [1, 1000000]" });
|
|
695
|
+
}
|
|
696
|
+
return ok(Object.freeze({
|
|
697
|
+
schema: LOGICAL_RUNTIME_SCHEMA,
|
|
698
|
+
nowMs: record.nowMs,
|
|
699
|
+
nextOperation: record.nextOperation,
|
|
700
|
+
acceleration: record.acceleration
|
|
701
|
+
}));
|
|
702
|
+
}
|
|
703
|
+
function sleepTimerChunk(wallMilliseconds, signal) {
|
|
704
|
+
return new Promise((resolve) => {
|
|
705
|
+
if (signal?.aborted === true) {
|
|
706
|
+
resolve();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
let timeout = null;
|
|
710
|
+
let settled = false;
|
|
711
|
+
const finish = () => {
|
|
712
|
+
if (settled)
|
|
713
|
+
return;
|
|
714
|
+
settled = true;
|
|
715
|
+
if (timeout !== null)
|
|
716
|
+
clearTimeout(timeout);
|
|
717
|
+
signal?.removeEventListener("abort", finish);
|
|
718
|
+
resolve();
|
|
719
|
+
};
|
|
720
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
721
|
+
timeout = setTimeout(finish, wallMilliseconds);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
async function defaultSleep(wallMilliseconds, signal) {
|
|
725
|
+
let remaining = wallMilliseconds;
|
|
726
|
+
while (remaining > 0 && signal?.aborted !== true) {
|
|
727
|
+
const chunk = Math.min(remaining, MAX_HOST_TIMER_MILLISECONDS);
|
|
728
|
+
await sleepTimerChunk(chunk, signal);
|
|
729
|
+
remaining -= chunk;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function parseDuration(logicalMilliseconds) {
|
|
733
|
+
return Number.isSafeInteger(logicalMilliseconds) && logicalMilliseconds >= 0 ? ok(logicalMilliseconds) : err({ code: "invalid-duration", message: "Logical durations must be non-negative safe integers" });
|
|
734
|
+
}
|
|
735
|
+
function isWaitCancelled(signal) {
|
|
736
|
+
return signal?.aborted === true;
|
|
737
|
+
}
|
|
738
|
+
function waitCancelled() {
|
|
739
|
+
return err({
|
|
740
|
+
code: "wait-cancelled",
|
|
741
|
+
message: "Logical wait was cancelled"
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
function nextLogicalTime(nowMs, duration) {
|
|
745
|
+
const nextNow = nowMs + duration;
|
|
746
|
+
return Number.isSafeInteger(nextNow) ? ok(nextNow) : err({ code: "time-overflow", message: "Logical time exceeds the safe integer range" });
|
|
747
|
+
}
|
|
748
|
+
function createLogicalRuntime(initial = DEFAULT_LOGICAL_RUNTIME_SNAPSHOT, sleep = defaultSleep) {
|
|
749
|
+
const parsed = parseLogicalRuntimeSnapshot(initial);
|
|
750
|
+
if (!parsed.ok) {
|
|
751
|
+
throw new Error(parsed.error.message);
|
|
752
|
+
}
|
|
753
|
+
let nowMs = parsed.value.nowMs;
|
|
754
|
+
let nextOperation = parsed.value.nextOperation;
|
|
755
|
+
const acceleration = parsed.value.acceleration;
|
|
756
|
+
let waitTail = Promise.resolve();
|
|
757
|
+
const snapshot = () => Object.freeze({
|
|
758
|
+
schema: LOGICAL_RUNTIME_SCHEMA,
|
|
759
|
+
nowMs,
|
|
760
|
+
nextOperation,
|
|
761
|
+
acceleration
|
|
762
|
+
});
|
|
763
|
+
const advance = (logicalMilliseconds) => {
|
|
764
|
+
const duration = parseDuration(logicalMilliseconds);
|
|
765
|
+
if (!duration.ok) {
|
|
766
|
+
return duration;
|
|
767
|
+
}
|
|
768
|
+
const nextNow = nextLogicalTime(nowMs, duration.value);
|
|
769
|
+
if (!nextNow.ok) {
|
|
770
|
+
return nextNow;
|
|
771
|
+
}
|
|
772
|
+
nowMs = nextNow.value;
|
|
773
|
+
return ok(nowMs);
|
|
774
|
+
};
|
|
775
|
+
const wait = (logicalMilliseconds, signal) => {
|
|
776
|
+
const duration = parseDuration(logicalMilliseconds);
|
|
777
|
+
if (!duration.ok) {
|
|
778
|
+
return Promise.resolve(duration);
|
|
779
|
+
}
|
|
780
|
+
const run = waitTail.then(async () => {
|
|
781
|
+
if (isWaitCancelled(signal))
|
|
782
|
+
return waitCancelled();
|
|
783
|
+
const target = nextLogicalTime(nowMs, duration.value);
|
|
784
|
+
if (!target.ok)
|
|
785
|
+
return target;
|
|
786
|
+
const wallMilliseconds = Math.ceil(duration.value / acceleration);
|
|
787
|
+
try {
|
|
788
|
+
if (wallMilliseconds > 0) {
|
|
789
|
+
await sleep(wallMilliseconds, signal);
|
|
790
|
+
}
|
|
791
|
+
} catch (reason) {
|
|
792
|
+
if (isWaitCancelled(signal))
|
|
793
|
+
return waitCancelled();
|
|
794
|
+
return err({
|
|
795
|
+
code: "sleep-failed",
|
|
796
|
+
message: renderUnknownReason(reason, "Logical sleep failed")
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
if (isWaitCancelled(signal))
|
|
800
|
+
return waitCancelled();
|
|
801
|
+
return advance(duration.value);
|
|
802
|
+
});
|
|
803
|
+
waitTail = run.then(() => {
|
|
804
|
+
return;
|
|
805
|
+
}, () => {
|
|
806
|
+
return;
|
|
807
|
+
});
|
|
808
|
+
return run;
|
|
809
|
+
};
|
|
810
|
+
return Object.freeze({
|
|
811
|
+
now: () => nowMs,
|
|
812
|
+
snapshot,
|
|
813
|
+
nextOperationId: (namespace = "operation") => {
|
|
814
|
+
if (!NAMESPACE_PATTERN.test(namespace) || namespace.length > 48) {
|
|
815
|
+
throw new Error("Operation namespaces must be lowercase hyphen-separated ASCII identifiers");
|
|
816
|
+
}
|
|
817
|
+
if (!Number.isSafeInteger(nextOperation) || nextOperation >= Number.MAX_SAFE_INTEGER) {
|
|
818
|
+
throw new Error("Operation sequence exceeds the safe integer range");
|
|
819
|
+
}
|
|
820
|
+
const candidate = `${namespace}-${String(nextOperation).padStart(6, "0")}`;
|
|
821
|
+
nextOperation += 1;
|
|
822
|
+
const parsedOperation = parseOperationId(candidate);
|
|
823
|
+
if (!parsedOperation.ok) {
|
|
824
|
+
throw new Error(parsedOperation.error.message);
|
|
825
|
+
}
|
|
826
|
+
return parsedOperation.value;
|
|
827
|
+
},
|
|
828
|
+
advance,
|
|
829
|
+
wait
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/core/fixture.ts
|
|
834
|
+
var FIXTURE_SCHEMA = "direct.fixture/v1";
|
|
835
|
+
var DEFAULT_MAX_FIXTURE_BYTES = 65536;
|
|
836
|
+
var FIXTURE_KEYS = new Set(["schema", "scenario", "route", "world", "runtime"]);
|
|
837
|
+
function fixtureError(code, message) {
|
|
838
|
+
return { code, message };
|
|
839
|
+
}
|
|
840
|
+
function maxFixtureBytes(value) {
|
|
841
|
+
const maximum = value ?? DEFAULT_MAX_FIXTURE_BYTES;
|
|
842
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1) {
|
|
843
|
+
throw new Error("Fixture maxBytes must be a positive safe integer");
|
|
844
|
+
}
|
|
845
|
+
return maximum;
|
|
846
|
+
}
|
|
847
|
+
function parseFixtureEnvelope(input, options) {
|
|
848
|
+
const maximum = maxFixtureBytes(options.maxBytes);
|
|
849
|
+
const serialized = canonicalJson(input);
|
|
850
|
+
if (!serialized.ok) {
|
|
851
|
+
return err(fixtureError("invalid-fixture", serialized.error.message));
|
|
852
|
+
}
|
|
853
|
+
if (utf8ByteLength(serialized.value) > maximum) {
|
|
854
|
+
return err(fixtureError("oversized-fixture", "Fixture exceeds its byte limit"));
|
|
855
|
+
}
|
|
856
|
+
const foreign = JSON.parse(serialized.value);
|
|
857
|
+
if (!isRecord(foreign)) {
|
|
858
|
+
return err(fixtureError("invalid-fixture", "Fixture must be an object"));
|
|
859
|
+
}
|
|
860
|
+
for (const key of Object.keys(foreign)) {
|
|
861
|
+
if (!FIXTURE_KEYS.has(key)) {
|
|
862
|
+
return err(fixtureError("unknown-key", `Unknown fixture key: ${key}`));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (foreign.schema !== FIXTURE_SCHEMA) {
|
|
866
|
+
return err(fixtureError("invalid-fixture", `Fixture schema must be ${FIXTURE_SCHEMA}`));
|
|
867
|
+
}
|
|
868
|
+
const id = parseScenarioId(foreign.scenario);
|
|
869
|
+
if (!id.ok) {
|
|
870
|
+
return err(fixtureError("invalid-scenario", id.error.message));
|
|
871
|
+
}
|
|
872
|
+
const scenario = options.scenarios.get(id.value);
|
|
873
|
+
if (scenario === undefined) {
|
|
874
|
+
return err(fixtureError("unknown-scenario", `Unknown fixture scenario: ${id.value}`));
|
|
875
|
+
}
|
|
876
|
+
if (typeof foreign.route !== "string" || foreign.route !== scenario.route) {
|
|
877
|
+
return err(fixtureError("mismatched-route", `Fixture route must match scenario ${id.value}`));
|
|
878
|
+
}
|
|
879
|
+
const runtime = foreign.runtime === undefined ? ok(scenario.runtime) : parseLogicalRuntimeSnapshot(foreign.runtime);
|
|
880
|
+
if (!runtime.ok) {
|
|
881
|
+
return err(fixtureError("invalid-runtime", runtime.error.message));
|
|
882
|
+
}
|
|
883
|
+
const world = parseAndCloneWorld(foreign.world, options.parseWorld);
|
|
884
|
+
if (!world.ok) {
|
|
885
|
+
return err(fixtureError("invalid-world", world.error.message));
|
|
886
|
+
}
|
|
887
|
+
const envelope = Object.freeze({
|
|
888
|
+
schema: FIXTURE_SCHEMA,
|
|
889
|
+
scenario: id.value,
|
|
890
|
+
route: scenario.route,
|
|
891
|
+
world: world.value,
|
|
892
|
+
runtime: runtime.value
|
|
893
|
+
});
|
|
894
|
+
const normalized = canonicalJson(envelope);
|
|
895
|
+
if (!normalized.ok) {
|
|
896
|
+
return err(fixtureError("invalid-fixture", normalized.error.message));
|
|
897
|
+
}
|
|
898
|
+
if (utf8ByteLength(normalized.value) > maximum) {
|
|
899
|
+
return err(fixtureError("oversized-fixture", "Normalized fixture exceeds its byte limit"));
|
|
900
|
+
}
|
|
901
|
+
return ok(envelope);
|
|
902
|
+
}
|
|
903
|
+
function parseFixtureJson(source, options) {
|
|
904
|
+
if (typeof source !== "string") {
|
|
905
|
+
return err(fixtureError("invalid-json", "Fixture JSON source must be a string"));
|
|
906
|
+
}
|
|
907
|
+
if (utf8ByteLength(source) > maxFixtureBytes(options.maxBytes)) {
|
|
908
|
+
return err(fixtureError("oversized-fixture", "Fixture exceeds its byte limit"));
|
|
909
|
+
}
|
|
910
|
+
const input = parseExactJsonSource(source);
|
|
911
|
+
if (!input.ok) {
|
|
912
|
+
return err(fixtureError(input.error.code === "duplicate-key" ? "duplicate-key" : "invalid-json", input.error.code === "duplicate-key" ? input.error.message : "Fixture is not valid JSON"));
|
|
913
|
+
}
|
|
914
|
+
return parseFixtureEnvelope(input.value, options);
|
|
915
|
+
}
|
|
916
|
+
function createFixtureEnvelope(input, options) {
|
|
917
|
+
const id = parseScenarioId(input.scenario);
|
|
918
|
+
if (!id.ok)
|
|
919
|
+
return err(fixtureError("invalid-scenario", id.error.message));
|
|
920
|
+
const scenario = options.scenarios.get(id.value);
|
|
921
|
+
if (scenario === undefined) {
|
|
922
|
+
return err(fixtureError("unknown-scenario", `Unknown fixture scenario: ${id.value}`));
|
|
923
|
+
}
|
|
924
|
+
return parseFixtureEnvelope({
|
|
925
|
+
schema: FIXTURE_SCHEMA,
|
|
926
|
+
scenario: id.value,
|
|
927
|
+
route: scenario.route,
|
|
928
|
+
world: input.world,
|
|
929
|
+
runtime: input.runtime ?? scenario.runtime
|
|
930
|
+
}, options);
|
|
931
|
+
}
|
|
932
|
+
function serializeFixtureJson(input, options) {
|
|
933
|
+
const fixture = createFixtureEnvelope(input, options);
|
|
934
|
+
if (!fixture.ok)
|
|
935
|
+
return fixture;
|
|
936
|
+
const serialized = canonicalJson(fixture.value);
|
|
937
|
+
if (!serialized.ok) {
|
|
938
|
+
return err(fixtureError("invalid-fixture", serialized.error.message));
|
|
939
|
+
}
|
|
940
|
+
if (utf8ByteLength(serialized.value) > maxFixtureBytes(options.maxBytes)) {
|
|
941
|
+
return err(fixtureError("oversized-fixture", "Normalized fixture exceeds its byte limit"));
|
|
942
|
+
}
|
|
943
|
+
return ok(serialized.value);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/core/query.ts
|
|
947
|
+
var SCENARIO_QUERY_KEY = "__direct_scenario";
|
|
948
|
+
var FIXTURE_QUERY_KEY = "__direct_fixture";
|
|
949
|
+
var FIXTURE_QUERY_PREFIX_BYTES = utf8ByteLength(`?${FIXTURE_QUERY_KEY}=`);
|
|
950
|
+
function maximumFixtureQueryBytes(maxFixtureBytes2) {
|
|
951
|
+
return maxFixtureBytes2 * 3 + FIXTURE_QUERY_PREFIX_BYTES;
|
|
952
|
+
}
|
|
953
|
+
var DEFAULT_MAX_QUERY_BYTES = maximumFixtureQueryBytes(DEFAULT_MAX_FIXTURE_BYTES);
|
|
954
|
+
function queryError(code, message) {
|
|
955
|
+
return { code, message };
|
|
956
|
+
}
|
|
957
|
+
function decodeQueryPart(value) {
|
|
958
|
+
try {
|
|
959
|
+
return ok(decodeURIComponent(value.replaceAll("+", " ")));
|
|
960
|
+
} catch {
|
|
961
|
+
return err(queryError("invalid-encoding", "Direct query contains invalid percent encoding"));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function queryBody(source) {
|
|
965
|
+
const question = source.indexOf("?");
|
|
966
|
+
const candidate = question >= 0 ? source.slice(question + 1) : source.startsWith("?") ? source.slice(1) : source;
|
|
967
|
+
const fragment = candidate.indexOf("#");
|
|
968
|
+
return fragment >= 0 ? candidate.slice(0, fragment) : candidate;
|
|
969
|
+
}
|
|
970
|
+
function parseActivationParameters(source) {
|
|
971
|
+
let scenario = null;
|
|
972
|
+
let fixture = null;
|
|
973
|
+
const body = queryBody(source);
|
|
974
|
+
if (body.length === 0) {
|
|
975
|
+
return ok({ scenario, fixture });
|
|
976
|
+
}
|
|
977
|
+
for (const part of body.split("&")) {
|
|
978
|
+
if (part.length === 0) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
const equals = part.indexOf("=");
|
|
982
|
+
const encodedKey = equals < 0 ? part : part.slice(0, equals);
|
|
983
|
+
const encodedValue = equals < 0 ? "" : part.slice(equals + 1);
|
|
984
|
+
const key = decodeQueryPart(encodedKey);
|
|
985
|
+
if (!key.ok) {
|
|
986
|
+
return key;
|
|
987
|
+
}
|
|
988
|
+
const reserved = key.value.startsWith("__direct_");
|
|
989
|
+
if (key.value !== SCENARIO_QUERY_KEY && key.value !== FIXTURE_QUERY_KEY) {
|
|
990
|
+
if (reserved) {
|
|
991
|
+
return err(queryError("unknown-parameter", `Unknown Direct query parameter: ${key.value}`));
|
|
992
|
+
}
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
const value = decodeQueryPart(encodedValue);
|
|
996
|
+
if (!value.ok) {
|
|
997
|
+
return value;
|
|
998
|
+
}
|
|
999
|
+
if (key.value === SCENARIO_QUERY_KEY) {
|
|
1000
|
+
if (scenario !== null) {
|
|
1001
|
+
return err(queryError("duplicate-parameter", `Duplicate ${SCENARIO_QUERY_KEY} parameter`));
|
|
1002
|
+
}
|
|
1003
|
+
scenario = value.value;
|
|
1004
|
+
} else {
|
|
1005
|
+
if (fixture !== null) {
|
|
1006
|
+
return err(queryError("duplicate-parameter", `Duplicate ${FIXTURE_QUERY_KEY} parameter`));
|
|
1007
|
+
}
|
|
1008
|
+
fixture = value.value;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return ok({ scenario, fixture });
|
|
1012
|
+
}
|
|
1013
|
+
function activationHash(source, scenario, route, world, runtime) {
|
|
1014
|
+
const hashed = stableHash({ source, scenario, route, world, runtime });
|
|
1015
|
+
if (!hashed.ok) {
|
|
1016
|
+
throw new Error(hashed.error.message);
|
|
1017
|
+
}
|
|
1018
|
+
return tagStableHash(hashed.value);
|
|
1019
|
+
}
|
|
1020
|
+
function activateDirectScenario(id, scenarios) {
|
|
1021
|
+
const parsed = parseScenarioId(id);
|
|
1022
|
+
if (!parsed.ok) {
|
|
1023
|
+
return err(queryError("invalid-scenario", parsed.error.message));
|
|
1024
|
+
}
|
|
1025
|
+
const scenario = scenarios.get(parsed.value);
|
|
1026
|
+
if (scenario === undefined) {
|
|
1027
|
+
return err(queryError("unknown-scenario", `Unknown scenario: ${parsed.value}`));
|
|
1028
|
+
}
|
|
1029
|
+
return ok(Object.freeze({
|
|
1030
|
+
kind: "active",
|
|
1031
|
+
source: "scenario",
|
|
1032
|
+
scenario: scenario.id,
|
|
1033
|
+
route: scenario.route,
|
|
1034
|
+
world: scenario.world,
|
|
1035
|
+
runtime: scenario.runtime,
|
|
1036
|
+
activationHash: activationHash("scenario", scenario.id, scenario.route, scenario.world, scenario.runtime)
|
|
1037
|
+
}));
|
|
1038
|
+
}
|
|
1039
|
+
function parseDirectQuery(source, options) {
|
|
1040
|
+
const maxBytes = options.maxQueryBytes ?? DEFAULT_MAX_QUERY_BYTES;
|
|
1041
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
|
1042
|
+
throw new Error("Query maxQueryBytes must be a positive safe integer");
|
|
1043
|
+
}
|
|
1044
|
+
if (typeof source !== "string") {
|
|
1045
|
+
return err(queryError("invalid-query", "Direct query source must be a string"));
|
|
1046
|
+
}
|
|
1047
|
+
if (utf8ByteLength(source) > maxBytes) {
|
|
1048
|
+
return err(queryError("oversized-query", "Direct query exceeds its byte limit"));
|
|
1049
|
+
}
|
|
1050
|
+
const parameters = parseActivationParameters(source);
|
|
1051
|
+
if (!parameters.ok) {
|
|
1052
|
+
return parameters;
|
|
1053
|
+
}
|
|
1054
|
+
if (parameters.value.scenario === null && parameters.value.fixture === null) {
|
|
1055
|
+
return ok(Object.freeze({ kind: "inactive" }));
|
|
1056
|
+
}
|
|
1057
|
+
const requestedScenario = parameters.value.scenario === null ? null : activateDirectScenario(parameters.value.scenario, options.scenarios);
|
|
1058
|
+
if (requestedScenario !== null && !requestedScenario.ok) {
|
|
1059
|
+
return requestedScenario;
|
|
1060
|
+
}
|
|
1061
|
+
if (parameters.value.fixture === null) {
|
|
1062
|
+
return requestedScenario ?? err(queryError("invalid-scenario", "Missing scenario activation"));
|
|
1063
|
+
}
|
|
1064
|
+
const fixture = parseFixtureJson(parameters.value.fixture, options);
|
|
1065
|
+
if (!fixture.ok) {
|
|
1066
|
+
return err(queryError("invalid-fixture", fixture.error.message));
|
|
1067
|
+
}
|
|
1068
|
+
if (requestedScenario !== null && requestedScenario.value.scenario !== fixture.value.scenario) {
|
|
1069
|
+
return err(queryError("mismatched-scenario", `${SCENARIO_QUERY_KEY} does not match the fixture scenario`));
|
|
1070
|
+
}
|
|
1071
|
+
return ok(Object.freeze({
|
|
1072
|
+
kind: "active",
|
|
1073
|
+
source: "fixture",
|
|
1074
|
+
scenario: fixture.value.scenario,
|
|
1075
|
+
route: fixture.value.route,
|
|
1076
|
+
world: fixture.value.world,
|
|
1077
|
+
runtime: fixture.value.runtime,
|
|
1078
|
+
activationHash: activationHash("fixture", fixture.value.scenario, fixture.value.route, fixture.value.world, fixture.value.runtime)
|
|
1079
|
+
}));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/core/scenario.ts
|
|
1083
|
+
var MAX_DIRECT_SCENARIOS = 256;
|
|
1084
|
+
function validText(value, maximum) {
|
|
1085
|
+
if (value.trim().length === 0 || value.length > maximum) {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
for (const character of value) {
|
|
1089
|
+
const code = character.charCodeAt(0);
|
|
1090
|
+
if (code < 32 && code !== 9 && code !== 10 && code !== 13 || code === 127) {
|
|
1091
|
+
return false;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
1096
|
+
function validRoute(value) {
|
|
1097
|
+
if (value.trim().length === 0 || value.length > 256)
|
|
1098
|
+
return false;
|
|
1099
|
+
for (const character of value) {
|
|
1100
|
+
const code = character.charCodeAt(0);
|
|
1101
|
+
if (code < 32 || code === 127)
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
return true;
|
|
1105
|
+
}
|
|
1106
|
+
function scenarioError(code, scenario, message) {
|
|
1107
|
+
return { code, scenario, message };
|
|
1108
|
+
}
|
|
1109
|
+
function createScenarioCatalog(inputs, parseWorld) {
|
|
1110
|
+
if (inputs.length > MAX_DIRECT_SCENARIOS) {
|
|
1111
|
+
return err(scenarioError("too-many-scenarios", inputs.length, `Direct definitions support at most ${String(MAX_DIRECT_SCENARIOS)} scenarios`));
|
|
1112
|
+
}
|
|
1113
|
+
const definitions = [];
|
|
1114
|
+
const byId = new Map;
|
|
1115
|
+
for (const input of inputs) {
|
|
1116
|
+
const id = parseScenarioId(input.id);
|
|
1117
|
+
if (!id.ok) {
|
|
1118
|
+
return err(scenarioError("invalid-scenario", input.id, id.error.message));
|
|
1119
|
+
}
|
|
1120
|
+
if (byId.has(id.value)) {
|
|
1121
|
+
return err(scenarioError("duplicate-scenario", id.value, `Duplicate scenario: ${id.value}`));
|
|
1122
|
+
}
|
|
1123
|
+
if (!validText(input.title, 160)) {
|
|
1124
|
+
return err(scenarioError("invalid-title", id.value, "Scenario titles must contain 1-160 visible characters"));
|
|
1125
|
+
}
|
|
1126
|
+
if (input.description !== undefined && !validText(input.description, 2000)) {
|
|
1127
|
+
return err(scenarioError("invalid-description", id.value, "Scenario descriptions must contain 1-2000 visible characters"));
|
|
1128
|
+
}
|
|
1129
|
+
if (!validRoute(input.route)) {
|
|
1130
|
+
return err(scenarioError("invalid-route", id.value, "Scenario routes must contain 1-256 visible characters"));
|
|
1131
|
+
}
|
|
1132
|
+
const runtime = parseLogicalRuntimeSnapshot(input.runtime ?? DEFAULT_LOGICAL_RUNTIME_SNAPSHOT);
|
|
1133
|
+
if (!runtime.ok) {
|
|
1134
|
+
return err(scenarioError("invalid-runtime", id.value, runtime.error.message));
|
|
1135
|
+
}
|
|
1136
|
+
const world = parseAndCloneWorld(input.world, parseWorld);
|
|
1137
|
+
if (!world.ok) {
|
|
1138
|
+
return err(scenarioError("invalid-world", id.value, world.error.message));
|
|
1139
|
+
}
|
|
1140
|
+
const definition = Object.freeze({
|
|
1141
|
+
id: id.value,
|
|
1142
|
+
title: input.title,
|
|
1143
|
+
description: input.description ?? null,
|
|
1144
|
+
route: input.route,
|
|
1145
|
+
world: world.value,
|
|
1146
|
+
runtime: runtime.value
|
|
1147
|
+
});
|
|
1148
|
+
definitions.push(definition);
|
|
1149
|
+
byId.set(id.value, definition);
|
|
1150
|
+
}
|
|
1151
|
+
const frozenDefinitions = Object.freeze(definitions);
|
|
1152
|
+
return ok(Object.freeze({
|
|
1153
|
+
size: frozenDefinitions.length,
|
|
1154
|
+
list: () => frozenDefinitions,
|
|
1155
|
+
get: (id) => byId.get(id),
|
|
1156
|
+
resolve: (input) => {
|
|
1157
|
+
const id = parseScenarioId(input);
|
|
1158
|
+
if (!id.ok) {
|
|
1159
|
+
return err(scenarioError("invalid-scenario", input, id.error.message));
|
|
1160
|
+
}
|
|
1161
|
+
const definition = byId.get(id.value);
|
|
1162
|
+
return definition === undefined ? err(scenarioError("unknown-scenario", id.value, `Unknown scenario: ${id.value}`)) : ok(definition);
|
|
1163
|
+
}
|
|
1164
|
+
}));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export { ok, err, isRecord, parseScenarioId, parseOperationId, parseCoverageKey, scenarioId, operationId, coverageKey, renderUnknownReason, DEFAULT_JSON_LIMITS, utf8ByteLength, parseExactJsonSource, parseJsonValue, canonicalJson, cloneJson, freezeJson, STABLE_HASH_ALGORITHM, tagStableHash, parseTaggedStableHash, stableHash, parseAndCloneWorld, DIRECT_COVERAGE_SCHEMA, MAX_DIRECT_COVERAGE_ENTRIES, EMPTY_COVERAGE_CATALOG_SNAPSHOT, createCoverageCatalogSnapshot, parseCoverageCatalogSnapshot, createCoverageCatalog, LOGICAL_RUNTIME_SCHEMA, MAX_HOST_TIMER_MILLISECONDS, DEFAULT_LOGICAL_RUNTIME_SNAPSHOT, parseLogicalRuntimeSnapshot, createLogicalRuntime, FIXTURE_SCHEMA, DEFAULT_MAX_FIXTURE_BYTES, parseFixtureEnvelope, parseFixtureJson, createFixtureEnvelope, serializeFixtureJson, SCENARIO_QUERY_KEY, FIXTURE_QUERY_KEY, maximumFixtureQueryBytes, DEFAULT_MAX_QUERY_BYTES, activateDirectScenario, parseDirectQuery, MAX_DIRECT_SCENARIOS, createScenarioCatalog };
|