stitchkit 0.77.0 → 0.79.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 (99) hide show
  1. package/dist/agent-runtime-browser.js +53 -581
  2. package/dist/agent-runtime-coding-tools.js +6 -5
  3. package/dist/agent-runtime-harness.js +20 -93
  4. package/dist/agent-runtime-openrouter.js +1 -1
  5. package/dist/agent-runtime.js +65 -61
  6. package/dist/application/decisions.d.ts +28 -25
  7. package/dist/application/decisions.d.ts.map +1 -1
  8. package/dist/application/kernel.d.ts +2 -0
  9. package/dist/application/kernel.d.ts.map +1 -1
  10. package/dist/application/keyspace.d.ts +11 -1
  11. package/dist/application/keyspace.d.ts.map +1 -1
  12. package/dist/application/schedule.d.ts.map +1 -1
  13. package/dist/application/schemas.d.ts +2 -0
  14. package/dist/application/schemas.d.ts.map +1 -1
  15. package/dist/application/server-resource.d.ts.map +1 -1
  16. package/dist/application/watch-hub.d.ts.map +1 -1
  17. package/dist/application-diagnostic-journal.d.ts +21 -0
  18. package/dist/application-diagnostic-journal.d.ts.map +1 -0
  19. package/dist/application-diagnostic-journal.js +632 -0
  20. package/dist/application-opentelemetry.js +3 -2
  21. package/dist/application-schemas.js +14 -98
  22. package/dist/application.d.ts +1 -2
  23. package/dist/application.d.ts.map +1 -1
  24. package/dist/application.js +155 -882
  25. package/dist/cli.js +13 -11
  26. package/dist/contract/index.js +15 -10
  27. package/dist/files/boundary.d.ts.map +1 -1
  28. package/dist/files.js +1 -1
  29. package/dist/{index-7b188kmz.js → index-2qzskm8h.js} +15 -1
  30. package/dist/{index-k2zczx1g.js → index-2rwwmr7v.js} +3 -3
  31. package/dist/{index-8eywc9zv.js → index-3gye9wzb.js} +6 -3
  32. package/dist/index-4fpa9dy2.js +51 -0
  33. package/dist/{index-7qy2ex0m.js → index-5mtc969c.js} +2 -2
  34. package/dist/index-5s3zajp8.js +192 -0
  35. package/dist/{index-3cwck0rm.js → index-6s7n2v50.js} +60 -374
  36. package/dist/{index-s4c8wy8m.js → index-758zx2ha.js} +11 -90
  37. package/dist/{index-qyrqwr4c.js → index-77fekveh.js} +4 -1
  38. package/dist/{index-58jzmnn4.js → index-7ag81v2f.js} +355 -34
  39. package/dist/index-7gnkny4z.js +326 -0
  40. package/dist/index-7zbps32p.js +89 -0
  41. package/dist/index-85vfqd7m.js +135 -0
  42. package/dist/{index-m668wzyc.js → index-9s6z8vgf.js} +17 -141
  43. package/dist/{index-bfcpjw20.js → index-actkayzn.js} +7 -3
  44. package/dist/index-b9aefrqd.js +20 -0
  45. package/dist/index-dre2ywck.js +48 -0
  46. package/dist/{index-nemjkxjp.js → index-dwf1f2ap.js} +16 -12
  47. package/dist/{index-wwst0td5.js → index-dx9xehwt.js} +1 -1
  48. package/dist/{index-ezmn6ac6.js → index-dxesg10d.js} +4 -4
  49. package/dist/{index-aczggrty.js → index-dxjjv399.js} +2 -2
  50. package/dist/{index-jqtsc9mj.js → index-egaabxkz.js} +3 -3
  51. package/dist/{index-vc1b0b1b.js → index-evcnmsc3.js} +49 -8
  52. package/dist/{index-nt1mp8km.js → index-fxz0v7vg.js} +84 -84
  53. package/dist/index-h4pj6fta.js +87 -0
  54. package/dist/{index-1rxswfbv.js → index-hb5pf3s4.js} +1 -1
  55. package/dist/{index-t8qyvrvg.js → index-k9ay6v6d.js} +21 -272
  56. package/dist/index-kzfs85xp.js +9 -0
  57. package/dist/{index-vbf2p6me.js → index-m9kzax52.js} +91 -89
  58. package/dist/{index-22by16v6.js → index-mgtxfe6b.js} +1 -1
  59. package/dist/index-p7hgdyhb.js +22 -0
  60. package/dist/index-pq95qkbf.js +0 -0
  61. package/dist/index-t0z5p5w6.js +318 -0
  62. package/dist/{index-s2rchahr.js → index-vwp69871.js} +13 -11
  63. package/dist/index-wc4x00bn.js +103 -0
  64. package/dist/{index-da1aqnhb.js → index-xcetq6j3.js} +8 -6
  65. package/dist/{index-f6n5n7nz.js → index-y01f4223.js} +33 -126
  66. package/dist/{index-35z5h2ty.js → index-ywd0y6m1.js} +18 -218
  67. package/dist/index.js +65 -1588
  68. package/dist/internal/deadline.d.ts +17 -0
  69. package/dist/internal/deadline.d.ts.map +1 -0
  70. package/dist/live.js +15 -89
  71. package/dist/node.js +17 -15
  72. package/dist/observability/context.d.ts.map +1 -1
  73. package/dist/observability/index.js +7 -6
  74. package/dist/primitives.js +5 -2
  75. package/dist/react.js +1 -1
  76. package/dist/remote.js +7 -6
  77. package/dist/server/event-bus.d.ts.map +1 -1
  78. package/dist/server/index.js +26 -38
  79. package/dist/telegram.js +1 -1
  80. package/dist/testing.js +86 -97
  81. package/dist/tool-invoker.js +11 -9
  82. package/dist/tools/async-operation-contract.d.ts +61 -0
  83. package/dist/tools/async-operation-contract.d.ts.map +1 -0
  84. package/dist/tools/async-operation.d.ts +2 -47
  85. package/dist/tools/async-operation.d.ts.map +1 -1
  86. package/dist/tools/view-file-contract.d.ts +98 -0
  87. package/dist/tools/view-file-contract.d.ts.map +1 -0
  88. package/dist/tools/view-file.d.ts +4 -83
  89. package/dist/tools/view-file.d.ts.map +1 -1
  90. package/dist/tools-contract.d.ts +29 -0
  91. package/dist/tools-contract.d.ts.map +1 -0
  92. package/dist/tools-contract.js +24 -0
  93. package/dist/tools.d.ts +3 -3
  94. package/dist/tools.d.ts.map +1 -1
  95. package/dist/tools.js +59 -95
  96. package/llms-full.txt +146 -14
  97. package/package.json +14 -7
  98. package/dist/index-ksp6e2ye.js +0 -24
  99. package/dist/index-vj3vvpaa.js +0 -307
@@ -1,3 +1,16 @@
1
+ import {
2
+ ApplicationAdmissionError,
3
+ ApplicationRestartInputSchema,
4
+ ApplicationRestartOutcomeSchema,
5
+ ApplicationRestartResultSchema,
6
+ ApplicationShutdownBudgetSchema,
7
+ ApplicationShutdownOptionsSchema,
8
+ createApplication
9
+ } from "./index-evcnmsc3.js";
10
+ import {
11
+ defineManagedResource,
12
+ managedResourceDependencyId
13
+ } from "./index-2k4yrqkc.js";
1
14
  import {
2
15
  ActivityIdSchema,
3
16
  ActivityLiveStateSchema,
@@ -6,41 +19,28 @@ import {
6
19
  ActivityStageSnapshotSchema,
7
20
  ActivityTokenBrand,
8
21
  ApplicationSnapshotSinkStatusSchema,
9
- BoundedChannelPolicySchema,
10
- BoundedChannelReaderError,
11
- BoundedChannelSnapshotSchema,
12
- BoundedChannelStateSchema,
13
- CreditWindowSnapshotSchema,
14
22
  ManagedScheduleDescriptorSchema,
15
23
  ManagedScheduleErrorPolicySchema,
16
24
  ManagedScheduleOverlapSchema,
17
25
  ManagedScheduleStatusSchema,
18
26
  createActivityProjection,
19
27
  createApplicationSnapshotSink,
20
- createBoundedChannel,
21
- createCreditWindow,
22
28
  createManagedSchedule
23
- } from "./index-3cwck0rm.js";
29
+ } from "./index-6s7n2v50.js";
24
30
  import {
25
- createBoundedSinkManager
26
- } from "./index-a59da114.js";
27
- import {
28
- argumentsDigest,
29
- stableValue
30
- } from "./index-a916km3r.js";
31
- import {
32
- ApplicationAdmissionError,
33
- ApplicationRestartInputSchema,
34
- ApplicationRestartOutcomeSchema,
35
- ApplicationRestartResultSchema,
36
- ApplicationShutdownBudgetSchema,
37
- ApplicationShutdownOptionsSchema,
38
- createApplication
39
- } from "./index-vc1b0b1b.js";
40
- import {
41
- defineManagedResource,
42
- managedResourceDependencyId
43
- } from "./index-2k4yrqkc.js";
31
+ DiagnosticJournalCloseResultSchema,
32
+ DiagnosticJournalFailurePhaseSchema,
33
+ DiagnosticJournalFrameSchema,
34
+ DiagnosticJournalLimitsSchema,
35
+ DiagnosticJournalLockPolicySchema,
36
+ DiagnosticJournalRefusalReasonSchema,
37
+ DiagnosticJournalStateSchema,
38
+ DiagnosticJournalStatusSchema,
39
+ DiagnosticJournalSubmitResultSchema,
40
+ DiagnosticJournalWaitResultSchema,
41
+ readDiagnosticJournalLockDiagnosis
42
+ } from "./index-wc4x00bn.js";
43
+ import"./index-pq95qkbf.js";
44
44
  import {
45
45
  ApplicationAdmissionSnapshotSchema,
46
46
  ApplicationHealthSchema,
@@ -53,12 +53,36 @@ import {
53
53
  ManagedResourceSnapshotSchema,
54
54
  ManagedResourceStateSchema,
55
55
  projectApplicationStatus
56
- } from "./index-8eywc9zv.js";
56
+ } from "./index-3gye9wzb.js";
57
+ import {
58
+ withDeadline
59
+ } from "./index-b9aefrqd.js";
57
60
  import"./index-3z73fh2c.js";
58
- import"./index-0w9abg87.js";
59
61
  import {
60
- isRecord
61
- } from "./index-qyrqwr4c.js";
62
+ createBoundedSinkManager
63
+ } from "./index-a59da114.js";
64
+ import {
65
+ createBackoff
66
+ } from "./index-h4pj6fta.js";
67
+ import {
68
+ watchKeyString
69
+ } from "./index-dre2ywck.js";
70
+ import"./index-ywd0y6m1.js";
71
+ import {
72
+ BoundedChannelPolicySchema,
73
+ BoundedChannelReaderError,
74
+ BoundedChannelSnapshotSchema,
75
+ BoundedChannelStateSchema,
76
+ CreditWindowSnapshotSchema,
77
+ createBoundedChannel,
78
+ createCreditWindow
79
+ } from "./index-7gnkny4z.js";
80
+ import {
81
+ argumentsDigest,
82
+ stableValue
83
+ } from "./index-a916km3r.js";
84
+ import"./index-0w9abg87.js";
85
+ import"./index-77fekveh.js";
62
86
  // src/application/admission.ts
63
87
  import { z } from "zod";
64
88
  var PositiveSafeIntegerSchema = z.number().int().positive().safe();
@@ -454,13 +478,19 @@ class DecisionUndecidedError extends Error {
454
478
 
455
479
  class DecisionPolicyError extends Error {
456
480
  policyId;
457
- constructor(policyId, detail) {
458
- super(`Policy "${policyId}" did not return a decision (${detail}). A policy returns { outcome: "allow" | "deny" | "defer" }, and a deny carries a reason.`);
481
+ trace;
482
+ constructor(policyId, detail, trace = []) {
483
+ super(`Policy "${policyId}" did not answer (${detail}). A policy returns { outcome: "allow" | "deny" | "defer" } within its deadline, and a deny carries a reason.`);
459
484
  this.name = "DecisionPolicyError";
460
485
  this.policyId = policyId;
486
+ this.trace = trace;
461
487
  }
462
488
  }
463
- function createDecisionPipeline(policies) {
489
+ function createDecisionPipeline(policies, config) {
490
+ const { policyTimeoutMs } = config;
491
+ if (!Number.isInteger(policyTimeoutMs) || policyTimeoutMs <= 0) {
492
+ throw new Error("[stitchkit] decision pipeline: policyTimeoutMs must be a positive integer number of milliseconds.");
493
+ }
464
494
  const seen = new Set;
465
495
  for (const policy of policies) {
466
496
  if (policy.id.trim() === "") {
@@ -476,10 +506,21 @@ function createDecisionPipeline(policies) {
476
506
  async decide(input) {
477
507
  const trace = [];
478
508
  for (const policy of policies) {
479
- const answer = await policy.decide(input);
509
+ let answer;
510
+ try {
511
+ const outcome = await withDeadline(policy.decide(input), policyTimeoutMs);
512
+ if (!outcome.settled) {
513
+ throw new DecisionPolicyError(policy.id, `did not answer within ${policyTimeoutMs}ms`, trace);
514
+ }
515
+ answer = outcome.value;
516
+ } catch (error) {
517
+ if (error instanceof DecisionPolicyError)
518
+ throw error;
519
+ throw new DecisionPolicyError(policy.id, error instanceof Error ? `threw: ${error.message}` : "threw", trace);
520
+ }
480
521
  const parsed = PolicyDecisionSchema.safeParse(answer);
481
522
  if (!parsed.success) {
482
- throw new DecisionPolicyError(policy.id, parsed.error.issues[0]?.message ?? "invalid");
523
+ throw new DecisionPolicyError(policy.id, parsed.error.issues[0]?.message ?? "invalid", trace);
483
524
  }
484
525
  const decision = parsed.data;
485
526
  trace.push(decision.outcome === "deny" ? { id: policy.id, outcome: "deny", reason: decision.reason } : { id: policy.id, outcome: decision.outcome });
@@ -494,728 +535,18 @@ function createDecisionPipeline(policies) {
494
535
  }
495
536
  };
496
537
  }
497
- // src/application/diagnostic-journal.ts
498
- import { randomUUID } from "node:crypto";
499
-
500
- // src/application/diagnostic-journal-contract.ts
501
- import { z as z3 } from "zod";
502
- var PositiveSafeIntegerSchema2 = z3.number().int().positive().safe();
503
- var FileModeSchema = z3.number().int().min(0).max(511);
504
- var DiagnosticJournalLimitsSchema = z3.object({
505
- maxEventBytes: PositiveSafeIntegerSchema2,
506
- maxPendingItems: PositiveSafeIntegerSchema2,
507
- maxPendingBytes: PositiveSafeIntegerSchema2,
508
- maxFileBytes: PositiveSafeIntegerSchema2,
509
- maxFiles: PositiveSafeIntegerSchema2
510
- }).strict().readonly();
511
- var DiagnosticJournalLockPolicySchema = z3.enum(["refuse", "reclaim-stale"]);
512
- var DiagnosticJournalStateSchema = z3.enum(["open", "draining", "closed", "failed"]);
513
- var DiagnosticJournalRefusalReasonSchema = z3.enum([
514
- "closed",
515
- "failed",
516
- "invalid",
517
- "oversized",
518
- "item-capacity",
519
- "byte-capacity"
520
- ]);
521
- var DiagnosticJournalRefusalCountersSchema = z3.object({
522
- closed: z3.number().int().nonnegative(),
523
- failed: z3.number().int().nonnegative(),
524
- invalid: z3.number().int().nonnegative(),
525
- oversized: z3.number().int().nonnegative(),
526
- "item-capacity": z3.number().int().nonnegative(),
527
- "byte-capacity": z3.number().int().nonnegative()
528
- }).strict().readonly();
529
- var DiagnosticJournalFailurePhaseSchema = z3.enum(["write", "rotation", "close"]);
530
- var DiagnosticJournalStatusSchema = z3.object({
531
- state: DiagnosticJournalStateSchema,
532
- epoch: z3.uuid(),
533
- limits: DiagnosticJournalLimitsSchema,
534
- lock: z3.object({
535
- policy: DiagnosticJournalLockPolicySchema,
536
- reclaimedStale: z3.boolean()
537
- }).strict().readonly(),
538
- received: z3.number().int().nonnegative(),
539
- accepted: z3.number().int().nonnegative(),
540
- refused: z3.number().int().nonnegative(),
541
- refusals: DiagnosticJournalRefusalCountersSchema,
542
- written: z3.number().int().nonnegative(),
543
- failedRecords: z3.number().int().nonnegative(),
544
- pendingItems: z3.number().int().nonnegative(),
545
- pendingBytes: z3.number().int().nonnegative(),
546
- inFlight: z3.boolean(),
547
- rotations: z3.number().int().nonnegative(),
548
- rotationFailures: z3.number().int().nonnegative(),
549
- partialTails: z3.number().int().nonnegative(),
550
- currentFileBytes: z3.number().int().nonnegative(),
551
- retainedFiles: z3.number().int().nonnegative(),
552
- lastAcceptedSequence: z3.number().int().positive().optional(),
553
- lastWrittenSequence: z3.number().int().positive().optional(),
554
- lastSettledSequence: z3.number().int().positive().optional(),
555
- lastFailure: z3.object({
556
- phase: DiagnosticJournalFailurePhaseSchema,
557
- sequence: z3.number().int().positive().optional()
558
- }).strict().readonly().optional()
559
- }).strict().readonly();
560
- var DiagnosticJournalSubmitResultSchema = z3.discriminatedUnion("outcome", [
561
- z3.object({
562
- outcome: z3.literal("accepted"),
563
- epoch: z3.uuid(),
564
- sequence: z3.number().int().positive()
565
- }).strict().readonly(),
566
- z3.object({
567
- outcome: z3.literal("refused"),
568
- reason: DiagnosticJournalRefusalReasonSchema
569
- }).strict().readonly()
570
- ]);
571
- var DiagnosticJournalWaitResultSchema = z3.object({
572
- outcome: z3.enum(["settled", "timed-out", "cancelled"]),
573
- state: DiagnosticJournalStateSchema,
574
- throughSequence: z3.number().int().nonnegative(),
575
- settledSequence: z3.number().int().nonnegative()
576
- }).strict().readonly();
577
- var DiagnosticJournalCloseResultSchema = z3.object({
578
- outcome: z3.enum(["closed", "timed-out", "cancelled"]),
579
- state: DiagnosticJournalStateSchema,
580
- pendingItems: z3.number().int().nonnegative()
581
- }).strict().readonly();
582
- var DiagnosticJournalFrameSchema = z3.object({
583
- schemaVersion: z3.literal(1),
584
- epoch: z3.uuid(),
585
- sequence: z3.number().int().positive(),
586
- event: z3.json()
587
- }).strict().readonly();
588
- function parseDiagnosticJournalMode(mode) {
589
- return FileModeSchema.parse(mode ?? 384);
590
- }
591
- function readDiagnosticJournalLockDiagnosis(error) {
592
- if (!isRecord(error))
593
- return;
594
- const diagnosis = error.journalLock;
595
- return isRecord(diagnosis) ? diagnosis : undefined;
596
- }
597
-
598
- // src/application/diagnostic-journal-manager.ts
599
- import { z as z4 } from "zod";
600
-
601
- // src/application/diagnostic-journal-storage.ts
602
- import { constants as constants2 } from "node:fs";
603
- import { lstat, open as open2, readdir, realpath, rename, unlink as unlink2 } from "node:fs/promises";
604
- import { basename, dirname, isAbsolute, resolve } from "node:path";
605
-
606
- // src/application/diagnostic-journal-lock.ts
607
- import { execFile } from "node:child_process";
608
- import { constants } from "node:fs";
609
- import { open, readFile, unlink } from "node:fs/promises";
610
- import { hostname, platform } from "node:os";
611
- import { promisify } from "node:util";
612
- function isExisting(error) {
613
- return isRecord(error) && error.code === "EEXIST";
614
- }
615
- function readOwner(text) {
616
- let parsed;
617
- try {
618
- parsed = JSON.parse(text);
619
- } catch {
620
- return;
621
- }
622
- if (!isRecord(parsed))
623
- return;
624
- const { pid, host, acquiredAt, machine } = parsed;
625
- if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0)
626
- return;
627
- if (typeof host !== "string" || host.length === 0)
628
- return;
629
- if (typeof acquiredAt !== "string")
630
- return;
631
- const identified = typeof machine === "string" && machine.length > 0 ? { machine } : {};
632
- return { pid, host, acquiredAt, ...identified };
633
- }
634
- var detectedIdentity;
635
- async function firstReadableLine(paths) {
636
- for (const path of paths) {
637
- try {
638
- const text = (await readFile(path, "utf8")).trim();
639
- if (text.length > 0)
640
- return text;
641
- } catch {}
642
- }
643
- return null;
644
- }
645
- var run = promisify(execFile);
646
- function parsePlatformUuid(dump) {
647
- return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(dump)?.[1] ?? null;
648
- }
649
- async function readDarwinPlatformUuid(command = "ioreg") {
650
- try {
651
- const { stdout } = await run(command, ["-rd1", "-c", "IOPlatformExpertDevice"], {
652
- timeout: 2000,
653
- maxBuffer: 1 << 20,
654
- encoding: "utf8"
655
- });
656
- return parsePlatformUuid(stdout);
657
- } catch {
658
- return null;
659
- }
660
- }
661
- async function machineIdentity(declared) {
662
- if (declared !== undefined)
663
- return declared;
664
- if (detectedIdentity !== undefined)
665
- return detectedIdentity;
666
- detectedIdentity = platform() === "darwin" ? await readDarwinPlatformUuid() : await firstReadableLine(["/etc/machine-id", "/var/lib/dbus/machine-id"]);
667
- return detectedIdentity;
668
- }
669
- function attribute(owner, identity) {
670
- if (identity !== null && owner.machine !== undefined) {
671
- return owner.machine === identity ? "this-machine" : "another-machine";
672
- }
673
- return owner.host === hostname() ? "this-machine" : "unattributable";
674
- }
675
- async function probeLiveness(owner) {
676
- try {
677
- process.kill(owner.pid, 0);
678
- } catch (error) {
679
- return isRecord(error) && error.code === "ESRCH" ? "gone" : "alive";
680
- }
681
- return await isZombieProcess(owner.pid) ? "gone" : "alive";
682
- }
683
- async function isZombieProcess(pid, seam = {}) {
684
- const { procRoot = "/proc", psCommand = "ps" } = seam;
685
- try {
686
- const stat = await readFile(`${procRoot}/${pid}/stat`, "utf8");
687
- return stat.slice(stat.lastIndexOf(")") + 1).trimStart().startsWith("Z");
688
- } catch {}
689
- try {
690
- const { stdout } = await run(psCommand, ["-o", "state=", "-p", String(pid)], {
691
- timeout: 2000,
692
- maxBuffer: 1 << 16,
693
- encoding: "utf8"
694
- });
695
- return stdout.trim().startsWith("Z");
696
- } catch {
697
- return false;
698
- }
699
- }
700
- async function diagnose(owner, declaredIdentity) {
701
- if (!owner)
702
- return { attribution: "unattributable", liveness: "not-probed", owner: null };
703
- const attribution = attribute(owner, await machineIdentity(declaredIdentity));
704
- const record = {
705
- pid: owner.pid,
706
- host: owner.host,
707
- acquiredAt: owner.acquiredAt,
708
- ...owner.machine !== undefined && { machine: owner.machine }
709
- };
710
- if (attribution !== "this-machine")
711
- return { attribution, liveness: "not-probed", owner: record };
712
- return { attribution, liveness: await probeLiveness(owner), owner: record };
713
- }
714
- async function readLockOwner(lockPath) {
715
- let handle;
716
- try {
717
- handle = await open(lockPath, constants.O_RDONLY | constants.O_NOFOLLOW);
718
- return readOwner(await handle.readFile("utf8"));
719
- } catch {
720
- return;
721
- } finally {
722
- await handle?.close().catch(() => {
723
- return;
724
- });
725
- }
726
- }
727
- async function createExclusive(lockPath, mode, declaredIdentity) {
728
- const handle = await open(lockPath, "wx", mode);
729
- const identity = await machineIdentity(declaredIdentity);
730
- const owner = {
731
- pid: process.pid,
732
- host: hostname(),
733
- acquiredAt: new Date().toISOString(),
734
- ...identity !== null && { machine: identity }
735
- };
736
- await handle.writeFile(`${JSON.stringify(owner)}
737
- `, "utf8");
738
- return handle;
739
- }
740
- async function acquireDiagnosticJournalLock(lockPath, mode, policy, declaredIdentity) {
741
- try {
742
- return {
743
- handle: await createExclusive(lockPath, mode, declaredIdentity),
744
- reclaimedStale: false
745
- };
746
- } catch (error) {
747
- if (policy !== "reclaim-stale" || !isExisting(error))
748
- throw error;
749
- const owner = await readLockOwner(lockPath);
750
- const diagnosis = await diagnose(owner, declaredIdentity);
751
- if (!owner || diagnosis.liveness !== "gone") {
752
- if (isRecord(error))
753
- Object.assign(error, { journalLock: diagnosis });
754
- throw error;
755
- }
756
- await unlink(lockPath).catch(() => {
757
- return;
758
- });
759
- return {
760
- handle: await createExclusive(lockPath, mode, declaredIdentity),
761
- reclaimedStale: true
762
- };
763
- }
764
- }
765
-
766
- // src/application/diagnostic-journal-storage.ts
767
- class DiagnosticJournalStorageError extends Error {
768
- phase;
769
- constructor(phase, message, options) {
770
- super(message, options);
771
- this.phase = phase;
772
- this.name = "DiagnosticJournalStorageError";
773
- }
774
- }
775
- function isMissing(error) {
776
- return isRecord(error) && error.code === "ENOENT";
777
- }
778
- async function removeIfPresent(path) {
779
- try {
780
- const info = await lstat(path);
781
- if (info.isSymbolicLink() || !info.isFile()) {
782
- throw new Error("Diagnostic journal generations must be regular files");
783
- }
784
- await unlink2(path);
785
- } catch (error) {
786
- if (!isMissing(error))
787
- throw error;
788
- }
789
- }
790
- async function moveIfPresent(from, to) {
791
- try {
792
- const info = await lstat(from);
793
- if (info.isSymbolicLink() || !info.isFile()) {
794
- throw new Error("Diagnostic journal generations must be regular files");
795
- }
796
- await rename(from, to);
797
- return true;
798
- } catch (error) {
799
- if (isMissing(error))
800
- return false;
801
- throw error;
802
- }
803
- }
804
- function generation(path, index) {
805
- return `${path}.${index}`;
806
- }
807
- function generationIndex(name, prefix) {
808
- if (!name.startsWith(prefix))
809
- return;
810
- const suffix = name.slice(prefix.length);
811
- if (!/^\d+$/.test(suffix))
812
- return;
813
- const value = Number(suffix);
814
- return Number.isSafeInteger(value) && value > 0 ? value : undefined;
815
- }
816
- async function createRotatingDiagnosticJournalStorage(config) {
817
- if (!isAbsolute(config.path) || resolve(config.path) !== config.path) {
818
- throw new TypeError("Diagnostic journal path must be normalized and absolute");
819
- }
820
- const parent = await realpath(dirname(config.path));
821
- const journalPath = resolve(parent, basename(config.path));
822
- const lockPath = `${journalPath}.lock`;
823
- const { handle: lock, reclaimedStale } = await acquireDiagnosticJournalLock(lockPath, config.mode, config.lock, config.machineIdentity);
824
- let handle;
825
- let currentFileBytes = 0;
826
- let retainedFiles = 1;
827
- let rotations = 0;
828
- let partialTails = 0;
829
- let closed = false;
830
- const openCurrent = async () => {
831
- handle = await open2(journalPath, constants2.O_APPEND | constants2.O_CREAT | constants2.O_RDWR | constants2.O_NOFOLLOW, config.mode);
832
- const info = await handle.stat();
833
- if (!info.isFile()) {
834
- await handle.close();
835
- handle = undefined;
836
- throw new Error("Diagnostic journal path must be a regular file");
837
- }
838
- currentFileBytes = Number(info.size);
839
- };
840
- const rotate = async () => {
841
- try {
842
- await handle?.close();
843
- handle = undefined;
844
- if (config.maxFiles === 1) {
845
- await removeIfPresent(journalPath);
846
- } else {
847
- await removeIfPresent(generation(journalPath, config.maxFiles - 1));
848
- for (let index = config.maxFiles - 2;index >= 1; index -= 1) {
849
- await moveIfPresent(generation(journalPath, index), generation(journalPath, index + 1));
850
- }
851
- await moveIfPresent(journalPath, generation(journalPath, 1));
852
- }
853
- retainedFiles = Math.min(config.maxFiles, retainedFiles + 1);
854
- rotations += 1;
855
- await openCurrent();
856
- } catch (error) {
857
- throw new DiagnosticJournalStorageError("rotation", "Diagnostic journal rotation failed", { cause: error });
858
- }
859
- };
860
- try {
861
- const names = await readdir(parent);
862
- const prefix = `${basename(journalPath)}.`;
863
- let existingGenerations = 0;
864
- for (const name of names) {
865
- const index = generationIndex(name, prefix);
866
- if (index === undefined)
867
- continue;
868
- if (index >= config.maxFiles) {
869
- await removeIfPresent(resolve(parent, name));
870
- } else {
871
- const info = await lstat(resolve(parent, name));
872
- if (info.isSymbolicLink() || !info.isFile()) {
873
- throw new Error("Diagnostic journal generations must be regular files");
874
- }
875
- existingGenerations += 1;
876
- }
877
- }
878
- retainedFiles = Math.min(config.maxFiles, existingGenerations + 1);
879
- await openCurrent();
880
- if (currentFileBytes > 0) {
881
- const tail = new Uint8Array(1);
882
- const read = await handle?.read(tail, 0, 1, currentFileBytes - 1);
883
- if (read?.bytesRead === 1 && tail[0] !== 10) {
884
- partialTails += 1;
885
- await rotate();
886
- }
887
- }
888
- } catch (error) {
889
- await handle?.close().catch(() => {
890
- return;
891
- });
892
- await lock.close().catch(() => {
893
- return;
894
- });
895
- await unlink2(lockPath).catch(() => {
896
- return;
897
- });
898
- throw error;
899
- }
900
- const snapshot = () => ({
901
- currentFileBytes,
902
- retainedFiles,
903
- rotations,
904
- partialTails
905
- });
906
- return {
907
- reclaimedStale,
908
- async append(bytes) {
909
- if (closed || !handle) {
910
- throw new DiagnosticJournalStorageError("write", "Diagnostic journal storage is closed");
911
- }
912
- let rotated = false;
913
- if (currentFileBytes > 0 && currentFileBytes + bytes.byteLength > config.maxFileBytes) {
914
- await rotate();
915
- rotated = true;
916
- }
917
- try {
918
- await handle.writeFile(bytes);
919
- currentFileBytes += bytes.byteLength;
920
- return { ...snapshot(), rotated };
921
- } catch (error) {
922
- throw new DiagnosticJournalStorageError("write", "Diagnostic journal write failed", {
923
- cause: error
924
- });
925
- }
926
- },
927
- snapshot,
928
- async close() {
929
- if (closed)
930
- return;
931
- closed = true;
932
- let failure;
933
- try {
934
- await handle?.close();
935
- } catch (error) {
936
- failure = error;
937
- }
938
- try {
939
- await lock.close();
940
- await unlink2(lockPath);
941
- } catch (error) {
942
- failure ??= error;
943
- }
944
- if (failure !== undefined) {
945
- throw new DiagnosticJournalStorageError("close", "Diagnostic journal close failed", {
946
- cause: failure
947
- });
948
- }
949
- }
950
- };
951
- }
952
-
953
- // src/application/diagnostic-journal-manager.ts
954
- function waitBudget(options) {
955
- if (options.timeoutMs === undefined)
956
- return;
957
- if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0) {
958
- throw new TypeError("Diagnostic journal timeoutMs must be a positive safe integer");
959
- }
960
- return options.timeoutMs;
961
- }
962
- function createDiagnosticJournalManager(config, storage) {
963
- const limits = DiagnosticJournalLimitsSchema.parse(config.limits);
964
- const encoder = new TextEncoder;
965
- const channel = createBoundedChannel({
966
- policy: "ordered",
967
- maxItems: limits.maxPendingItems,
968
- maxBytes: limits.maxPendingBytes,
969
- sizeOf: (frame) => frame.bytes.byteLength
970
- });
971
- const refusals = {
972
- closed: 0,
973
- failed: 0,
974
- invalid: 0,
975
- oversized: 0,
976
- "item-capacity": 0,
977
- "byte-capacity": 0
978
- };
979
- const progressWaiters = new Set;
980
- let state = "open";
981
- let received = 0;
982
- let accepted = 0;
983
- let refused = 0;
984
- let written = 0;
985
- let failedRecords = 0;
986
- let pendingItems = 0;
987
- let pendingBytes = 0;
988
- let inFlight = false;
989
- let rotationFailures = 0;
990
- let lastAcceptedSequence = 0;
991
- let lastWrittenSequence = 0;
992
- let lastSettledSequence = 0;
993
- let lastFailure;
994
- let physicalClosed = false;
995
- const isFailed = () => state === "failed";
996
- const notifyProgress = () => {
997
- for (const waiter of progressWaiters)
998
- waiter();
999
- };
1000
- const reportFailure = (failure) => {
1001
- if (!config.onFailure)
1002
- return;
1003
- Promise.resolve().then(() => config.onFailure?.(failure)).catch(() => {
1004
- return;
1005
- });
1006
- };
1007
- const markFailure = (error, fallbackPhase, sequence) => {
1008
- const phase = error instanceof DiagnosticJournalStorageError ? error.phase : fallbackPhase;
1009
- if (phase === "rotation")
1010
- rotationFailures += 1;
1011
- lastFailure = { phase, ...sequence !== undefined && { sequence } };
1012
- state = "failed";
1013
- channel.close({ mode: "drain" });
1014
- reportFailure({ phase, error, ...sequence !== undefined && { sequence } });
1015
- };
1016
- const worker = (async () => {
1017
- for await (const frame of channel) {
1018
- inFlight = true;
1019
- if (isFailed()) {
1020
- failedRecords += 1;
1021
- } else {
1022
- try {
1023
- await storage.append(frame.bytes);
1024
- written += 1;
1025
- lastWrittenSequence = frame.sequence;
1026
- } catch (error) {
1027
- failedRecords += 1;
1028
- markFailure(error, "write", frame.sequence);
1029
- }
1030
- }
1031
- pendingItems -= 1;
1032
- pendingBytes -= frame.bytes.byteLength;
1033
- lastSettledSequence = frame.sequence;
1034
- inFlight = false;
1035
- notifyProgress();
1036
- }
1037
- try {
1038
- await storage.close();
1039
- } catch (error) {
1040
- markFailure(error, "close");
1041
- }
1042
- physicalClosed = true;
1043
- if (!isFailed())
1044
- state = "closed";
1045
- notifyProgress();
1046
- })();
1047
- worker.catch((error) => {
1048
- markFailure(error, "close");
1049
- physicalClosed = true;
1050
- notifyProgress();
1051
- });
1052
- const refuse = (reason) => {
1053
- refused += 1;
1054
- refusals[reason] += 1;
1055
- return DiagnosticJournalSubmitResultSchema.parse({ outcome: "refused", reason });
1056
- };
1057
- const waitUntil = (predicate, options) => {
1058
- const timeoutMs = waitBudget(options);
1059
- if (predicate())
1060
- return Promise.resolve("settled");
1061
- if (options.signal?.aborted)
1062
- return Promise.resolve("cancelled");
1063
- return new Promise((resolve2) => {
1064
- let settled = false;
1065
- let timer;
1066
- const finish = (outcome) => {
1067
- if (settled)
1068
- return;
1069
- settled = true;
1070
- progressWaiters.delete(onProgress);
1071
- options.signal?.removeEventListener("abort", onAbort);
1072
- if (timer)
1073
- clearTimeout(timer);
1074
- resolve2(outcome);
1075
- };
1076
- const onProgress = () => {
1077
- if (predicate())
1078
- finish("settled");
1079
- };
1080
- const onAbort = () => finish("cancelled");
1081
- progressWaiters.add(onProgress);
1082
- options.signal?.addEventListener("abort", onAbort, { once: true });
1083
- if (timeoutMs !== undefined)
1084
- timer = setTimeout(() => finish("timed-out"), timeoutMs);
1085
- onProgress();
1086
- });
1087
- };
1088
- const getStatus = () => {
1089
- const file = storage.snapshot();
1090
- return DiagnosticJournalStatusSchema.parse({
1091
- state,
1092
- epoch: config.epoch,
1093
- limits,
1094
- lock: config.lock,
1095
- received,
1096
- accepted,
1097
- refused,
1098
- refusals,
1099
- written,
1100
- failedRecords,
1101
- pendingItems,
1102
- pendingBytes,
1103
- inFlight,
1104
- rotations: file.rotations,
1105
- rotationFailures,
1106
- partialTails: file.partialTails,
1107
- currentFileBytes: file.currentFileBytes,
1108
- retainedFiles: file.retainedFiles,
1109
- ...lastAcceptedSequence > 0 && { lastAcceptedSequence },
1110
- ...lastWrittenSequence > 0 && { lastWrittenSequence },
1111
- ...lastSettledSequence > 0 && { lastSettledSequence },
1112
- ...lastFailure && { lastFailure }
1113
- });
1114
- };
1115
- return {
1116
- submit(event) {
1117
- received += 1;
1118
- if (state === "failed")
1119
- return refuse("failed");
1120
- if (state !== "open")
1121
- return refuse("closed");
1122
- const parsed = config.eventSchema.safeParse(event);
1123
- if (!parsed.success)
1124
- return refuse("invalid");
1125
- const json = z4.json().safeParse(parsed.data);
1126
- if (!json.success)
1127
- return refuse("invalid");
1128
- const payload = encoder.encode(JSON.stringify(json.data));
1129
- if (payload.byteLength > limits.maxEventBytes)
1130
- return refuse("oversized");
1131
- if (pendingItems >= limits.maxPendingItems)
1132
- return refuse("item-capacity");
1133
- const sequence = lastAcceptedSequence + 1;
1134
- const frame = DiagnosticJournalFrameSchema.parse({
1135
- schemaVersion: 1,
1136
- epoch: config.epoch,
1137
- sequence,
1138
- event: json.data
1139
- });
1140
- const bytes = encoder.encode(`${JSON.stringify(frame)}
1141
- `);
1142
- if (bytes.byteLength > limits.maxFileBytes)
1143
- return refuse("oversized");
1144
- if (pendingBytes + bytes.byteLength > limits.maxPendingBytes) {
1145
- return refuse("byte-capacity");
1146
- }
1147
- const offered = channel.offer({ sequence, bytes });
1148
- if (offered.outcome === "refused") {
1149
- return refuse(offered.reason === "not-open" ? "closed" : "item-capacity");
1150
- }
1151
- accepted += 1;
1152
- pendingItems += 1;
1153
- pendingBytes += bytes.byteLength;
1154
- lastAcceptedSequence = sequence;
1155
- return DiagnosticJournalSubmitResultSchema.parse({
1156
- outcome: "accepted",
1157
- epoch: config.epoch,
1158
- sequence
1159
- });
1160
- },
1161
- async flush(options = {}) {
1162
- const throughSequence = lastAcceptedSequence;
1163
- const outcome = await waitUntil(() => lastSettledSequence >= throughSequence, options);
1164
- return DiagnosticJournalWaitResultSchema.parse({
1165
- outcome,
1166
- state,
1167
- throughSequence,
1168
- settledSequence: lastSettledSequence
1169
- });
1170
- },
1171
- getStatus,
1172
- async close(options = {}) {
1173
- if (state === "open") {
1174
- state = "draining";
1175
- channel.close({ mode: "drain" });
1176
- }
1177
- const wait = await waitUntil(() => physicalClosed, options);
1178
- return DiagnosticJournalCloseResultSchema.parse({
1179
- outcome: wait === "settled" ? "closed" : wait,
1180
- state,
1181
- pendingItems
1182
- });
1183
- }
1184
- };
1185
- }
1186
-
1187
- // src/application/diagnostic-journal.ts
1188
- async function createDiagnosticJournal(config) {
1189
- const limits = DiagnosticJournalLimitsSchema.parse(config.limits);
1190
- const lock = DiagnosticJournalLockPolicySchema.parse(config.lock ?? "refuse");
1191
- const storage = await createRotatingDiagnosticJournalStorage({
1192
- path: config.path,
1193
- maxFileBytes: limits.maxFileBytes,
1194
- maxFiles: limits.maxFiles,
1195
- mode: parseDiagnosticJournalMode(config.mode),
1196
- lock,
1197
- ...config.machineIdentity !== undefined && { machineIdentity: config.machineIdentity }
1198
- });
1199
- return createDiagnosticJournalManager({
1200
- eventSchema: config.eventSchema,
1201
- epoch: randomUUID(),
1202
- limits,
1203
- lock: { policy: lock, reclaimedStale: storage.reclaimedStale },
1204
- ...config.onFailure && { onFailure: config.onFailure }
1205
- }, storage);
1206
- }
1207
538
  // src/application/events.ts
1208
- import { z as z5 } from "zod";
1209
- var ApplicationLifecycleEventSchema = z5.object({
1210
- type: z5.literal("application-state"),
539
+ import { z as z3 } from "zod";
540
+ var ApplicationLifecycleEventSchema = z3.object({
541
+ type: z3.literal("application-state"),
1211
542
  applicationId: ApplicationIdSchema,
1212
- epoch: z5.string().uuid(),
1213
- revision: z5.number().int().nonnegative(),
543
+ epoch: z3.string().uuid(),
544
+ revision: z3.number().int().nonnegative(),
1214
545
  lifecycle: ApplicationLifecycleSchema,
1215
546
  health: ApplicationHealthSchema,
1216
- ready: z5.boolean(),
1217
- capturedAt: z5.string().datetime({ offset: true }),
1218
- resources: z5.array(ManagedResourceSnapshotSchema).readonly()
547
+ ready: z3.boolean(),
548
+ capturedAt: z3.string().datetime({ offset: true }),
549
+ resources: z3.array(ManagedResourceSnapshotSchema).readonly()
1219
550
  }).strict().readonly();
1220
551
  function applicationLifecycleEvent(snapshot) {
1221
552
  return ApplicationLifecycleEventSchema.parse({
@@ -1246,13 +577,13 @@ function createApplicationEventSink(config) {
1246
577
  };
1247
578
  }
1248
579
  // src/application/health.ts
1249
- import { z as z6 } from "zod";
1250
- var ApplicationHealthHandlerOptionsSchema = z6.object({
1251
- kind: z6.enum(["liveness", "readiness"]),
1252
- retryAfterSeconds: z6.number().int().nonnegative().default(5)
580
+ import { z as z4 } from "zod";
581
+ var ApplicationHealthHandlerOptionsSchema = z4.object({
582
+ kind: z4.enum(["liveness", "readiness"]),
583
+ retryAfterSeconds: z4.number().int().nonnegative().default(5)
1253
584
  });
1254
- var ApplicationOperationalHandlersOptionsSchema = z6.object({
1255
- retryAfterSeconds: z6.number().int().nonnegative().default(5)
585
+ var ApplicationOperationalHandlersOptionsSchema = z4.object({
586
+ retryAfterSeconds: z4.number().int().nonnegative().default(5)
1256
587
  });
1257
588
  function createApplicationHealthHandler(application, options) {
1258
589
  const parsed = ApplicationHealthHandlerOptionsSchema.parse(options);
@@ -1302,7 +633,7 @@ function keyspaceResource(declaration, config) {
1302
633
  id: config.id ?? `keyspace:${declaration.name}`,
1303
634
  ...config.dependsOn && { dependsOn: config.dependsOn },
1304
635
  async start(_context) {
1305
- await machine.load();
636
+ await machine.open();
1306
637
  return { value: machine.keyspace };
1307
638
  },
1308
639
  stopAdmission: machine.stopAdmission,
@@ -1315,9 +646,14 @@ function keyspaceMachine(declaration, config) {
1315
646
  const maxPending = config.maxPendingWrites ?? 1024;
1316
647
  const records = new Map;
1317
648
  let admitting = true;
649
+ let backend;
650
+ const currentBackend = () => {
651
+ backend ??= typeof config.backend === "function" ? config.backend() : config.backend;
652
+ return backend;
653
+ };
1318
654
  let pending = 0;
1319
655
  let chain = Promise.resolve();
1320
- function run2(work) {
656
+ function run(work) {
1321
657
  if (!admitting) {
1322
658
  return Promise.reject(new Error(`[stitchkit] keyspace "${declaration.name}" is shutting down and is not accepting writes.`));
1323
659
  }
@@ -1344,8 +680,8 @@ function keyspaceMachine(declaration, config) {
1344
680
  put(value) {
1345
681
  const parsed = declaration.schema.parse(value);
1346
682
  const key = declaration.key(parsed);
1347
- return run2(async () => {
1348
- await config.backend.put(key, parsed);
683
+ return run(async () => {
684
+ await currentBackend().put(key, parsed);
1349
685
  records.set(key, parsed);
1350
686
  config.onChanged?.({
1351
687
  keyspace: declaration.name,
@@ -1356,8 +692,8 @@ function keyspaceMachine(declaration, config) {
1356
692
  });
1357
693
  },
1358
694
  delete(key) {
1359
- return run2(async () => {
1360
- await config.backend.delete(key);
695
+ return run(async () => {
696
+ await currentBackend().delete(key);
1361
697
  records.delete(key);
1362
698
  config.onChanged?.({ keyspace: declaration.name, key, change: "delete" });
1363
699
  });
@@ -1371,11 +707,19 @@ function keyspaceMachine(declaration, config) {
1371
707
  return {
1372
708
  keyspace,
1373
709
  async load() {
1374
- for (const stored of await config.backend.load()) {
710
+ for (const stored of await currentBackend().load()) {
1375
711
  const value = declaration.schema.parse(stored);
1376
712
  records.set(declaration.key(value), value);
1377
713
  }
1378
714
  },
715
+ async open() {
716
+ backend = undefined;
717
+ records.clear();
718
+ chain = Promise.resolve();
719
+ pending = 0;
720
+ admitting = true;
721
+ await this.load();
722
+ },
1379
723
  stopAdmission() {
1380
724
  admitting = false;
1381
725
  },
@@ -1384,7 +728,7 @@ function keyspaceMachine(declaration, config) {
1384
728
  },
1385
729
  async close() {
1386
730
  reportUnwritten();
1387
- await config.backend.close?.();
731
+ await backend?.close?.();
1388
732
  },
1389
733
  force() {
1390
734
  reportUnwritten();
@@ -1475,6 +819,11 @@ function managedServerResource(config) {
1475
819
  ...config.dependsOn && { dependsOn: config.dependsOn },
1476
820
  ...config.required !== undefined && { required: config.required },
1477
821
  async start(context) {
822
+ if (startAttempted && typeof config.server !== "function") {
823
+ throw new Error(`[stitchkit] managed server "${config.id}" cannot be restarted: it was given a server instance, and that one has been shut down. Pass a factory — \`server: (context) => …\` — so each generation gets its own.`);
824
+ }
825
+ shutdownPromise = undefined;
826
+ started = undefined;
1478
827
  startAttempted = true;
1479
828
  started = await resolveServer(context);
1480
829
  return { value: started };
@@ -1493,109 +842,6 @@ function managedServerResource(config) {
1493
842
  }
1494
843
  });
1495
844
  }
1496
- // src/browser/resumable.ts
1497
- import { z as z7 } from "zod";
1498
- var PositiveSafeIntegerSchema3 = z7.number().int().positive().safe();
1499
- var BackoffPolicySchema = z7.object({
1500
- minDelayMs: PositiveSafeIntegerSchema3,
1501
- maxDelayMs: PositiveSafeIntegerSchema3,
1502
- jitter: z7.number().min(0).max(1)
1503
- }).strict().readonly().refine((policy) => policy.maxDelayMs >= policy.minDelayMs, {
1504
- message: "maxDelayMs must be at least minDelayMs"
1505
- });
1506
- function createBackoff(policy, random = Math.random) {
1507
- const { minDelayMs, maxDelayMs, jitter } = BackoffPolicySchema.parse(policy);
1508
- let attempt = 0;
1509
- return {
1510
- next() {
1511
- const exponential = Math.min(maxDelayMs, minDelayMs * 2 ** attempt);
1512
- attempt += 1;
1513
- const spread = exponential * jitter * random();
1514
- return Math.max(1, Math.round(exponential - spread));
1515
- },
1516
- reset() {
1517
- attempt = 0;
1518
- }
1519
- };
1520
- }
1521
-
1522
- // src/live/watch-contract.ts
1523
- import { z as z9 } from "zod";
1524
-
1525
- // src/browser/live-state.ts
1526
- import { z as z8 } from "zod";
1527
- var PositiveSafeIntegerSchema4 = z8.number().int().positive().safe();
1528
- var LiveStatePhaseSchema = z8.enum([
1529
- "idle",
1530
- "opening",
1531
- "live",
1532
- "resync-required",
1533
- "unavailable",
1534
- "closed"
1535
- ]);
1536
- var LiveStateStopReasonSchema = z8.enum([
1537
- "gap",
1538
- "buffer-overflow",
1539
- "source-unavailable",
1540
- "source-error",
1541
- "controller-error",
1542
- "controller-capacity"
1543
- ]);
1544
- var LiveStateControllerStatusSchema = z8.object({
1545
- phase: LiveStatePhaseSchema,
1546
- generation: z8.number().int().nonnegative(),
1547
- hasValue: z8.boolean(),
1548
- bufferedEvents: z8.number().int().nonnegative(),
1549
- bufferedBytes: z8.number().int().nonnegative(),
1550
- receivedEvents: z8.number().int().nonnegative(),
1551
- appliedEvents: z8.number().int().nonnegative(),
1552
- duplicateEvents: z8.number().int().nonnegative(),
1553
- gapEvents: z8.number().int().nonnegative(),
1554
- refusedEvents: z8.number().int().nonnegative(),
1555
- reason: LiveStateStopReasonSchema.optional()
1556
- }).strict().readonly();
1557
-
1558
- // src/live/watch-contract.ts
1559
- var WatchKeySchema = z9.object({
1560
- service: z9.string().min(1),
1561
- action: z9.string().min(1),
1562
- digest: z9.string().min(1)
1563
- }).strict().readonly();
1564
- var WatchOpenSchema = z9.object({ key: WatchKeySchema, args: z9.unknown() }).readonly();
1565
- var WatchAcceptedSchema = z9.object({
1566
- accepted: z9.boolean(),
1567
- reason: z9.string().optional()
1568
- }).strict().readonly();
1569
- var WatchValueSchema = z9.object({
1570
- key: WatchKeySchema,
1571
- revision: z9.number().int().nonnegative(),
1572
- value: z9.unknown()
1573
- }).readonly();
1574
- var WatchStateSchema = z9.object({
1575
- key: WatchKeySchema,
1576
- phase: LiveStatePhaseSchema,
1577
- reason: LiveStateStopReasonSchema.optional(),
1578
- code: z9.string().optional(),
1579
- message: z9.string().optional()
1580
- }).readonly();
1581
- var WATCH_OPEN = "stitchkit.watch.open";
1582
- var WATCH_CLOSE = "stitchkit.watch.close";
1583
- var WATCH_VALUE = "stitchkit.watch.value";
1584
- var WATCH_STATE = "stitchkit.watch.state";
1585
- var watchContract = {
1586
- serverToClient: {
1587
- [WATCH_VALUE]: { args: z9.tuple([WatchValueSchema]) },
1588
- [WATCH_STATE]: { args: z9.tuple([WatchStateSchema]) }
1589
- },
1590
- clientToServer: {
1591
- [WATCH_OPEN]: { args: z9.tuple([WatchOpenSchema]), ack: WatchAcceptedSchema },
1592
- [WATCH_CLOSE]: { args: z9.tuple([z9.object({ key: WatchKeySchema }).readonly()]) }
1593
- }
1594
- };
1595
- function watchKeyString(key) {
1596
- return `${key.service}/${key.action}/${key.digest}`;
1597
- }
1598
-
1599
845
  // src/application/watch-hub.ts
1600
846
  var DEFAULT_BACKOFF = { minDelayMs: 250, maxDelayMs: 30000, jitter: 0.2 };
1601
847
  function watchKey(operation, args) {
@@ -1614,17 +860,29 @@ function createWatchHub(config) {
1614
860
  const same = config.same ?? ((previous, next) => JSON.stringify(stableValue(previous)) === JSON.stringify(stableValue(next)));
1615
861
  let reads = 0;
1616
862
  let closed = false;
863
+ function tell(key, deliver) {
864
+ try {
865
+ deliver();
866
+ } catch (error) {
867
+ config.logger?.warn?.("[stitchkit] watch subscriber threw", {
868
+ key: watchKeyString(key),
869
+ error: error instanceof Error ? error.message : String(error)
870
+ });
871
+ }
872
+ }
1617
873
  function announceState(source, state) {
1618
874
  source.state = state;
1619
- for (const subscriber of source.subscribers)
1620
- subscriber.state(state);
875
+ for (const subscriber of source.subscribers) {
876
+ tell(source.key, () => subscriber.state(state));
877
+ }
1621
878
  }
1622
879
  function publish(source, value) {
1623
880
  source.revision += 1;
1624
881
  source.value = value;
1625
882
  const frame = { key: source.key, revision: source.revision, value };
1626
- for (const subscriber of source.subscribers)
1627
- subscriber.value(frame);
883
+ for (const subscriber of source.subscribers) {
884
+ tell(source.key, () => subscriber.value(frame));
885
+ }
1628
886
  }
1629
887
  async function pump(source) {
1630
888
  if (source.reading || closed)
@@ -1754,10 +1012,20 @@ function createWatchHub(config) {
1754
1012
  const source = acquire(operation, key, args);
1755
1013
  source.subscribers.add(subscriber);
1756
1014
  keys.add(id);
1757
- if (source.signature !== undefined) {
1758
- subscriber.value({ key, revision: source.revision, value: source.value });
1015
+ try {
1016
+ if (source.signature !== undefined) {
1017
+ subscriber.value({ key, revision: source.revision, value: source.value });
1018
+ }
1019
+ subscriber.state(source.state);
1020
+ } catch (error) {
1021
+ source.subscribers.delete(subscriber);
1022
+ keys.delete(id);
1023
+ release(source);
1024
+ return {
1025
+ accepted: false,
1026
+ reason: error instanceof Error ? error.message : String(error)
1027
+ };
1759
1028
  }
1760
- subscriber.state(source.state);
1761
1029
  pump(source);
1762
1030
  return { accepted: true };
1763
1031
  },
@@ -1788,14 +1056,20 @@ function createWatchHub(config) {
1788
1056
  size: () => sources.size,
1789
1057
  close() {
1790
1058
  closed = true;
1791
- for (const source of sources.values()) {
1059
+ const teardown = [...sources.values()];
1060
+ sources.clear();
1061
+ held.clear();
1062
+ for (const source of teardown) {
1792
1063
  clearTimeout(source.retry);
1793
1064
  clearTimeout(source.release);
1065
+ announceState(source, {
1066
+ key: source.key,
1067
+ phase: "unavailable",
1068
+ reason: "source-unavailable"
1069
+ });
1794
1070
  for (const unsubscribe of source.unsubscribes)
1795
1071
  unsubscribe();
1796
1072
  }
1797
- sources.clear();
1798
- held.clear();
1799
1073
  }
1800
1074
  };
1801
1075
  }
@@ -1819,7 +1093,6 @@ export {
1819
1093
  defineKeyspace,
1820
1094
  createWatchHub,
1821
1095
  createManagedSchedule,
1822
- createDiagnosticJournal,
1823
1096
  createDecisionPipeline,
1824
1097
  createCreditWindow,
1825
1098
  createBoundedChannel,