@termwright/protocol 0.2.0 → 0.3.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 +213 -605
- package/dist/action-model-BP9Znu6L.d.ts +219 -0
- package/dist/action-model.d.ts +3 -0
- package/dist/action-model.js +15 -0
- package/dist/action-model.js.map +1 -0
- package/dist/capability-graph.d.ts +90 -0
- package/dist/capability-graph.js +43 -0
- package/dist/capability-graph.js.map +1 -0
- package/dist/chunk-B4VUTTUE.js +59 -0
- package/dist/chunk-B4VUTTUE.js.map +1 -0
- package/dist/chunk-CZK6NNP3.js +389 -0
- package/dist/chunk-CZK6NNP3.js.map +1 -0
- package/dist/chunk-ODOJRXL6.js +84 -0
- package/dist/chunk-ODOJRXL6.js.map +1 -0
- package/dist/chunk-PUXRCPGY.js +112 -0
- package/dist/chunk-PUXRCPGY.js.map +1 -0
- package/dist/chunk-VBLS6E6U.js +1109 -0
- package/dist/chunk-VBLS6E6U.js.map +1 -0
- package/dist/chunk-ZZIYHDJ4.js +202 -0
- package/dist/chunk-ZZIYHDJ4.js.map +1 -0
- package/dist/contract-CH9gmj2Y.d.ts +746 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +21 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +82 -798
- package/dist/index.js +1087 -766
- package/dist/index.js.map +1 -1
- package/dist/run-events.d.ts +158 -0
- package/dist/run-events.js +27 -0
- package/dist/run-events.js.map +1 -0
- package/dist/run-journal.d.ts +55 -0
- package/dist/run-journal.js +10 -0
- package/dist/run-journal.js.map +1 -0
- package/dist/run-state.d.ts +38 -0
- package/dist/run-state.js +19 -0
- package/dist/run-state.js.map +1 -0
- package/dist/test-provider.d.ts +22 -0
- package/dist/test-provider.js +32 -0
- package/dist/test-provider.js.map +1 -0
- package/package.json +33 -5
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
// src/run-events.ts
|
|
2
|
+
var RUN_ID_KINDS = Object.freeze([
|
|
3
|
+
"invocation",
|
|
4
|
+
"run",
|
|
5
|
+
"project",
|
|
6
|
+
"shard",
|
|
7
|
+
"spec",
|
|
8
|
+
"runner-task",
|
|
9
|
+
"execution",
|
|
10
|
+
"attempt",
|
|
11
|
+
"session",
|
|
12
|
+
"step",
|
|
13
|
+
"action",
|
|
14
|
+
"producer",
|
|
15
|
+
"event"
|
|
16
|
+
]);
|
|
17
|
+
var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
18
|
+
var ID_PATTERNS = Object.freeze(
|
|
19
|
+
Object.fromEntries(
|
|
20
|
+
RUN_ID_KINDS.map((kind) => [kind, new RegExp(`^${kind}:${UUID}$`, "u")])
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
function parseRunId(kind, value) {
|
|
24
|
+
if (typeof value !== "string" || !ID_PATTERNS[kind].test(value)) {
|
|
25
|
+
throw new TypeError(`${kind} id must use the canonical ${kind}:<uuid-v4> form`);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function createRunId(kind, randomUUID = browserRandomUuid) {
|
|
30
|
+
const uuid = randomUUID().toLowerCase();
|
|
31
|
+
return parseRunId(kind, `${kind}:${uuid}`);
|
|
32
|
+
}
|
|
33
|
+
var RunIdFactory = class {
|
|
34
|
+
#randomUUID;
|
|
35
|
+
#issued = /* @__PURE__ */ new Set();
|
|
36
|
+
constructor(randomUUID = browserRandomUuid) {
|
|
37
|
+
this.#randomUUID = randomUUID;
|
|
38
|
+
}
|
|
39
|
+
create(kind) {
|
|
40
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
41
|
+
const value = createRunId(kind, this.#randomUUID);
|
|
42
|
+
if (!this.#issued.has(value)) {
|
|
43
|
+
this.#issued.add(value);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw new Error("secure id source produced eight collisions");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function browserRandomUuid() {
|
|
51
|
+
const crypto = globalThis.crypto;
|
|
52
|
+
if (crypto?.randomUUID !== void 0) return crypto.randomUUID();
|
|
53
|
+
if (crypto?.getRandomValues === void 0) {
|
|
54
|
+
throw new Error("secure randomness is unavailable; pass an explicit randomUUID function");
|
|
55
|
+
}
|
|
56
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
57
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
58
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
59
|
+
const hex = [...bytes].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
60
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
61
|
+
}
|
|
62
|
+
function browserMonotonicNow() {
|
|
63
|
+
const performance = globalThis.performance;
|
|
64
|
+
if (performance === void 0)
|
|
65
|
+
throw new Error("a monotonic clock is unavailable; pass monotonicNow");
|
|
66
|
+
return performance.now();
|
|
67
|
+
}
|
|
68
|
+
var RUN_EVENT_CLASSES = Object.freeze(["authoritative", "state", "diagnostic"]);
|
|
69
|
+
var RUN_EVENT_VERSION = 2;
|
|
70
|
+
var DEFAULT_RUN_EVENT_LIMITS = Object.freeze({
|
|
71
|
+
maxEventBytes: 256 * 1024,
|
|
72
|
+
maxPayloadDepth: 32,
|
|
73
|
+
maxPayloadEntries: 1e4,
|
|
74
|
+
maxStringBytes: 16 * 1024,
|
|
75
|
+
maxCauses: 64
|
|
76
|
+
});
|
|
77
|
+
var RunEventProducer = class {
|
|
78
|
+
#producerId;
|
|
79
|
+
#epoch;
|
|
80
|
+
#ids;
|
|
81
|
+
#monotonicNow;
|
|
82
|
+
#wallNow;
|
|
83
|
+
#seq = 0;
|
|
84
|
+
#lastTime = 0;
|
|
85
|
+
constructor(options) {
|
|
86
|
+
if (!nonNegativeInteger(options.epoch))
|
|
87
|
+
throw new TypeError("producer epoch must be a non-negative safe integer");
|
|
88
|
+
parseRunId("producer", options.producerId);
|
|
89
|
+
this.#producerId = options.producerId;
|
|
90
|
+
this.#epoch = options.epoch;
|
|
91
|
+
this.#ids = new RunIdFactory(options.randomUUID);
|
|
92
|
+
this.#monotonicNow = options.monotonicNow ?? browserMonotonicNow;
|
|
93
|
+
this.#wallNow = options.wallNow;
|
|
94
|
+
}
|
|
95
|
+
emit(input, limits = DEFAULT_RUN_EVENT_LIMITS) {
|
|
96
|
+
if (!Number.isSafeInteger(this.#seq))
|
|
97
|
+
throw new Error("producer sequence exhausted the safe integer range");
|
|
98
|
+
const now = this.#monotonicNow();
|
|
99
|
+
if (!time(now) || now < this.#lastTime)
|
|
100
|
+
throw new Error("producer monotonic clock moved backwards");
|
|
101
|
+
const event = createRunEvent(
|
|
102
|
+
{
|
|
103
|
+
eventId: this.#ids.create("event"),
|
|
104
|
+
producerId: this.#producerId,
|
|
105
|
+
epoch: this.#epoch,
|
|
106
|
+
seq: this.#seq,
|
|
107
|
+
eventClass: input.eventClass,
|
|
108
|
+
type: input.type,
|
|
109
|
+
monotonicTime: now,
|
|
110
|
+
...this.#wallNow === void 0 ? {} : { wallTime: this.#wallNow() },
|
|
111
|
+
identity: input.identity,
|
|
112
|
+
...input.causedBy === void 0 ? {} : { causedBy: input.causedBy },
|
|
113
|
+
payload: input.payload
|
|
114
|
+
},
|
|
115
|
+
limits
|
|
116
|
+
);
|
|
117
|
+
this.#seq += 1;
|
|
118
|
+
this.#lastTime = now;
|
|
119
|
+
return event;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function createRunEvent(input, limits = DEFAULT_RUN_EVENT_LIMITS) {
|
|
123
|
+
const candidate = {
|
|
124
|
+
v: RUN_EVENT_VERSION,
|
|
125
|
+
eventId: input.eventId ?? createRunId("event", input.randomUUID),
|
|
126
|
+
producerId: input.producerId,
|
|
127
|
+
epoch: input.epoch,
|
|
128
|
+
seq: input.seq,
|
|
129
|
+
eventClass: input.eventClass,
|
|
130
|
+
type: input.type,
|
|
131
|
+
monotonicTime: input.monotonicTime,
|
|
132
|
+
...input.wallTime === void 0 ? {} : { wallTime: input.wallTime },
|
|
133
|
+
identity: input.identity,
|
|
134
|
+
...input.causedBy === void 0 ? {} : { causedBy: input.causedBy },
|
|
135
|
+
payload: input.payload
|
|
136
|
+
};
|
|
137
|
+
const parsed = validateRunEvent(candidate, limits);
|
|
138
|
+
if (!parsed.ok) throw new TypeError(`${parsed.code}: ${parsed.detail}`);
|
|
139
|
+
return parsed.value;
|
|
140
|
+
}
|
|
141
|
+
function validateRunEvent(value, limits = DEFAULT_RUN_EVENT_LIMITS) {
|
|
142
|
+
const checkedLimits = validateLimits(limits);
|
|
143
|
+
if (checkedLimits !== null) return checkedLimits;
|
|
144
|
+
const projected = projectJson(value, limits);
|
|
145
|
+
if (!projected.ok) return projected;
|
|
146
|
+
const event = projected.value;
|
|
147
|
+
if (!isRecord(event) || event["v"] !== RUN_EVENT_VERSION)
|
|
148
|
+
return fail("invalid-envelope", `v must be exactly ${RUN_EVENT_VERSION}`);
|
|
149
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
150
|
+
"v",
|
|
151
|
+
"eventId",
|
|
152
|
+
"producerId",
|
|
153
|
+
"epoch",
|
|
154
|
+
"seq",
|
|
155
|
+
"eventClass",
|
|
156
|
+
"type",
|
|
157
|
+
"monotonicTime",
|
|
158
|
+
"wallTime",
|
|
159
|
+
"identity",
|
|
160
|
+
"causedBy",
|
|
161
|
+
"payload"
|
|
162
|
+
]);
|
|
163
|
+
if (Object.keys(event).some((key) => !allowed.has(key)))
|
|
164
|
+
return fail("invalid-envelope", "event contains an unknown field");
|
|
165
|
+
if (!id("event", event["eventId"])) return fail("invalid-id", "eventId is invalid");
|
|
166
|
+
if (!id("producer", event["producerId"])) return fail("invalid-id", "producerId is invalid");
|
|
167
|
+
if (!nonNegativeInteger(event["epoch"]))
|
|
168
|
+
return fail("invalid-envelope", "epoch must be a non-negative safe integer");
|
|
169
|
+
if (!nonNegativeInteger(event["seq"]))
|
|
170
|
+
return fail("invalid-envelope", "seq must be a non-negative safe integer");
|
|
171
|
+
if (!RUN_EVENT_CLASSES.includes(event["eventClass"]))
|
|
172
|
+
return fail("invalid-envelope", "eventClass is invalid");
|
|
173
|
+
if (typeof event["type"] !== "string" || !/^[a-z][a-z0-9]*(?:[./:-][a-z0-9]+)*$/u.test(event["type"]) || utf8(event["type"]) > 128) {
|
|
174
|
+
return fail("invalid-envelope", "type must be a bounded lowercase namespaced token");
|
|
175
|
+
}
|
|
176
|
+
if (!time(event["monotonicTime"]))
|
|
177
|
+
return fail("invalid-envelope", "monotonicTime must be a finite non-negative number");
|
|
178
|
+
if (event["wallTime"] !== void 0 && !time(event["wallTime"]))
|
|
179
|
+
return fail("invalid-envelope", "wallTime must be a finite non-negative number");
|
|
180
|
+
const identityFailure = validateIdentity(event["identity"]);
|
|
181
|
+
if (identityFailure !== null) return identityFailure;
|
|
182
|
+
if (event["causedBy"] !== void 0) {
|
|
183
|
+
if (!Array.isArray(event["causedBy"]) || event["causedBy"].length > limits.maxCauses)
|
|
184
|
+
return fail("invalid-envelope", "causedBy exceeds its bound");
|
|
185
|
+
const causes = event["causedBy"];
|
|
186
|
+
if (causes.some((cause) => !id("event", cause)))
|
|
187
|
+
return fail("invalid-id", "causedBy contains an invalid event id");
|
|
188
|
+
if (new Set(causes).size !== causes.length)
|
|
189
|
+
return fail("invalid-envelope", "causedBy contains duplicates");
|
|
190
|
+
if (causes.includes(event["eventId"]))
|
|
191
|
+
return fail("causal-cycle", "an event cannot cause itself");
|
|
192
|
+
}
|
|
193
|
+
const bytes = utf8(JSON.stringify(event));
|
|
194
|
+
if (bytes > limits.maxEventBytes)
|
|
195
|
+
return fail("event-oversized", `event is ${bytes} bytes, ceiling is ${limits.maxEventBytes}`);
|
|
196
|
+
return { ok: true, value: event };
|
|
197
|
+
}
|
|
198
|
+
var RunEventStreamValidator = class {
|
|
199
|
+
#limits;
|
|
200
|
+
#eventIds = /* @__PURE__ */ new Set();
|
|
201
|
+
#sequenceKeys = /* @__PURE__ */ new Set();
|
|
202
|
+
#producerEpoch = /* @__PURE__ */ new Map();
|
|
203
|
+
#last = /* @__PURE__ */ new Map();
|
|
204
|
+
#causes = /* @__PURE__ */ new Map();
|
|
205
|
+
constructor(limits = DEFAULT_RUN_EVENT_LIMITS) {
|
|
206
|
+
const failure = validateLimits(limits);
|
|
207
|
+
if (failure !== null) throw new TypeError(failure.detail);
|
|
208
|
+
this.#limits = limits;
|
|
209
|
+
}
|
|
210
|
+
accept(value) {
|
|
211
|
+
const parsed = validateRunEvent(value, this.#limits);
|
|
212
|
+
if (!parsed.ok) return parsed;
|
|
213
|
+
const event = parsed.value;
|
|
214
|
+
if (this.#eventIds.has(event.eventId))
|
|
215
|
+
return fail("event-collision", `event id ${event.eventId} was already observed`);
|
|
216
|
+
const sequenceKey = `${event.producerId}/${event.epoch}/${event.seq}`;
|
|
217
|
+
if (this.#sequenceKeys.has(sequenceKey))
|
|
218
|
+
return fail("event-collision", "producer epoch/sequence was already observed");
|
|
219
|
+
const newestEpoch = this.#producerEpoch.get(event.producerId);
|
|
220
|
+
if (newestEpoch !== void 0 && event.epoch < newestEpoch)
|
|
221
|
+
return fail("epoch-regression", "producer epoch moved backwards");
|
|
222
|
+
const incarnation = `${event.producerId}/${event.epoch}`;
|
|
223
|
+
const previous = this.#last.get(incarnation);
|
|
224
|
+
if (previous !== void 0 && event.seq <= previous.seq)
|
|
225
|
+
return fail("sequence-regression", "producer sequence did not increase");
|
|
226
|
+
if (previous !== void 0 && event.monotonicTime < previous.monotonicTime)
|
|
227
|
+
return fail("monotonic-time-regression", "producer monotonic time moved backwards");
|
|
228
|
+
const causes = event.causedBy ?? [];
|
|
229
|
+
this.#causes.set(event.eventId, causes);
|
|
230
|
+
if (this.#hasCycle(event.eventId)) {
|
|
231
|
+
this.#causes.delete(event.eventId);
|
|
232
|
+
return fail("causal-cycle", "causedBy introduces a cycle");
|
|
233
|
+
}
|
|
234
|
+
this.#eventIds.add(event.eventId);
|
|
235
|
+
this.#sequenceKeys.add(sequenceKey);
|
|
236
|
+
this.#producerEpoch.set(event.producerId, Math.max(newestEpoch ?? event.epoch, event.epoch));
|
|
237
|
+
this.#last.set(incarnation, { seq: event.seq, monotonicTime: event.monotonicTime });
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
#hasCycle(start) {
|
|
241
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
242
|
+
const visited = /* @__PURE__ */ new Set();
|
|
243
|
+
const visit = (idValue) => {
|
|
244
|
+
if (visiting.has(idValue)) return true;
|
|
245
|
+
if (visited.has(idValue)) return false;
|
|
246
|
+
visiting.add(idValue);
|
|
247
|
+
for (const cause of this.#causes.get(idValue) ?? []) {
|
|
248
|
+
if (visit(cause)) return true;
|
|
249
|
+
}
|
|
250
|
+
visiting.delete(idValue);
|
|
251
|
+
visited.add(idValue);
|
|
252
|
+
return false;
|
|
253
|
+
};
|
|
254
|
+
return visit(start);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
function validateIdentity(value) {
|
|
258
|
+
if (!isRecord(value)) return fail("invalid-identity", "identity must be an object");
|
|
259
|
+
const fields = Object.freeze([
|
|
260
|
+
["invocationId", "invocation"],
|
|
261
|
+
["runId", "run"],
|
|
262
|
+
["projectId", "project"],
|
|
263
|
+
["shardId", "shard"],
|
|
264
|
+
["specId", "spec"],
|
|
265
|
+
["runnerTaskId", "runner-task"],
|
|
266
|
+
["executionId", "execution"],
|
|
267
|
+
["attemptId", "attempt"],
|
|
268
|
+
["sessionId", "session"],
|
|
269
|
+
["stepId", "step"],
|
|
270
|
+
["actionId", "action"]
|
|
271
|
+
]);
|
|
272
|
+
if (Object.keys(value).some((key) => !fields.some(([field]) => field === key)))
|
|
273
|
+
return fail("invalid-identity", "identity contains an unknown field");
|
|
274
|
+
for (const [field, kind] of fields) {
|
|
275
|
+
if ((field === "invocationId" || value[field] !== void 0) && !id(kind, value[field]))
|
|
276
|
+
return fail("invalid-id", `${field} is invalid`);
|
|
277
|
+
}
|
|
278
|
+
const needs = (child, parent) => value[child] !== void 0 && value[parent] === void 0 ? fail("invalid-identity", `${child} requires ${parent}`) : null;
|
|
279
|
+
return needs("runId", "invocationId") ?? needs("projectId", "runId") ?? needs("shardId", "runId") ?? needs("specId", "projectId") ?? needs("runnerTaskId", "specId") ?? needs("executionId", "runnerTaskId") ?? needs("attemptId", "executionId") ?? needs("sessionId", "attemptId") ?? needs("stepId", "attemptId") ?? needs("actionId", "sessionId");
|
|
280
|
+
}
|
|
281
|
+
function validateLimits(limits) {
|
|
282
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
283
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
284
|
+
return fail("invalid-envelope", `${name} must be a positive safe integer`);
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
var ProjectionFailure = class {
|
|
289
|
+
constructor(failure) {
|
|
290
|
+
this.failure = failure;
|
|
291
|
+
}
|
|
292
|
+
failure;
|
|
293
|
+
};
|
|
294
|
+
function reject(code, detail) {
|
|
295
|
+
return new ProjectionFailure(fail(code, detail));
|
|
296
|
+
}
|
|
297
|
+
function projectJson(value, limits) {
|
|
298
|
+
let entries = 0;
|
|
299
|
+
const seen = /* @__PURE__ */ new Set();
|
|
300
|
+
const visit = (current, depth) => {
|
|
301
|
+
if (current === null || typeof current === "boolean" || typeof current === "string") {
|
|
302
|
+
if (typeof current === "string" && utf8(current) > limits.maxStringBytes)
|
|
303
|
+
return reject("invalid-payload", "a string exceeds maxStringBytes");
|
|
304
|
+
return current;
|
|
305
|
+
}
|
|
306
|
+
if (typeof current === "number")
|
|
307
|
+
return Number.isFinite(current) ? current : reject("invalid-payload", "numbers must be finite");
|
|
308
|
+
if (typeof current !== "object") return reject("invalid-payload", "payload is not JSON data");
|
|
309
|
+
if (depth > limits.maxPayloadDepth)
|
|
310
|
+
return reject("invalid-payload", "payload exceeds maxPayloadDepth");
|
|
311
|
+
if (seen.has(current)) return reject("invalid-payload", "payload aliases or cycles an object");
|
|
312
|
+
seen.add(current);
|
|
313
|
+
try {
|
|
314
|
+
const symbols = Object.getOwnPropertySymbols(current);
|
|
315
|
+
if (symbols.length > 0) return reject("invalid-payload", "payload contains symbol keys");
|
|
316
|
+
if (Array.isArray(current)) {
|
|
317
|
+
if (Object.keys(current).length !== current.length)
|
|
318
|
+
return reject("invalid-payload", "payload contains a sparse array or extra array keys");
|
|
319
|
+
const result2 = [];
|
|
320
|
+
for (const item of current) {
|
|
321
|
+
entries += 1;
|
|
322
|
+
if (entries > limits.maxPayloadEntries)
|
|
323
|
+
return reject("invalid-payload", "payload exceeds maxPayloadEntries");
|
|
324
|
+
const child = visit(item, depth + 1);
|
|
325
|
+
if (child instanceof ProjectionFailure) return child;
|
|
326
|
+
result2.push(child);
|
|
327
|
+
}
|
|
328
|
+
return Object.freeze(result2);
|
|
329
|
+
}
|
|
330
|
+
const prototype = Object.getPrototypeOf(current);
|
|
331
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
332
|
+
return reject("invalid-payload", "payload object has a non-plain prototype");
|
|
333
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
334
|
+
for (const key of Object.keys(current)) {
|
|
335
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype")
|
|
336
|
+
return reject("invalid-payload", "payload contains a reserved key");
|
|
337
|
+
if (utf8(key) > limits.maxStringBytes)
|
|
338
|
+
return reject("invalid-payload", "a payload key exceeds maxStringBytes");
|
|
339
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
340
|
+
if (descriptor === void 0 || !("value" in descriptor))
|
|
341
|
+
return reject("invalid-payload", "payload contains an accessor");
|
|
342
|
+
entries += 1;
|
|
343
|
+
if (entries > limits.maxPayloadEntries)
|
|
344
|
+
return reject("invalid-payload", "payload exceeds maxPayloadEntries");
|
|
345
|
+
const child = visit(descriptor.value, depth + 1);
|
|
346
|
+
if (child instanceof ProjectionFailure) return child;
|
|
347
|
+
result[key] = child;
|
|
348
|
+
}
|
|
349
|
+
return Object.freeze(result);
|
|
350
|
+
} catch {
|
|
351
|
+
return reject("invalid-payload", "payload object could not be inspected safely");
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const projected = visit(value, 0);
|
|
355
|
+
return projected instanceof ProjectionFailure ? projected.failure : { ok: true, value: projected };
|
|
356
|
+
}
|
|
357
|
+
function isRecord(value) {
|
|
358
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
359
|
+
}
|
|
360
|
+
function fail(code, detail) {
|
|
361
|
+
return Object.freeze({ ok: false, code, detail });
|
|
362
|
+
}
|
|
363
|
+
function id(kind, value) {
|
|
364
|
+
return typeof value === "string" && ID_PATTERNS[kind].test(value);
|
|
365
|
+
}
|
|
366
|
+
function nonNegativeInteger(value) {
|
|
367
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
368
|
+
}
|
|
369
|
+
function time(value) {
|
|
370
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
371
|
+
}
|
|
372
|
+
function utf8(value) {
|
|
373
|
+
return new TextEncoder().encode(value).byteLength;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export {
|
|
377
|
+
RUN_ID_KINDS,
|
|
378
|
+
parseRunId,
|
|
379
|
+
createRunId,
|
|
380
|
+
RunIdFactory,
|
|
381
|
+
RUN_EVENT_CLASSES,
|
|
382
|
+
RUN_EVENT_VERSION,
|
|
383
|
+
DEFAULT_RUN_EVENT_LIMITS,
|
|
384
|
+
RunEventProducer,
|
|
385
|
+
createRunEvent,
|
|
386
|
+
validateRunEvent,
|
|
387
|
+
RunEventStreamValidator
|
|
388
|
+
};
|
|
389
|
+
//# sourceMappingURL=chunk-CZK6NNP3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/run-events.ts"],"sourcesContent":["/**\n * Browser-safe identity and event envelope for one Termwright invocation.\n *\n * These identifiers describe orchestration, not framework semantics. Every\n * kind has a distinct wire prefix, so an execution id can never be accepted as\n * an attempt id merely because both happen to contain the same random bits.\n */\n\nexport const RUN_ID_KINDS = Object.freeze([\n 'invocation',\n 'run',\n 'project',\n 'shard',\n 'spec',\n 'runner-task',\n 'execution',\n 'attempt',\n 'session',\n 'step',\n 'action',\n 'producer',\n 'event',\n] as const);\n\nexport type RunIdKind = (typeof RUN_ID_KINDS)[number];\n\ndeclare const runIdBrand: unique symbol;\ntype BrandedRunId<Kind extends RunIdKind> = string & {\n readonly [runIdBrand]: Kind;\n};\n\nexport type InvocationId = BrandedRunId<'invocation'>;\nexport type RunId = BrandedRunId<'run'>;\nexport type ProjectId = BrandedRunId<'project'>;\nexport type ShardId = BrandedRunId<'shard'>;\nexport type SpecId = BrandedRunId<'spec'>;\nexport type RunnerTaskId = BrandedRunId<'runner-task'>;\nexport type ExecutionId = BrandedRunId<'execution'>;\nexport type AttemptId = BrandedRunId<'attempt'>;\nexport type SessionId = BrandedRunId<'session'>;\nexport type StepId = BrandedRunId<'step'>;\nexport type ActionId = BrandedRunId<'action'>;\nexport type RunEventProducerId = BrandedRunId<'producer'>;\nexport type RunEventId = BrandedRunId<'event'>;\n\nexport interface RunIdByKind {\n readonly invocation: InvocationId;\n readonly run: RunId;\n readonly project: ProjectId;\n readonly shard: ShardId;\n readonly spec: SpecId;\n readonly 'runner-task': RunnerTaskId;\n readonly execution: ExecutionId;\n readonly attempt: AttemptId;\n readonly session: SessionId;\n readonly step: StepId;\n readonly action: ActionId;\n readonly producer: RunEventProducerId;\n readonly event: RunEventId;\n}\n\nconst UUID = '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';\nconst ID_PATTERNS: Readonly<Record<RunIdKind, RegExp>> = Object.freeze(\n Object.fromEntries(\n RUN_ID_KINDS.map((kind) => [kind, new RegExp(`^${kind}:${UUID}$`, 'u')]),\n ) as Record<RunIdKind, RegExp>,\n);\n\n/** Validate and brand an id received from an untrusted or persisted source. */\nexport function parseRunId<Kind extends RunIdKind>(kind: Kind, value: unknown): RunIdByKind[Kind] {\n if (typeof value !== 'string' || !ID_PATTERNS[kind].test(value)) {\n throw new TypeError(`${kind} id must use the canonical ${kind}:<uuid-v4> form`);\n }\n return value as RunIdByKind[Kind];\n}\n\n/**\n * Mint a collision-resistant, kind-separated id without importing Node APIs.\n * `randomUUID` is injectable for deterministic tests and embedded runtimes.\n */\nexport function createRunId<Kind extends RunIdKind>(\n kind: Kind,\n randomUUID: () => string = browserRandomUuid,\n): RunIdByKind[Kind] {\n const uuid = randomUUID().toLowerCase();\n return parseRunId(kind, `${kind}:${uuid}`);\n}\n\n/** Scoped issuer that detects a broken randomness source before ids escape. */\nexport class RunIdFactory {\n readonly #randomUUID: () => string;\n readonly #issued = new Set<string>();\n\n constructor(randomUUID: () => string = browserRandomUuid) {\n this.#randomUUID = randomUUID;\n }\n\n create<Kind extends RunIdKind>(kind: Kind): RunIdByKind[Kind] {\n for (let attempt = 0; attempt < 8; attempt += 1) {\n const value = createRunId(kind, this.#randomUUID);\n if (!this.#issued.has(value)) {\n this.#issued.add(value);\n return value;\n }\n }\n throw new Error('secure id source produced eight collisions');\n }\n}\n\nfunction browserRandomUuid(): string {\n const crypto = (\n globalThis as {\n readonly crypto?: {\n randomUUID?: () => string;\n getRandomValues?<T extends ArrayBufferView>(value: T): T;\n };\n }\n ).crypto;\n if (crypto?.randomUUID !== undefined) return crypto.randomUUID();\n if (crypto?.getRandomValues === undefined) {\n throw new Error('secure randomness is unavailable; pass an explicit randomUUID function');\n }\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n const hex = [...bytes].map((value) => value.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nfunction browserMonotonicNow(): number {\n const performance = (globalThis as { readonly performance?: { now(): number } }).performance;\n if (performance === undefined)\n throw new Error('a monotonic clock is unavailable; pass monotonicNow');\n return performance.now();\n}\n\nexport const RUN_EVENT_CLASSES = Object.freeze(['authoritative', 'state', 'diagnostic'] as const);\nexport type RunEventClass = (typeof RUN_EVENT_CLASSES)[number];\nexport const RUN_EVENT_VERSION = 2 as const;\n\nexport type RunEventJson =\n | null\n | boolean\n | number\n | string\n | readonly RunEventJson[]\n | { readonly [key: string]: RunEventJson };\n\n/** Hierarchical identity carried by every event. */\nexport interface RunEventIdentity {\n readonly invocationId: InvocationId;\n readonly runId?: RunId;\n readonly projectId?: ProjectId;\n readonly shardId?: ShardId;\n readonly specId?: SpecId;\n readonly runnerTaskId?: RunnerTaskId;\n readonly executionId?: ExecutionId;\n readonly attemptId?: AttemptId;\n readonly sessionId?: SessionId;\n readonly stepId?: StepId;\n readonly actionId?: ActionId;\n}\n\n/** Version 2 is the only accepted event shape; there is no legacy envelope. */\nexport interface RunEvent<\n Type extends string = string,\n Payload extends RunEventJson = RunEventJson,\n> {\n readonly v: typeof RUN_EVENT_VERSION;\n readonly eventId: RunEventId;\n readonly producerId: RunEventProducerId;\n /** Producer incarnation. A restarted producer increments this value. */\n readonly epoch: number;\n /** Strictly increasing within one producer incarnation. */\n readonly seq: number;\n readonly eventClass: RunEventClass;\n readonly type: Type;\n /** Milliseconds from the producer's monotonic clock. */\n readonly monotonicTime: number;\n /** Optional Unix epoch milliseconds, for human correlation only. */\n readonly wallTime?: number;\n readonly identity: RunEventIdentity;\n readonly causedBy?: readonly RunEventId[];\n readonly payload: Payload;\n}\n\nexport interface RunEventLimits {\n readonly maxEventBytes: number;\n readonly maxPayloadDepth: number;\n readonly maxPayloadEntries: number;\n readonly maxStringBytes: number;\n readonly maxCauses: number;\n}\n\nexport const DEFAULT_RUN_EVENT_LIMITS: RunEventLimits = Object.freeze({\n maxEventBytes: 256 * 1024,\n maxPayloadDepth: 32,\n maxPayloadEntries: 10_000,\n maxStringBytes: 16 * 1024,\n maxCauses: 64,\n});\n\nexport type RunEventViolationCode =\n | 'invalid-envelope'\n | 'invalid-id'\n | 'invalid-identity'\n | 'invalid-payload'\n | 'event-oversized'\n | 'event-collision'\n | 'epoch-regression'\n | 'sequence-regression'\n | 'monotonic-time-regression'\n | 'causal-cycle';\n\nexport type RunEventValidationResult =\n | { readonly ok: true; readonly value: RunEvent }\n | { readonly ok: false; readonly code: RunEventViolationCode; readonly detail: string };\n\ntype RunEventFailure = Extract<RunEventValidationResult, { readonly ok: false }>;\n\nexport interface CreateRunEventInput<Type extends string, Payload extends RunEventJson> {\n readonly eventId?: RunEventId;\n readonly producerId: RunEventProducerId;\n readonly epoch: number;\n readonly seq: number;\n readonly eventClass: RunEventClass;\n readonly type: Type;\n readonly monotonicTime: number;\n readonly wallTime?: number;\n readonly identity: RunEventIdentity;\n readonly causedBy?: readonly RunEventId[];\n readonly payload: Payload;\n readonly randomUUID?: () => string;\n}\n\nexport interface ProduceRunEventInput<Type extends string, Payload extends RunEventJson> {\n readonly eventClass: RunEventClass;\n readonly type: Type;\n readonly identity: RunEventIdentity;\n readonly causedBy?: readonly RunEventId[];\n readonly payload: Payload;\n}\n\n/**\n * Producer-side constructor owning epoch, sequence, event ids and clock.\n * Callers therefore cannot accidentally reuse a journal coordinate.\n */\nexport class RunEventProducer {\n readonly #producerId: RunEventProducerId;\n readonly #epoch: number;\n readonly #ids: RunIdFactory;\n readonly #monotonicNow: () => number;\n readonly #wallNow: (() => number) | undefined;\n #seq = 0;\n #lastTime = 0;\n\n constructor(options: {\n readonly producerId: RunEventProducerId;\n readonly epoch: number;\n readonly randomUUID?: () => string;\n readonly monotonicNow?: () => number;\n readonly wallNow?: () => number;\n }) {\n if (!nonNegativeInteger(options.epoch))\n throw new TypeError('producer epoch must be a non-negative safe integer');\n parseRunId('producer', options.producerId);\n this.#producerId = options.producerId;\n this.#epoch = options.epoch;\n this.#ids = new RunIdFactory(options.randomUUID);\n this.#monotonicNow = options.monotonicNow ?? browserMonotonicNow;\n this.#wallNow = options.wallNow;\n }\n\n emit<Type extends string, Payload extends RunEventJson>(\n input: ProduceRunEventInput<Type, Payload>,\n limits: RunEventLimits = DEFAULT_RUN_EVENT_LIMITS,\n ): RunEvent<Type, Payload> {\n if (!Number.isSafeInteger(this.#seq))\n throw new Error('producer sequence exhausted the safe integer range');\n const now = this.#monotonicNow();\n if (!time(now) || now < this.#lastTime)\n throw new Error('producer monotonic clock moved backwards');\n const event = createRunEvent(\n {\n eventId: this.#ids.create('event'),\n producerId: this.#producerId,\n epoch: this.#epoch,\n seq: this.#seq,\n eventClass: input.eventClass,\n type: input.type,\n monotonicTime: now,\n ...(this.#wallNow === undefined ? {} : { wallTime: this.#wallNow() }),\n identity: input.identity,\n ...(input.causedBy === undefined ? {} : { causedBy: input.causedBy }),\n payload: input.payload,\n },\n limits,\n );\n this.#seq += 1;\n this.#lastTime = now;\n return event;\n }\n}\n\n/** Construct and validate one deeply immutable envelope. */\nexport function createRunEvent<Type extends string, Payload extends RunEventJson>(\n input: CreateRunEventInput<Type, Payload>,\n limits: RunEventLimits = DEFAULT_RUN_EVENT_LIMITS,\n): RunEvent<Type, Payload> {\n const candidate = {\n v: RUN_EVENT_VERSION,\n eventId: input.eventId ?? createRunId('event', input.randomUUID),\n producerId: input.producerId,\n epoch: input.epoch,\n seq: input.seq,\n eventClass: input.eventClass,\n type: input.type,\n monotonicTime: input.monotonicTime,\n ...(input.wallTime === undefined ? {} : { wallTime: input.wallTime }),\n identity: input.identity,\n ...(input.causedBy === undefined ? {} : { causedBy: input.causedBy }),\n payload: input.payload,\n };\n const parsed = validateRunEvent(candidate, limits);\n if (!parsed.ok) throw new TypeError(`${parsed.code}: ${parsed.detail}`);\n return parsed.value as RunEvent<Type, Payload>;\n}\n\n/** Validate, copy and deeply freeze a single untrusted event. */\nexport function validateRunEvent(\n value: unknown,\n limits: RunEventLimits = DEFAULT_RUN_EVENT_LIMITS,\n): RunEventValidationResult {\n const checkedLimits = validateLimits(limits);\n if (checkedLimits !== null) return checkedLimits;\n const projected = projectJson(value, limits);\n if (!projected.ok) return projected;\n const event = projected.value;\n if (!isRecord(event) || event['v'] !== RUN_EVENT_VERSION)\n return fail('invalid-envelope', `v must be exactly ${RUN_EVENT_VERSION}`);\n const allowed = new Set([\n 'v',\n 'eventId',\n 'producerId',\n 'epoch',\n 'seq',\n 'eventClass',\n 'type',\n 'monotonicTime',\n 'wallTime',\n 'identity',\n 'causedBy',\n 'payload',\n ]);\n if (Object.keys(event).some((key) => !allowed.has(key)))\n return fail('invalid-envelope', 'event contains an unknown field');\n if (!id('event', event['eventId'])) return fail('invalid-id', 'eventId is invalid');\n if (!id('producer', event['producerId'])) return fail('invalid-id', 'producerId is invalid');\n if (!nonNegativeInteger(event['epoch']))\n return fail('invalid-envelope', 'epoch must be a non-negative safe integer');\n if (!nonNegativeInteger(event['seq']))\n return fail('invalid-envelope', 'seq must be a non-negative safe integer');\n if (!RUN_EVENT_CLASSES.includes(event['eventClass'] as RunEventClass))\n return fail('invalid-envelope', 'eventClass is invalid');\n if (\n typeof event['type'] !== 'string' ||\n !/^[a-z][a-z0-9]*(?:[./:-][a-z0-9]+)*$/u.test(event['type']) ||\n utf8(event['type']) > 128\n ) {\n return fail('invalid-envelope', 'type must be a bounded lowercase namespaced token');\n }\n if (!time(event['monotonicTime']))\n return fail('invalid-envelope', 'monotonicTime must be a finite non-negative number');\n if (event['wallTime'] !== undefined && !time(event['wallTime']))\n return fail('invalid-envelope', 'wallTime must be a finite non-negative number');\n const identityFailure = validateIdentity(event['identity']);\n if (identityFailure !== null) return identityFailure;\n if (event['causedBy'] !== undefined) {\n if (!Array.isArray(event['causedBy']) || event['causedBy'].length > limits.maxCauses)\n return fail('invalid-envelope', 'causedBy exceeds its bound');\n const causes = event['causedBy'];\n if (causes.some((cause) => !id('event', cause)))\n return fail('invalid-id', 'causedBy contains an invalid event id');\n if (new Set(causes).size !== causes.length)\n return fail('invalid-envelope', 'causedBy contains duplicates');\n if (causes.includes(event['eventId']))\n return fail('causal-cycle', 'an event cannot cause itself');\n }\n const bytes = utf8(JSON.stringify(event));\n if (bytes > limits.maxEventBytes)\n return fail('event-oversized', `event is ${bytes} bytes, ceiling is ${limits.maxEventBytes}`);\n return { ok: true, value: event as unknown as RunEvent };\n}\n\n/** Stateful collision, ordering and causal-DAG validator for merged streams. */\nexport class RunEventStreamValidator {\n readonly #limits: RunEventLimits;\n readonly #eventIds = new Set<string>();\n readonly #sequenceKeys = new Set<string>();\n readonly #producerEpoch = new Map<string, number>();\n readonly #last = new Map<string, { readonly seq: number; readonly monotonicTime: number }>();\n readonly #causes = new Map<string, readonly string[]>();\n\n constructor(limits: RunEventLimits = DEFAULT_RUN_EVENT_LIMITS) {\n const failure = validateLimits(limits);\n if (failure !== null) throw new TypeError(failure.detail);\n this.#limits = limits;\n }\n\n accept(value: unknown): RunEventValidationResult {\n const parsed = validateRunEvent(value, this.#limits);\n if (!parsed.ok) return parsed;\n const event = parsed.value;\n if (this.#eventIds.has(event.eventId))\n return fail('event-collision', `event id ${event.eventId} was already observed`);\n const sequenceKey = `${event.producerId}/${event.epoch}/${event.seq}`;\n if (this.#sequenceKeys.has(sequenceKey))\n return fail('event-collision', 'producer epoch/sequence was already observed');\n const newestEpoch = this.#producerEpoch.get(event.producerId);\n if (newestEpoch !== undefined && event.epoch < newestEpoch)\n return fail('epoch-regression', 'producer epoch moved backwards');\n const incarnation = `${event.producerId}/${event.epoch}`;\n const previous = this.#last.get(incarnation);\n if (previous !== undefined && event.seq <= previous.seq)\n return fail('sequence-regression', 'producer sequence did not increase');\n if (previous !== undefined && event.monotonicTime < previous.monotonicTime)\n return fail('monotonic-time-regression', 'producer monotonic time moved backwards');\n\n const causes = event.causedBy ?? [];\n this.#causes.set(event.eventId, causes);\n if (this.#hasCycle(event.eventId)) {\n this.#causes.delete(event.eventId);\n return fail('causal-cycle', 'causedBy introduces a cycle');\n }\n this.#eventIds.add(event.eventId);\n this.#sequenceKeys.add(sequenceKey);\n this.#producerEpoch.set(event.producerId, Math.max(newestEpoch ?? event.epoch, event.epoch));\n this.#last.set(incarnation, { seq: event.seq, monotonicTime: event.monotonicTime });\n return parsed;\n }\n\n #hasCycle(start: string): boolean {\n const visiting = new Set<string>();\n const visited = new Set<string>();\n const visit = (idValue: string): boolean => {\n if (visiting.has(idValue)) return true;\n if (visited.has(idValue)) return false;\n visiting.add(idValue);\n for (const cause of this.#causes.get(idValue) ?? []) {\n if (visit(cause)) return true;\n }\n visiting.delete(idValue);\n visited.add(idValue);\n return false;\n };\n return visit(start);\n }\n}\n\nfunction validateIdentity(value: RunEventJson | undefined): RunEventFailure | null {\n if (!isRecord(value)) return fail('invalid-identity', 'identity must be an object');\n const fields = Object.freeze([\n ['invocationId', 'invocation'],\n ['runId', 'run'],\n ['projectId', 'project'],\n ['shardId', 'shard'],\n ['specId', 'spec'],\n ['runnerTaskId', 'runner-task'],\n ['executionId', 'execution'],\n ['attemptId', 'attempt'],\n ['sessionId', 'session'],\n ['stepId', 'step'],\n ['actionId', 'action'],\n ] as const);\n if (Object.keys(value).some((key) => !fields.some(([field]) => field === key)))\n return fail('invalid-identity', 'identity contains an unknown field');\n for (const [field, kind] of fields) {\n if ((field === 'invocationId' || value[field] !== undefined) && !id(kind, value[field]))\n return fail('invalid-id', `${field} is invalid`);\n }\n const needs = (child: string, parent: string): RunEventFailure | null =>\n value[child] !== undefined && value[parent] === undefined\n ? fail('invalid-identity', `${child} requires ${parent}`)\n : null;\n return (\n needs('runId', 'invocationId') ??\n needs('projectId', 'runId') ??\n needs('shardId', 'runId') ??\n needs('specId', 'projectId') ??\n needs('runnerTaskId', 'specId') ??\n needs('executionId', 'runnerTaskId') ??\n needs('attemptId', 'executionId') ??\n needs('sessionId', 'attemptId') ??\n needs('stepId', 'attemptId') ??\n needs('actionId', 'sessionId')\n );\n}\n\nfunction validateLimits(limits: RunEventLimits): RunEventFailure | null {\n for (const [name, value] of Object.entries(limits)) {\n if (!Number.isSafeInteger(value) || value <= 0)\n return fail('invalid-envelope', `${name} must be a positive safe integer`);\n }\n return null;\n}\n\n/**\n * Carries a rejection out of the projection walk.\n *\n * The walk cannot signal failure with a plain `{ok: false}` object: that is\n * indistinguishable from payload data of the same shape, and `action.finished`\n * carries exactly that — an `ok` field reporting whether the action succeeded.\n * A failed action therefore looked like a rejected projection, and the event\n * was thrown away with a message built from a code and detail that were never\n * there (\"undefined: undefined\"). A class cannot be forged by JSON data.\n */\nclass ProjectionFailure {\n constructor(readonly failure: RunEventFailure) {}\n}\n\nfunction reject(code: RunEventViolationCode, detail: string): ProjectionFailure {\n return new ProjectionFailure(fail(code, detail));\n}\n\nfunction projectJson(\n value: unknown,\n limits: RunEventLimits,\n): { readonly ok: true; readonly value: RunEventJson } | RunEventFailure {\n let entries = 0;\n const seen = new Set<object>();\n const visit = (current: unknown, depth: number): RunEventJson | ProjectionFailure => {\n if (current === null || typeof current === 'boolean' || typeof current === 'string') {\n if (typeof current === 'string' && utf8(current) > limits.maxStringBytes)\n return reject('invalid-payload', 'a string exceeds maxStringBytes');\n return current;\n }\n if (typeof current === 'number')\n return Number.isFinite(current)\n ? current\n : reject('invalid-payload', 'numbers must be finite');\n if (typeof current !== 'object') return reject('invalid-payload', 'payload is not JSON data');\n if (depth > limits.maxPayloadDepth)\n return reject('invalid-payload', 'payload exceeds maxPayloadDepth');\n if (seen.has(current)) return reject('invalid-payload', 'payload aliases or cycles an object');\n seen.add(current);\n try {\n const symbols = Object.getOwnPropertySymbols(current);\n if (symbols.length > 0) return reject('invalid-payload', 'payload contains symbol keys');\n if (Array.isArray(current)) {\n if (Object.keys(current).length !== current.length)\n return reject('invalid-payload', 'payload contains a sparse array or extra array keys');\n const result: RunEventJson[] = [];\n for (const item of current) {\n entries += 1;\n if (entries > limits.maxPayloadEntries)\n return reject('invalid-payload', 'payload exceeds maxPayloadEntries');\n const child = visit(item, depth + 1);\n if (child instanceof ProjectionFailure) return child;\n result.push(child);\n }\n return Object.freeze(result);\n }\n const prototype = Object.getPrototypeOf(current);\n if (prototype !== Object.prototype && prototype !== null)\n return reject('invalid-payload', 'payload object has a non-plain prototype');\n const result: Record<string, RunEventJson> = Object.create(null) as Record<\n string,\n RunEventJson\n >;\n for (const key of Object.keys(current)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype')\n return reject('invalid-payload', 'payload contains a reserved key');\n if (utf8(key) > limits.maxStringBytes)\n return reject('invalid-payload', 'a payload key exceeds maxStringBytes');\n const descriptor = Object.getOwnPropertyDescriptor(current, key);\n if (descriptor === undefined || !('value' in descriptor))\n return reject('invalid-payload', 'payload contains an accessor');\n entries += 1;\n if (entries > limits.maxPayloadEntries)\n return reject('invalid-payload', 'payload exceeds maxPayloadEntries');\n const child = visit(descriptor.value, depth + 1);\n if (child instanceof ProjectionFailure) return child;\n result[key] = child;\n }\n return Object.freeze(result);\n } catch {\n return reject('invalid-payload', 'payload object could not be inspected safely');\n }\n };\n const projected = visit(value, 0);\n return projected instanceof ProjectionFailure\n ? projected.failure\n : { ok: true, value: projected };\n}\n\nfunction isRecord(\n value: RunEventJson | undefined,\n): value is { readonly [key: string]: RunEventJson } {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction fail(code: RunEventViolationCode, detail: string): RunEventFailure {\n return Object.freeze({ ok: false, code, detail });\n}\n\nfunction id(kind: RunIdKind, value: RunEventJson | undefined): boolean {\n return typeof value === 'string' && ID_PATTERNS[kind].test(value);\n}\n\nfunction nonNegativeInteger(value: RunEventJson | undefined): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction time(value: RunEventJson | undefined): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n\nfunction utf8(value: string): number {\n return new TextEncoder().encode(value).byteLength;\n}\n"],"mappings":"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","names":["result"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/run-state.ts
|
|
2
|
+
var RUN_STATES = Object.freeze([
|
|
3
|
+
"requested",
|
|
4
|
+
"collecting",
|
|
5
|
+
"scheduled",
|
|
6
|
+
"running",
|
|
7
|
+
"cancelling",
|
|
8
|
+
"finalizing",
|
|
9
|
+
"passed",
|
|
10
|
+
"passed-with-skips",
|
|
11
|
+
"failed",
|
|
12
|
+
"skipped",
|
|
13
|
+
"cancelled",
|
|
14
|
+
"infrastructure-failed",
|
|
15
|
+
"crashed",
|
|
16
|
+
"incomplete",
|
|
17
|
+
"flaky"
|
|
18
|
+
]);
|
|
19
|
+
var TERMINAL_RUN_STATES = Object.freeze([
|
|
20
|
+
"passed",
|
|
21
|
+
"passed-with-skips",
|
|
22
|
+
"failed",
|
|
23
|
+
"skipped",
|
|
24
|
+
"cancelled",
|
|
25
|
+
"infrastructure-failed",
|
|
26
|
+
"crashed",
|
|
27
|
+
"incomplete",
|
|
28
|
+
"flaky"
|
|
29
|
+
]);
|
|
30
|
+
var TERMINAL = new Set(TERMINAL_RUN_STATES);
|
|
31
|
+
var RUN_STATE_TRANSITIONS = Object.freeze({
|
|
32
|
+
requested: ["collecting", "cancelling", "infrastructure-failed"],
|
|
33
|
+
collecting: ["scheduled", "cancelling", "infrastructure-failed", "incomplete"],
|
|
34
|
+
scheduled: ["running", "cancelling", "finalizing", "infrastructure-failed", "incomplete"],
|
|
35
|
+
running: ["cancelling", "finalizing", "infrastructure-failed", "crashed", "incomplete"],
|
|
36
|
+
cancelling: ["finalizing", "cancelled", "infrastructure-failed", "incomplete"],
|
|
37
|
+
finalizing: TERMINAL_RUN_STATES,
|
|
38
|
+
passed: [],
|
|
39
|
+
"passed-with-skips": [],
|
|
40
|
+
failed: [],
|
|
41
|
+
skipped: [],
|
|
42
|
+
cancelled: [],
|
|
43
|
+
"infrastructure-failed": [],
|
|
44
|
+
crashed: [],
|
|
45
|
+
incomplete: [],
|
|
46
|
+
flaky: []
|
|
47
|
+
});
|
|
48
|
+
function isRunState(value) {
|
|
49
|
+
return typeof value === "string" && RUN_STATES.includes(value);
|
|
50
|
+
}
|
|
51
|
+
function isTerminalRunState(value) {
|
|
52
|
+
return TERMINAL.has(value);
|
|
53
|
+
}
|
|
54
|
+
function canTransitionRunState(from, to) {
|
|
55
|
+
return RUN_STATE_TRANSITIONS[from].includes(to);
|
|
56
|
+
}
|
|
57
|
+
function validateRunStateTransition(from, to) {
|
|
58
|
+
if (!isRunState(from) || !isRunState(to)) {
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
ok: false,
|
|
61
|
+
code: "invalid-run-state",
|
|
62
|
+
detail: "run state is outside the closed Termwright vocabulary"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (!canTransitionRunState(from, to)) {
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
ok: false,
|
|
68
|
+
code: "illegal-run-transition",
|
|
69
|
+
detail: `run cannot transition from ${from} to ${to}`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze({ ok: true, from, to });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
RUN_STATES,
|
|
77
|
+
TERMINAL_RUN_STATES,
|
|
78
|
+
RUN_STATE_TRANSITIONS,
|
|
79
|
+
isRunState,
|
|
80
|
+
isTerminalRunState,
|
|
81
|
+
canTransitionRunState,
|
|
82
|
+
validateRunStateTransition
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=chunk-ODOJRXL6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/run-state.ts"],"sourcesContent":["/** Closed first-class lifecycle vocabulary for a Termwright run. */\nexport const RUN_STATES = Object.freeze([\n 'requested',\n 'collecting',\n 'scheduled',\n 'running',\n 'cancelling',\n 'finalizing',\n 'passed',\n 'passed-with-skips',\n 'failed',\n 'skipped',\n 'cancelled',\n 'infrastructure-failed',\n 'crashed',\n 'incomplete',\n 'flaky',\n] as const);\n\nexport type RunState = (typeof RUN_STATES)[number];\n\nexport const TERMINAL_RUN_STATES = Object.freeze([\n 'passed',\n 'passed-with-skips',\n 'failed',\n 'skipped',\n 'cancelled',\n 'infrastructure-failed',\n 'crashed',\n 'incomplete',\n 'flaky',\n] as const satisfies readonly RunState[]);\n\nexport type TerminalRunState = (typeof TERMINAL_RUN_STATES)[number];\n\nconst TERMINAL = new Set<RunState>(TERMINAL_RUN_STATES);\n\n/** Executable transition graph; consumers must not duplicate this table. */\nexport const RUN_STATE_TRANSITIONS = Object.freeze({\n requested: ['collecting', 'cancelling', 'infrastructure-failed'],\n collecting: ['scheduled', 'cancelling', 'infrastructure-failed', 'incomplete'],\n scheduled: ['running', 'cancelling', 'finalizing', 'infrastructure-failed', 'incomplete'],\n running: ['cancelling', 'finalizing', 'infrastructure-failed', 'crashed', 'incomplete'],\n cancelling: ['finalizing', 'cancelled', 'infrastructure-failed', 'incomplete'],\n finalizing: TERMINAL_RUN_STATES,\n passed: [],\n 'passed-with-skips': [],\n failed: [],\n skipped: [],\n cancelled: [],\n 'infrastructure-failed': [],\n crashed: [],\n incomplete: [],\n flaky: [],\n} as const satisfies Readonly<Record<RunState, readonly RunState[]>>);\n\nexport function isRunState(value: unknown): value is RunState {\n return typeof value === 'string' && RUN_STATES.includes(value as RunState);\n}\n\nexport function isTerminalRunState(value: RunState): value is TerminalRunState {\n return TERMINAL.has(value);\n}\n\nexport function canTransitionRunState(from: RunState, to: RunState): boolean {\n return (RUN_STATE_TRANSITIONS[from] as readonly RunState[]).includes(to);\n}\n\nexport type RunStateTransitionResult =\n | { readonly ok: true; readonly from: RunState; readonly to: RunState }\n | {\n readonly ok: false;\n readonly code: 'invalid-run-state' | 'illegal-run-transition';\n readonly detail: string;\n };\n\nexport function validateRunStateTransition(from: unknown, to: unknown): RunStateTransitionResult {\n if (!isRunState(from) || !isRunState(to)) {\n return Object.freeze({\n ok: false,\n code: 'invalid-run-state',\n detail: 'run state is outside the closed Termwright vocabulary',\n });\n }\n if (!canTransitionRunState(from, to)) {\n return Object.freeze({\n ok: false,\n code: 'illegal-run-transition',\n detail: `run cannot transition from ${from} to ${to}`,\n });\n }\n return Object.freeze({ ok: true, from, to });\n}\n"],"mappings":";AACO,IAAM,aAAa,OAAO,OAAO;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAU;AAIH,IAAM,sBAAsB,OAAO,OAAO;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAwC;AAIxC,IAAM,WAAW,IAAI,IAAc,mBAAmB;AAG/C,IAAM,wBAAwB,OAAO,OAAO;AAAA,EACjD,WAAW,CAAC,cAAc,cAAc,uBAAuB;AAAA,EAC/D,YAAY,CAAC,aAAa,cAAc,yBAAyB,YAAY;AAAA,EAC7E,WAAW,CAAC,WAAW,cAAc,cAAc,yBAAyB,YAAY;AAAA,EACxF,SAAS,CAAC,cAAc,cAAc,yBAAyB,WAAW,YAAY;AAAA,EACtF,YAAY,CAAC,cAAc,aAAa,yBAAyB,YAAY;AAAA,EAC7E,YAAY;AAAA,EACZ,QAAQ,CAAC;AAAA,EACT,qBAAqB,CAAC;AAAA,EACtB,QAAQ,CAAC;AAAA,EACT,SAAS,CAAC;AAAA,EACV,WAAW,CAAC;AAAA,EACZ,yBAAyB,CAAC;AAAA,EAC1B,SAAS,CAAC;AAAA,EACV,YAAY,CAAC;AAAA,EACb,OAAO,CAAC;AACV,CAAoE;AAE7D,SAAS,WAAW,OAAmC;AAC5D,SAAO,OAAO,UAAU,YAAY,WAAW,SAAS,KAAiB;AAC3E;AAEO,SAAS,mBAAmB,OAA4C;AAC7E,SAAO,SAAS,IAAI,KAAK;AAC3B;AAEO,SAAS,sBAAsB,MAAgB,IAAuB;AAC3E,SAAQ,sBAAsB,IAAI,EAA0B,SAAS,EAAE;AACzE;AAUO,SAAS,2BAA2B,MAAe,IAAuC;AAC/F,MAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,EAAE,GAAG;AACxC,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AACA,MAAI,CAAC,sBAAsB,MAAM,EAAE,GAAG;AACpC,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,QAAQ,8BAA8B,IAAI,OAAO,EAAE;AAAA,IACrD,CAAC;AAAA,EACH;AACA,SAAO,OAAO,OAAO,EAAE,IAAI,MAAM,MAAM,GAAG,CAAC;AAC7C;","names":[]}
|