stitchkit 0.77.0 → 0.78.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.
Files changed (65) hide show
  1. package/dist/agent-runtime-coding-tools.js +1 -1
  2. package/dist/agent-runtime-harness.js +6 -6
  3. package/dist/agent-runtime.js +11 -11
  4. package/dist/application/decisions.d.ts +28 -25
  5. package/dist/application/decisions.d.ts.map +1 -1
  6. package/dist/application/kernel.d.ts +2 -0
  7. package/dist/application/kernel.d.ts.map +1 -1
  8. package/dist/application/keyspace.d.ts +11 -1
  9. package/dist/application/keyspace.d.ts.map +1 -1
  10. package/dist/application/schedule.d.ts.map +1 -1
  11. package/dist/application/server-resource.d.ts.map +1 -1
  12. package/dist/application-diagnostic-journal.d.ts +21 -0
  13. package/dist/application-diagnostic-journal.d.ts.map +1 -0
  14. package/dist/application-diagnostic-journal.js +714 -0
  15. package/dist/application-opentelemetry.js +92 -5
  16. package/dist/application-schemas.js +13 -98
  17. package/dist/application.d.ts +1 -2
  18. package/dist/application.d.ts.map +1 -1
  19. package/dist/application.js +1775 -858
  20. package/dist/cli.js +7 -7
  21. package/dist/contract/index.js +9 -7
  22. package/dist/files/boundary.d.ts.map +1 -1
  23. package/dist/files.js +1 -1
  24. package/dist/{index-3z73fh2c.js → index-1ckavx4h.js} +49 -1
  25. package/dist/{index-s2rchahr.js → index-31k1ns1s.js} +4 -4
  26. package/dist/{index-m668wzyc.js → index-4ntp63ps.js} +4 -4
  27. package/dist/{index-1rxswfbv.js → index-56fm73re.js} +1 -1
  28. package/dist/{index-ezmn6ac6.js → index-5r0ak8fr.js} +3 -3
  29. package/dist/{index-f6n5n7nz.js → index-7z69kyrh.js} +10 -87
  30. package/dist/{index-k2zczx1g.js → index-93xp5tgg.js} +1 -1
  31. package/dist/{index-bfcpjw20.js → index-actkayzn.js} +7 -3
  32. package/dist/index-anyqnqcv.js +79 -0
  33. package/dist/{index-7qy2ex0m.js → index-cwp38wxn.js} +1 -1
  34. package/dist/{index-35z5h2ty.js → index-dmfn9m4x.js} +125 -4
  35. package/dist/index-h4pj6fta.js +87 -0
  36. package/dist/{index-jqtsc9mj.js → index-j6mfaw26.js} +2 -2
  37. package/dist/{index-58jzmnn4.js → index-k45qqrdh.js} +513 -39
  38. package/dist/{index-8eywc9zv.js → index-kw12fsbc.js} +0 -1
  39. package/dist/index-n1g2xm6a.js +203 -0
  40. package/dist/{index-t8qyvrvg.js → index-n7apa6sy.js} +12 -12
  41. package/dist/{index-s4c8wy8m.js → index-qaajjfp6.js} +8 -8
  42. package/dist/{index-nemjkxjp.js → index-s5tn4kdv.js} +14 -10
  43. package/dist/index-wfq8zerm.js +92 -0
  44. package/dist/index.js +53 -1215
  45. package/dist/internal/deadline.d.ts +17 -0
  46. package/dist/internal/deadline.d.ts.map +1 -0
  47. package/dist/live.js +12 -89
  48. package/dist/node.js +5 -6
  49. package/dist/observability/context.d.ts.map +1 -1
  50. package/dist/observability/index.js +3 -3
  51. package/dist/primitives.js +2 -1
  52. package/dist/remote.js +3 -6
  53. package/dist/server/event-bus.d.ts.map +1 -1
  54. package/dist/server/index.js +31 -30
  55. package/dist/testing.js +1562 -87
  56. package/dist/tool-invoker.js +6 -6
  57. package/dist/tools.js +66 -23
  58. package/llms-full.txt +110 -13
  59. package/package.json +8 -4
  60. package/dist/index-3cwck0rm.js +0 -897
  61. package/dist/index-7b188kmz.js +0 -49
  62. package/dist/index-a916km3r.js +0 -45
  63. package/dist/index-vc1b0b1b.js +0 -825
  64. package/dist/{index-da1aqnhb.js → index-aewy3x5j.js} +3 -3
  65. package/dist/{index-nt1mp8km.js → index-efm026pw.js} +3 -3
@@ -0,0 +1,714 @@
1
+ import {
2
+ createBoundedChannel
3
+ } from "./index-n1g2xm6a.js";
4
+ import {
5
+ isRecord
6
+ } from "./index-qyrqwr4c.js";
7
+
8
+ // src/application/diagnostic-journal.ts
9
+ import { randomUUID } from "node:crypto";
10
+
11
+ // src/application/diagnostic-journal-contract.ts
12
+ import { z } from "zod";
13
+ var PositiveSafeIntegerSchema = z.number().int().positive().safe();
14
+ var FileModeSchema = z.number().int().min(0).max(511);
15
+ var DiagnosticJournalLimitsSchema = z.object({
16
+ maxEventBytes: PositiveSafeIntegerSchema,
17
+ maxPendingItems: PositiveSafeIntegerSchema,
18
+ maxPendingBytes: PositiveSafeIntegerSchema,
19
+ maxFileBytes: PositiveSafeIntegerSchema,
20
+ maxFiles: PositiveSafeIntegerSchema
21
+ }).strict().readonly();
22
+ var DiagnosticJournalLockPolicySchema = z.enum(["refuse", "reclaim-stale"]);
23
+ var DiagnosticJournalStateSchema = z.enum(["open", "draining", "closed", "failed"]);
24
+ var DiagnosticJournalRefusalReasonSchema = z.enum([
25
+ "closed",
26
+ "failed",
27
+ "invalid",
28
+ "oversized",
29
+ "item-capacity",
30
+ "byte-capacity"
31
+ ]);
32
+ var DiagnosticJournalRefusalCountersSchema = z.object({
33
+ closed: z.number().int().nonnegative(),
34
+ failed: z.number().int().nonnegative(),
35
+ invalid: z.number().int().nonnegative(),
36
+ oversized: z.number().int().nonnegative(),
37
+ "item-capacity": z.number().int().nonnegative(),
38
+ "byte-capacity": z.number().int().nonnegative()
39
+ }).strict().readonly();
40
+ var DiagnosticJournalFailurePhaseSchema = z.enum(["write", "rotation", "close"]);
41
+ var DiagnosticJournalStatusSchema = z.object({
42
+ state: DiagnosticJournalStateSchema,
43
+ epoch: z.uuid(),
44
+ limits: DiagnosticJournalLimitsSchema,
45
+ lock: z.object({
46
+ policy: DiagnosticJournalLockPolicySchema,
47
+ reclaimedStale: z.boolean()
48
+ }).strict().readonly(),
49
+ received: z.number().int().nonnegative(),
50
+ accepted: z.number().int().nonnegative(),
51
+ refused: z.number().int().nonnegative(),
52
+ refusals: DiagnosticJournalRefusalCountersSchema,
53
+ written: z.number().int().nonnegative(),
54
+ failedRecords: z.number().int().nonnegative(),
55
+ pendingItems: z.number().int().nonnegative(),
56
+ pendingBytes: z.number().int().nonnegative(),
57
+ inFlight: z.boolean(),
58
+ rotations: z.number().int().nonnegative(),
59
+ rotationFailures: z.number().int().nonnegative(),
60
+ partialTails: z.number().int().nonnegative(),
61
+ currentFileBytes: z.number().int().nonnegative(),
62
+ retainedFiles: z.number().int().nonnegative(),
63
+ lastAcceptedSequence: z.number().int().positive().optional(),
64
+ lastWrittenSequence: z.number().int().positive().optional(),
65
+ lastSettledSequence: z.number().int().positive().optional(),
66
+ lastFailure: z.object({
67
+ phase: DiagnosticJournalFailurePhaseSchema,
68
+ sequence: z.number().int().positive().optional()
69
+ }).strict().readonly().optional()
70
+ }).strict().readonly();
71
+ var DiagnosticJournalSubmitResultSchema = z.discriminatedUnion("outcome", [
72
+ z.object({
73
+ outcome: z.literal("accepted"),
74
+ epoch: z.uuid(),
75
+ sequence: z.number().int().positive()
76
+ }).strict().readonly(),
77
+ z.object({
78
+ outcome: z.literal("refused"),
79
+ reason: DiagnosticJournalRefusalReasonSchema
80
+ }).strict().readonly()
81
+ ]);
82
+ var DiagnosticJournalWaitResultSchema = z.object({
83
+ outcome: z.enum(["settled", "timed-out", "cancelled"]),
84
+ state: DiagnosticJournalStateSchema,
85
+ throughSequence: z.number().int().nonnegative(),
86
+ settledSequence: z.number().int().nonnegative()
87
+ }).strict().readonly();
88
+ var DiagnosticJournalCloseResultSchema = z.object({
89
+ outcome: z.enum(["closed", "timed-out", "cancelled"]),
90
+ state: DiagnosticJournalStateSchema,
91
+ pendingItems: z.number().int().nonnegative()
92
+ }).strict().readonly();
93
+ var DiagnosticJournalFrameSchema = z.object({
94
+ schemaVersion: z.literal(1),
95
+ epoch: z.uuid(),
96
+ sequence: z.number().int().positive(),
97
+ event: z.json()
98
+ }).strict().readonly();
99
+ function parseDiagnosticJournalMode(mode) {
100
+ return FileModeSchema.parse(mode ?? 384);
101
+ }
102
+
103
+ // src/application/diagnostic-journal-manager.ts
104
+ import { z as z2 } from "zod";
105
+
106
+ // src/application/diagnostic-journal-storage.ts
107
+ import { constants as constants2 } from "node:fs";
108
+ import { lstat, open as open2, readdir, realpath, rename, unlink as unlink2 } from "node:fs/promises";
109
+ import { basename, dirname, isAbsolute, resolve } from "node:path";
110
+
111
+ // src/application/diagnostic-journal-lock.ts
112
+ import { execFile } from "node:child_process";
113
+ import { constants } from "node:fs";
114
+ import { open, readFile, unlink } from "node:fs/promises";
115
+ import { hostname, platform } from "node:os";
116
+ import { promisify } from "node:util";
117
+ function isExisting(error) {
118
+ return isRecord(error) && error.code === "EEXIST";
119
+ }
120
+ function readOwner(text) {
121
+ let parsed;
122
+ try {
123
+ parsed = JSON.parse(text);
124
+ } catch {
125
+ return;
126
+ }
127
+ if (!isRecord(parsed))
128
+ return;
129
+ const { pid, host, acquiredAt, machine } = parsed;
130
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0)
131
+ return;
132
+ if (typeof host !== "string" || host.length === 0)
133
+ return;
134
+ if (typeof acquiredAt !== "string")
135
+ return;
136
+ const identified = typeof machine === "string" && machine.length > 0 ? { machine } : {};
137
+ return { pid, host, acquiredAt, ...identified };
138
+ }
139
+ var detectedIdentity;
140
+ async function firstReadableLine(paths) {
141
+ for (const path of paths) {
142
+ try {
143
+ const text = (await readFile(path, "utf8")).trim();
144
+ if (text.length > 0)
145
+ return text;
146
+ } catch {}
147
+ }
148
+ return null;
149
+ }
150
+ var run = promisify(execFile);
151
+ function parsePlatformUuid(dump) {
152
+ return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(dump)?.[1] ?? null;
153
+ }
154
+ async function readDarwinPlatformUuid(command = "ioreg") {
155
+ try {
156
+ const { stdout } = await run(command, ["-rd1", "-c", "IOPlatformExpertDevice"], {
157
+ timeout: 2000,
158
+ maxBuffer: 1 << 20,
159
+ encoding: "utf8"
160
+ });
161
+ return parsePlatformUuid(stdout);
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+ async function machineIdentity(declared) {
167
+ if (declared !== undefined)
168
+ return declared;
169
+ if (detectedIdentity !== undefined)
170
+ return detectedIdentity;
171
+ detectedIdentity = platform() === "darwin" ? await readDarwinPlatformUuid() : await firstReadableLine(["/etc/machine-id", "/var/lib/dbus/machine-id"]);
172
+ return detectedIdentity;
173
+ }
174
+ function attribute(owner, identity) {
175
+ if (identity !== null && owner.machine !== undefined) {
176
+ return owner.machine === identity ? "this-machine" : "another-machine";
177
+ }
178
+ return owner.host === hostname() ? "this-machine" : "unattributable";
179
+ }
180
+ async function probeLiveness(owner) {
181
+ try {
182
+ process.kill(owner.pid, 0);
183
+ } catch (error) {
184
+ return isRecord(error) && error.code === "ESRCH" ? "gone" : "alive";
185
+ }
186
+ return await isZombieProcess(owner.pid) ? "gone" : "alive";
187
+ }
188
+ async function isZombieProcess(pid, seam = {}) {
189
+ const { procRoot = "/proc", psCommand = "ps" } = seam;
190
+ try {
191
+ const stat = await readFile(`${procRoot}/${pid}/stat`, "utf8");
192
+ return stat.slice(stat.lastIndexOf(")") + 1).trimStart().startsWith("Z");
193
+ } catch {}
194
+ try {
195
+ const { stdout } = await run(psCommand, ["-o", "state=", "-p", String(pid)], {
196
+ timeout: 2000,
197
+ maxBuffer: 1 << 16,
198
+ encoding: "utf8"
199
+ });
200
+ return stdout.trim().startsWith("Z");
201
+ } catch {
202
+ return false;
203
+ }
204
+ }
205
+ async function diagnose(owner, declaredIdentity) {
206
+ if (!owner)
207
+ return { attribution: "unattributable", liveness: "not-probed", owner: null };
208
+ const attribution = attribute(owner, await machineIdentity(declaredIdentity));
209
+ const record = {
210
+ pid: owner.pid,
211
+ host: owner.host,
212
+ acquiredAt: owner.acquiredAt,
213
+ ...owner.machine !== undefined && { machine: owner.machine }
214
+ };
215
+ if (attribution !== "this-machine")
216
+ return { attribution, liveness: "not-probed", owner: record };
217
+ return { attribution, liveness: await probeLiveness(owner), owner: record };
218
+ }
219
+ async function readLockOwner(lockPath) {
220
+ let handle;
221
+ try {
222
+ handle = await open(lockPath, constants.O_RDONLY | constants.O_NOFOLLOW);
223
+ return readOwner(await handle.readFile("utf8"));
224
+ } catch {
225
+ return;
226
+ } finally {
227
+ await handle?.close().catch(() => {
228
+ return;
229
+ });
230
+ }
231
+ }
232
+ async function createExclusive(lockPath, mode, declaredIdentity) {
233
+ const handle = await open(lockPath, "wx", mode);
234
+ const identity = await machineIdentity(declaredIdentity);
235
+ const owner = {
236
+ pid: process.pid,
237
+ host: hostname(),
238
+ acquiredAt: new Date().toISOString(),
239
+ ...identity !== null && { machine: identity }
240
+ };
241
+ await handle.writeFile(`${JSON.stringify(owner)}
242
+ `, "utf8");
243
+ return handle;
244
+ }
245
+ async function acquireDiagnosticJournalLock(lockPath, mode, policy, declaredIdentity) {
246
+ try {
247
+ return {
248
+ handle: await createExclusive(lockPath, mode, declaredIdentity),
249
+ reclaimedStale: false
250
+ };
251
+ } catch (error) {
252
+ if (policy !== "reclaim-stale" || !isExisting(error))
253
+ throw error;
254
+ const owner = await readLockOwner(lockPath);
255
+ const diagnosis = await diagnose(owner, declaredIdentity);
256
+ if (!owner || diagnosis.liveness !== "gone") {
257
+ if (isRecord(error))
258
+ Object.assign(error, { journalLock: diagnosis });
259
+ throw error;
260
+ }
261
+ await unlink(lockPath).catch(() => {
262
+ return;
263
+ });
264
+ return {
265
+ handle: await createExclusive(lockPath, mode, declaredIdentity),
266
+ reclaimedStale: true
267
+ };
268
+ }
269
+ }
270
+
271
+ // src/application/diagnostic-journal-storage.ts
272
+ class DiagnosticJournalStorageError extends Error {
273
+ phase;
274
+ constructor(phase, message, options) {
275
+ super(message, options);
276
+ this.phase = phase;
277
+ this.name = "DiagnosticJournalStorageError";
278
+ }
279
+ }
280
+ function isMissing(error) {
281
+ return isRecord(error) && error.code === "ENOENT";
282
+ }
283
+ async function removeIfPresent(path) {
284
+ try {
285
+ const info = await lstat(path);
286
+ if (info.isSymbolicLink() || !info.isFile()) {
287
+ throw new Error("Diagnostic journal generations must be regular files");
288
+ }
289
+ await unlink2(path);
290
+ } catch (error) {
291
+ if (!isMissing(error))
292
+ throw error;
293
+ }
294
+ }
295
+ async function moveIfPresent(from, to) {
296
+ try {
297
+ const info = await lstat(from);
298
+ if (info.isSymbolicLink() || !info.isFile()) {
299
+ throw new Error("Diagnostic journal generations must be regular files");
300
+ }
301
+ await rename(from, to);
302
+ return true;
303
+ } catch (error) {
304
+ if (isMissing(error))
305
+ return false;
306
+ throw error;
307
+ }
308
+ }
309
+ function generation(path, index) {
310
+ return `${path}.${index}`;
311
+ }
312
+ function generationIndex(name, prefix) {
313
+ if (!name.startsWith(prefix))
314
+ return;
315
+ const suffix = name.slice(prefix.length);
316
+ if (!/^\d+$/.test(suffix))
317
+ return;
318
+ const value = Number(suffix);
319
+ return Number.isSafeInteger(value) && value > 0 ? value : undefined;
320
+ }
321
+ async function createRotatingDiagnosticJournalStorage(config) {
322
+ if (!isAbsolute(config.path) || resolve(config.path) !== config.path) {
323
+ throw new TypeError("Diagnostic journal path must be normalized and absolute");
324
+ }
325
+ const parent = await realpath(dirname(config.path));
326
+ const journalPath = resolve(parent, basename(config.path));
327
+ const lockPath = `${journalPath}.lock`;
328
+ const { handle: lock, reclaimedStale } = await acquireDiagnosticJournalLock(lockPath, config.mode, config.lock, config.machineIdentity);
329
+ let handle;
330
+ let currentFileBytes = 0;
331
+ let retainedFiles = 1;
332
+ let rotations = 0;
333
+ let partialTails = 0;
334
+ let closed = false;
335
+ const openCurrent = async () => {
336
+ handle = await open2(journalPath, constants2.O_APPEND | constants2.O_CREAT | constants2.O_RDWR | constants2.O_NOFOLLOW, config.mode);
337
+ const info = await handle.stat();
338
+ if (!info.isFile()) {
339
+ await handle.close();
340
+ handle = undefined;
341
+ throw new Error("Diagnostic journal path must be a regular file");
342
+ }
343
+ currentFileBytes = Number(info.size);
344
+ };
345
+ const rotate = async () => {
346
+ try {
347
+ await handle?.close();
348
+ handle = undefined;
349
+ if (config.maxFiles === 1) {
350
+ await removeIfPresent(journalPath);
351
+ } else {
352
+ await removeIfPresent(generation(journalPath, config.maxFiles - 1));
353
+ for (let index = config.maxFiles - 2;index >= 1; index -= 1) {
354
+ await moveIfPresent(generation(journalPath, index), generation(journalPath, index + 1));
355
+ }
356
+ await moveIfPresent(journalPath, generation(journalPath, 1));
357
+ }
358
+ retainedFiles = Math.min(config.maxFiles, retainedFiles + 1);
359
+ rotations += 1;
360
+ await openCurrent();
361
+ } catch (error) {
362
+ throw new DiagnosticJournalStorageError("rotation", "Diagnostic journal rotation failed", { cause: error });
363
+ }
364
+ };
365
+ try {
366
+ const names = await readdir(parent);
367
+ const prefix = `${basename(journalPath)}.`;
368
+ let existingGenerations = 0;
369
+ for (const name of names) {
370
+ const index = generationIndex(name, prefix);
371
+ if (index === undefined)
372
+ continue;
373
+ if (index >= config.maxFiles) {
374
+ await removeIfPresent(resolve(parent, name));
375
+ } else {
376
+ const info = await lstat(resolve(parent, name));
377
+ if (info.isSymbolicLink() || !info.isFile()) {
378
+ throw new Error("Diagnostic journal generations must be regular files");
379
+ }
380
+ existingGenerations += 1;
381
+ }
382
+ }
383
+ retainedFiles = Math.min(config.maxFiles, existingGenerations + 1);
384
+ await openCurrent();
385
+ if (currentFileBytes > 0) {
386
+ const tail = new Uint8Array(1);
387
+ const read = await handle?.read(tail, 0, 1, currentFileBytes - 1);
388
+ if (read?.bytesRead === 1 && tail[0] !== 10) {
389
+ partialTails += 1;
390
+ await rotate();
391
+ }
392
+ }
393
+ } catch (error) {
394
+ await handle?.close().catch(() => {
395
+ return;
396
+ });
397
+ await lock.close().catch(() => {
398
+ return;
399
+ });
400
+ await unlink2(lockPath).catch(() => {
401
+ return;
402
+ });
403
+ throw error;
404
+ }
405
+ const snapshot = () => ({
406
+ currentFileBytes,
407
+ retainedFiles,
408
+ rotations,
409
+ partialTails
410
+ });
411
+ return {
412
+ reclaimedStale,
413
+ async append(bytes) {
414
+ if (closed || !handle) {
415
+ throw new DiagnosticJournalStorageError("write", "Diagnostic journal storage is closed");
416
+ }
417
+ let rotated = false;
418
+ if (currentFileBytes > 0 && currentFileBytes + bytes.byteLength > config.maxFileBytes) {
419
+ await rotate();
420
+ rotated = true;
421
+ }
422
+ try {
423
+ await handle.writeFile(bytes);
424
+ currentFileBytes += bytes.byteLength;
425
+ return { ...snapshot(), rotated };
426
+ } catch (error) {
427
+ throw new DiagnosticJournalStorageError("write", "Diagnostic journal write failed", {
428
+ cause: error
429
+ });
430
+ }
431
+ },
432
+ snapshot,
433
+ async close() {
434
+ if (closed)
435
+ return;
436
+ closed = true;
437
+ let failure;
438
+ try {
439
+ await handle?.close();
440
+ } catch (error) {
441
+ failure = error;
442
+ }
443
+ try {
444
+ await lock.close();
445
+ await unlink2(lockPath);
446
+ } catch (error) {
447
+ failure ??= error;
448
+ }
449
+ if (failure !== undefined) {
450
+ throw new DiagnosticJournalStorageError("close", "Diagnostic journal close failed", {
451
+ cause: failure
452
+ });
453
+ }
454
+ }
455
+ };
456
+ }
457
+
458
+ // src/application/diagnostic-journal-manager.ts
459
+ function waitBudget(options) {
460
+ if (options.timeoutMs === undefined)
461
+ return;
462
+ if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0) {
463
+ throw new TypeError("Diagnostic journal timeoutMs must be a positive safe integer");
464
+ }
465
+ return options.timeoutMs;
466
+ }
467
+ function createDiagnosticJournalManager(config, storage) {
468
+ const limits = DiagnosticJournalLimitsSchema.parse(config.limits);
469
+ const encoder = new TextEncoder;
470
+ const channel = createBoundedChannel({
471
+ policy: "ordered",
472
+ maxItems: limits.maxPendingItems,
473
+ maxBytes: limits.maxPendingBytes,
474
+ sizeOf: (frame) => frame.bytes.byteLength
475
+ });
476
+ const refusals = {
477
+ closed: 0,
478
+ failed: 0,
479
+ invalid: 0,
480
+ oversized: 0,
481
+ "item-capacity": 0,
482
+ "byte-capacity": 0
483
+ };
484
+ const progressWaiters = new Set;
485
+ let state = "open";
486
+ let received = 0;
487
+ let accepted = 0;
488
+ let refused = 0;
489
+ let written = 0;
490
+ let failedRecords = 0;
491
+ let pendingItems = 0;
492
+ let pendingBytes = 0;
493
+ let inFlight = false;
494
+ let rotationFailures = 0;
495
+ let lastAcceptedSequence = 0;
496
+ let lastWrittenSequence = 0;
497
+ let lastSettledSequence = 0;
498
+ let lastFailure;
499
+ let physicalClosed = false;
500
+ const isFailed = () => state === "failed";
501
+ const notifyProgress = () => {
502
+ for (const waiter of progressWaiters)
503
+ waiter();
504
+ };
505
+ const reportFailure = (failure) => {
506
+ if (!config.onFailure)
507
+ return;
508
+ Promise.resolve().then(() => config.onFailure?.(failure)).catch(() => {
509
+ return;
510
+ });
511
+ };
512
+ const markFailure = (error, fallbackPhase, sequence) => {
513
+ const phase = error instanceof DiagnosticJournalStorageError ? error.phase : fallbackPhase;
514
+ if (phase === "rotation")
515
+ rotationFailures += 1;
516
+ lastFailure = { phase, ...sequence !== undefined && { sequence } };
517
+ state = "failed";
518
+ channel.close({ mode: "drain" });
519
+ reportFailure({ phase, error, ...sequence !== undefined && { sequence } });
520
+ };
521
+ const worker = (async () => {
522
+ for await (const frame of channel) {
523
+ inFlight = true;
524
+ if (isFailed()) {
525
+ failedRecords += 1;
526
+ } else {
527
+ try {
528
+ await storage.append(frame.bytes);
529
+ written += 1;
530
+ lastWrittenSequence = frame.sequence;
531
+ } catch (error) {
532
+ failedRecords += 1;
533
+ markFailure(error, "write", frame.sequence);
534
+ }
535
+ }
536
+ pendingItems -= 1;
537
+ pendingBytes -= frame.bytes.byteLength;
538
+ lastSettledSequence = frame.sequence;
539
+ inFlight = false;
540
+ notifyProgress();
541
+ }
542
+ try {
543
+ await storage.close();
544
+ } catch (error) {
545
+ markFailure(error, "close");
546
+ }
547
+ physicalClosed = true;
548
+ if (!isFailed())
549
+ state = "closed";
550
+ notifyProgress();
551
+ })();
552
+ worker.catch((error) => {
553
+ markFailure(error, "close");
554
+ physicalClosed = true;
555
+ notifyProgress();
556
+ });
557
+ const refuse = (reason) => {
558
+ refused += 1;
559
+ refusals[reason] += 1;
560
+ return DiagnosticJournalSubmitResultSchema.parse({ outcome: "refused", reason });
561
+ };
562
+ const waitUntil = (predicate, options) => {
563
+ const timeoutMs = waitBudget(options);
564
+ if (predicate())
565
+ return Promise.resolve("settled");
566
+ if (options.signal?.aborted)
567
+ return Promise.resolve("cancelled");
568
+ return new Promise((resolve2) => {
569
+ let settled = false;
570
+ let timer;
571
+ const finish = (outcome) => {
572
+ if (settled)
573
+ return;
574
+ settled = true;
575
+ progressWaiters.delete(onProgress);
576
+ options.signal?.removeEventListener("abort", onAbort);
577
+ if (timer)
578
+ clearTimeout(timer);
579
+ resolve2(outcome);
580
+ };
581
+ const onProgress = () => {
582
+ if (predicate())
583
+ finish("settled");
584
+ };
585
+ const onAbort = () => finish("cancelled");
586
+ progressWaiters.add(onProgress);
587
+ options.signal?.addEventListener("abort", onAbort, { once: true });
588
+ if (timeoutMs !== undefined)
589
+ timer = setTimeout(() => finish("timed-out"), timeoutMs);
590
+ onProgress();
591
+ });
592
+ };
593
+ const getStatus = () => {
594
+ const file = storage.snapshot();
595
+ return DiagnosticJournalStatusSchema.parse({
596
+ state,
597
+ epoch: config.epoch,
598
+ limits,
599
+ lock: config.lock,
600
+ received,
601
+ accepted,
602
+ refused,
603
+ refusals,
604
+ written,
605
+ failedRecords,
606
+ pendingItems,
607
+ pendingBytes,
608
+ inFlight,
609
+ rotations: file.rotations,
610
+ rotationFailures,
611
+ partialTails: file.partialTails,
612
+ currentFileBytes: file.currentFileBytes,
613
+ retainedFiles: file.retainedFiles,
614
+ ...lastAcceptedSequence > 0 && { lastAcceptedSequence },
615
+ ...lastWrittenSequence > 0 && { lastWrittenSequence },
616
+ ...lastSettledSequence > 0 && { lastSettledSequence },
617
+ ...lastFailure && { lastFailure }
618
+ });
619
+ };
620
+ return {
621
+ submit(event) {
622
+ received += 1;
623
+ if (state === "failed")
624
+ return refuse("failed");
625
+ if (state !== "open")
626
+ return refuse("closed");
627
+ const parsed = config.eventSchema.safeParse(event);
628
+ if (!parsed.success)
629
+ return refuse("invalid");
630
+ const json = z2.json().safeParse(parsed.data);
631
+ if (!json.success)
632
+ return refuse("invalid");
633
+ const payload = encoder.encode(JSON.stringify(json.data));
634
+ if (payload.byteLength > limits.maxEventBytes)
635
+ return refuse("oversized");
636
+ if (pendingItems >= limits.maxPendingItems)
637
+ return refuse("item-capacity");
638
+ const sequence = lastAcceptedSequence + 1;
639
+ const frame = DiagnosticJournalFrameSchema.parse({
640
+ schemaVersion: 1,
641
+ epoch: config.epoch,
642
+ sequence,
643
+ event: json.data
644
+ });
645
+ const bytes = encoder.encode(`${JSON.stringify(frame)}
646
+ `);
647
+ if (bytes.byteLength > limits.maxFileBytes)
648
+ return refuse("oversized");
649
+ if (pendingBytes + bytes.byteLength > limits.maxPendingBytes) {
650
+ return refuse("byte-capacity");
651
+ }
652
+ const offered = channel.offer({ sequence, bytes });
653
+ if (offered.outcome === "refused") {
654
+ return refuse(offered.reason === "not-open" ? "closed" : "item-capacity");
655
+ }
656
+ accepted += 1;
657
+ pendingItems += 1;
658
+ pendingBytes += bytes.byteLength;
659
+ lastAcceptedSequence = sequence;
660
+ return DiagnosticJournalSubmitResultSchema.parse({
661
+ outcome: "accepted",
662
+ epoch: config.epoch,
663
+ sequence
664
+ });
665
+ },
666
+ async flush(options = {}) {
667
+ const throughSequence = lastAcceptedSequence;
668
+ const outcome = await waitUntil(() => lastSettledSequence >= throughSequence, options);
669
+ return DiagnosticJournalWaitResultSchema.parse({
670
+ outcome,
671
+ state,
672
+ throughSequence,
673
+ settledSequence: lastSettledSequence
674
+ });
675
+ },
676
+ getStatus,
677
+ async close(options = {}) {
678
+ if (state === "open") {
679
+ state = "draining";
680
+ channel.close({ mode: "drain" });
681
+ }
682
+ const wait = await waitUntil(() => physicalClosed, options);
683
+ return DiagnosticJournalCloseResultSchema.parse({
684
+ outcome: wait === "settled" ? "closed" : wait,
685
+ state,
686
+ pendingItems
687
+ });
688
+ }
689
+ };
690
+ }
691
+
692
+ // src/application/diagnostic-journal.ts
693
+ async function createDiagnosticJournal(config) {
694
+ const limits = DiagnosticJournalLimitsSchema.parse(config.limits);
695
+ const lock = DiagnosticJournalLockPolicySchema.parse(config.lock ?? "refuse");
696
+ const storage = await createRotatingDiagnosticJournalStorage({
697
+ path: config.path,
698
+ maxFileBytes: limits.maxFileBytes,
699
+ maxFiles: limits.maxFiles,
700
+ mode: parseDiagnosticJournalMode(config.mode),
701
+ lock,
702
+ ...config.machineIdentity !== undefined && { machineIdentity: config.machineIdentity }
703
+ });
704
+ return createDiagnosticJournalManager({
705
+ eventSchema: config.eventSchema,
706
+ epoch: randomUUID(),
707
+ limits,
708
+ lock: { policy: lock, reclaimedStale: storage.reclaimedStale },
709
+ ...config.onFailure && { onFailure: config.onFailure }
710
+ }, storage);
711
+ }
712
+ export {
713
+ createDiagnosticJournal
714
+ };