@intx/workflow-host 0.2.2 → 0.4.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 (101) hide show
  1. package/README.md +77 -14
  2. package/dist/adapters/mail-part-store.d.ts +46 -0
  3. package/dist/adapters/mail-part-store.js +251 -0
  4. package/dist/adapters/repo-store.d.ts +22 -1
  5. package/dist/adapters/repo-store.js +56 -65
  6. package/dist/adapters/spawn-child.d.ts +109 -44
  7. package/dist/adapters/spawn-child.js +77 -81
  8. package/dist/adapters/step-invoker.d.ts +52 -2
  9. package/dist/adapters/step-invoker.js +284 -37
  10. package/dist/adapters/substrate-mailbox-store.d.ts +80 -0
  11. package/dist/adapters/substrate-mailbox-store.js +404 -0
  12. package/dist/child/child-mailbox-reader.d.ts +10 -0
  13. package/dist/child/child-mailbox-reader.js +23 -0
  14. package/dist/child/credential-cell.d.ts +8 -0
  15. package/dist/child/credential-cell.js +66 -0
  16. package/dist/child/env-bootstrap.d.ts +20 -6
  17. package/dist/child/env-bootstrap.js +9 -1
  18. package/dist/child/from-process-env.d.ts +12 -0
  19. package/dist/child/from-process-env.js +6 -0
  20. package/dist/child/index.d.ts +6 -2
  21. package/dist/child/index.js +4 -1
  22. package/dist/child/mailbox-mutation-bridge.d.ts +61 -0
  23. package/dist/child/mailbox-mutation-bridge.js +101 -0
  24. package/dist/child/mailbox-watch-registry.d.ts +17 -0
  25. package/dist/child/mailbox-watch-registry.js +61 -0
  26. package/dist/child/outbound-mail-bridge.d.ts +3 -2
  27. package/dist/child/outbound-mail-bridge.js +20 -32
  28. package/dist/child/parked-correlations.d.ts +42 -0
  29. package/dist/child/parked-correlations.js +80 -0
  30. package/dist/child/pending-request.d.ts +89 -0
  31. package/dist/child/pending-request.js +80 -0
  32. package/dist/child/proxy-repo-store.d.ts +3 -2
  33. package/dist/child/proxy-repo-store.js +2 -0
  34. package/dist/child/run-child.d.ts +170 -14
  35. package/dist/child/run-child.js +569 -155
  36. package/dist/child/self-discovery.d.ts +10 -0
  37. package/dist/child/self-discovery.js +25 -1
  38. package/dist/child/substrate-write-bridge.d.ts +3 -2
  39. package/dist/child/substrate-write-bridge.js +21 -38
  40. package/dist/child/supervisor-backed-transport.d.ts +52 -6
  41. package/dist/child/supervisor-backed-transport.js +205 -62
  42. package/dist/child/verified-definition-loader.d.ts +33 -0
  43. package/dist/child/verified-definition-loader.js +43 -0
  44. package/dist/child/warm-agent-cache.d.ts +44 -4
  45. package/dist/child/warm-agent-cache.js +41 -10
  46. package/dist/index.d.ts +6 -4
  47. package/dist/index.js +6 -4
  48. package/dist/ipc/control-channel.d.ts +151 -2
  49. package/dist/ipc/control-channel.js +222 -29
  50. package/dist/ipc/event-channel.d.ts +32 -1
  51. package/dist/ipc/index.d.ts +1 -1
  52. package/dist/ipc/index.js +1 -1
  53. package/dist/mail-bus/hub-transport-adapter.d.ts +12 -7
  54. package/dist/mail-bus/hub-transport-adapter.js +9 -5
  55. package/dist/run-body-then-cleanup.d.ts +17 -0
  56. package/dist/run-body-then-cleanup.js +38 -0
  57. package/dist/seams/scheduler.d.ts +16 -6
  58. package/dist/seams/scheduler.js +87 -97
  59. package/dist/supervisor/cancel-signing.d.ts +2 -2
  60. package/dist/supervisor/cancel-signing.js +4 -8
  61. package/dist/supervisor/credentials.d.ts +28 -15
  62. package/dist/supervisor/credentials.js +7 -7
  63. package/dist/supervisor/dispatch-attribution.js +1 -1
  64. package/dist/supervisor/drain-timeout.d.ts +2 -2
  65. package/dist/supervisor/drain-timeout.js +1 -1
  66. package/dist/supervisor/index.d.ts +3 -3
  67. package/dist/supervisor/index.js +2 -2
  68. package/dist/supervisor/recycle.d.ts +10 -3
  69. package/dist/supervisor/recycle.js +18 -7
  70. package/dist/supervisor/run-event-compaction.d.ts +5 -5
  71. package/dist/supervisor/run-event-compaction.js +14 -19
  72. package/dist/supervisor/run-event-recovery.d.ts +34 -0
  73. package/dist/supervisor/run-event-recovery.js +45 -0
  74. package/dist/supervisor/spawn-env.d.ts +2 -2
  75. package/dist/supervisor/spawn-env.js +1 -1
  76. package/dist/supervisor/supervisor.d.ts +106 -26
  77. package/dist/supervisor/supervisor.js +1903 -414
  78. package/dist/supervisor/terminal-commit.d.ts +36 -0
  79. package/dist/supervisor/terminal-commit.js +130 -0
  80. package/dist/supervisor/types.d.ts +180 -23
  81. package/dist/testing/change-notifier.d.ts +12 -0
  82. package/dist/testing/change-notifier.js +63 -0
  83. package/dist/testing/index.d.ts +8 -0
  84. package/dist/testing/index.js +16 -0
  85. package/dist/testing/log-capture.d.ts +52 -0
  86. package/dist/testing/log-capture.js +124 -0
  87. package/dist/testing/mail-bus.d.ts +22 -0
  88. package/dist/testing/mail-bus.js +78 -0
  89. package/dist/testing/memory-streams.d.ts +43 -0
  90. package/dist/testing/memory-streams.js +211 -0
  91. package/dist/testing/spawn-observer.d.ts +12 -0
  92. package/dist/testing/spawn-observer.js +36 -0
  93. package/dist/testing/stub-repo-store.d.ts +10 -0
  94. package/dist/testing/stub-repo-store.js +39 -0
  95. package/dist/testing/supervisor-reaper.d.ts +24 -0
  96. package/dist/testing/supervisor-reaper.js +49 -0
  97. package/dist/testing/upstream-frames.d.ts +47 -0
  98. package/dist/testing/upstream-frames.js +94 -0
  99. package/dist/workflow-definition-loader.d.ts +187 -0
  100. package/dist/workflow-definition-loader.js +422 -0
  101. package/package.json +18 -11
@@ -4,8 +4,9 @@
4
4
  //
5
5
  // Recycle is the supervisor's "same deploy tree, fresh process" path.
6
6
  // It tears the existing workflow-process child down and stands a new
7
- // one up against the SAME deploy tree (same `workflow.json`, same
8
- // per-step credential repos). It is STRICTLY ORTHOGONAL TO REDEPLOY:
7
+ // one up against the SAME deploy tree (same materialized source
8
+ // closure, same per-step credential repos). It is STRICTLY ORTHOGONAL
9
+ // TO REDEPLOY:
9
10
  //
10
11
  // - Recycle = same deploy tree, fresh process.
11
12
  // - Redeploy = new deploy tree.
@@ -20,6 +21,15 @@
20
21
  //
21
22
  // Six-step sequence (locked):
22
23
  //
24
+ // The `drain` step and the `SubprocessHandle` handed in as `current` are
25
+ // caller-parameterized. For the operator/policy/self recycle origins the
26
+ // child is live: `drain` sends the real drain control mail and `kill`
27
+ // terminates a running process. For the `crash` origin the child has
28
+ // already exited unexpectedly, so the caller supplies a no-op `drain`
29
+ // (there is nothing to drain) and the `kill` in step 2 lands on an
30
+ // already-dead handle as a cheap no-op. Steps 3-6 are identical for every
31
+ // origin.
32
+ //
23
33
  // 1. `drain` -- send the existing drain control mail. Wait for
24
34
  // in-flight runs to drain per each step's `drainBehavior`.
25
35
  // `drainTimeout` escalation applies normally; the drain-timeout
@@ -153,8 +163,8 @@ export async function triggerRecycle(ctx, opts) {
153
163
  // Step 3: respawn. Fresh channelId, fresh HMAC key, fresh Ed25519
154
164
  // IPC keypair. Per-step credentials are re-read so a grants update
155
165
  // that landed since the original spawn is reflected in the new
156
- // child's snapshot. The deploy tree (`workflow.json`, agents,
157
- // workflow-asset repo) is UNCHANGED.
166
+ // child's snapshot. The deploy tree (the materialized source closure,
167
+ // the workflow-asset repo, the agent-state repos) is UNCHANGED.
158
168
  const channelId = generateChannelId();
159
169
  const hmacKey = generateHmacKey();
160
170
  const ipcKeypair = await (ctx.bindings.ipcKeyPairFactory ?? generateKeyPair)();
@@ -164,7 +174,7 @@ export async function triggerRecycle(ctx, opts) {
164
174
  channelId,
165
175
  hmacKey,
166
176
  hostPublicKey: ipcKeypair.publicKey,
167
- deploymentId: ctx.bindings.deploymentId,
177
+ anchorRunId: ctx.bindings.anchorRunId,
168
178
  deploymentMailAddress: ctx.bindings.deploymentMailAddress,
169
179
  stepCount: ctx.bindings.stepCount,
170
180
  definitionHash: ctx.definitionHash,
@@ -210,7 +220,8 @@ export async function triggerRecycle(ctx, opts) {
210
220
  // the previous child's lifetime is picked up here -- the recycle
211
221
  // doubles as the supervisor's grant-refresh path. The deploy tree
212
222
  // is not consulted; this read is against the `agent-state` repos
213
- // alone, whose contents are independent of `workflow.json`.
223
+ // alone, whose contents are independent of the materialized source
224
+ // closure.
214
225
  //
215
226
  // This is a substrate read that can reject -- a grants file that
216
227
  // became malformed is precisely the recycle's grant-refresh path. The
@@ -227,7 +238,7 @@ export async function triggerRecycle(ctx, opts) {
227
238
  repoStore: ctx.bindings.repoStore,
228
239
  principal: ctx.bindings.readPrincipal,
229
240
  stepOrder: ctx.stepOrder,
230
- deploymentId: ctx.bindings.deploymentId,
241
+ anchorRunId: ctx.bindings.anchorRunId,
231
242
  deriveStepAddress: ctx.bindings.deriveStepAddress,
232
243
  ...(ctx.bindings.deriveStepRepoId !== undefined
233
244
  ? { deriveStepRepoId: ctx.bindings.deriveStepRepoId }
@@ -6,8 +6,8 @@ export type CompactRunEventsOpts = {
6
6
  repoId: RepoId;
7
7
  /** Events ref the workflow-run repo writes to. */
8
8
  ref: string;
9
- /** Deployment id used to construct the supervisor principal. */
10
- deploymentId: string;
9
+ /** Anchor run id used to construct the supervisor principal. */
10
+ anchorRunId: string;
11
11
  /** Run to seal. */
12
12
  runId: string;
13
13
  };
@@ -19,9 +19,9 @@ export type CompactRunEventsOpts = {
19
19
  *
20
20
  * Idempotent and terminal-only: a run already sealed (no `events/` subtree)
21
21
  * or one whose latest event is not terminal is left untouched, so the call
22
- * is safe to repeat. The live caller fires it once per run, right after the
23
- * run terminates; a bounded recovery sweep that would re-fire it to seal a
24
- * run whose fold a crash interrupted is not yet implemented.
22
+ * is safe to repeat. The live caller invokes it once per run, right after the
23
+ * run terminates; `recoverInterruptedCompactions` re-runs it for a run whose
24
+ * fold a crash interrupted before it could seal.
25
25
  *
26
26
  * The combined file is the verbatim byte concatenation of the per-event
27
27
  * blobs in seq order (`encodeCombinedEventLog`), the exact shape the
@@ -5,18 +5,12 @@
5
5
  // and drops the per-event files. This shrinks the workflow-run repo's
6
6
  // file count -- and every per-commit cost that scales with it --
7
7
  // without losing any event. The fold writes under the substrate's
8
- // per-repo lock as the `supervisor` principal, whose `deploymentId`
8
+ // per-repo lock as the `supervisor` principal, whose `anchorRunId`
9
9
  // the workflow-run kind handler checks against `repoId.id`.
10
- import { WORKFLOW_RUN_EVENTS_FILE, encodeCombinedEventLog, } from "@intx/hub-sessions/substrate";
10
+ import { classifyTerminalEvent, parseEventSeq, WORKFLOW_RUN_EVENTS_FILE, encodeCombinedEventLog, } from "@intx/hub-sessions/substrate";
11
11
  import { SUPERVISOR_PRINCIPAL_KIND } from "./cancel-signing.js";
12
12
  const RUNS_PREFIX = "runs";
13
13
  const EVENTS_DIR = "events";
14
- const EVENT_FILENAME_RE = /^(0|[1-9][0-9]*)\.json$/;
15
- const TERMINAL_EVENT_TYPES = new Set([
16
- "RunCompleted",
17
- "RunFailed",
18
- "RunCancelled",
19
- ]);
20
14
  /**
21
15
  * Fold a terminated run's per-event `events/<seq>.json` blobs into one
22
16
  * combined `events.jsonl`, dropping the per-event files. This shrinks the
@@ -25,9 +19,9 @@ const TERMINAL_EVENT_TYPES = new Set([
25
19
  *
26
20
  * Idempotent and terminal-only: a run already sealed (no `events/` subtree)
27
21
  * or one whose latest event is not terminal is left untouched, so the call
28
- * is safe to repeat. The live caller fires it once per run, right after the
29
- * run terminates; a bounded recovery sweep that would re-fire it to seal a
30
- * run whose fold a crash interrupted is not yet implemented.
22
+ * is safe to repeat. The live caller invokes it once per run, right after the
23
+ * run terminates; `recoverInterruptedCompactions` re-runs it for a run whose
24
+ * fold a crash interrupted before it could seal.
31
25
  *
32
26
  * The combined file is the verbatim byte concatenation of the per-event
33
27
  * blobs in seq order (`encodeCombinedEventLog`), the exact shape the
@@ -55,10 +49,10 @@ export async function compactRunEvents(opts) {
55
49
  }
56
50
  const seqs = [];
57
51
  for (const name of filenames) {
58
- const match = EVENT_FILENAME_RE.exec(name);
59
- if (match === null || match[1] === undefined)
52
+ const seq = parseEventSeq(name);
53
+ if (seq === null)
60
54
  continue;
61
- seqs.push(Number.parseInt(match[1], 10));
55
+ seqs.push(seq);
62
56
  }
63
57
  if (seqs.length === 0)
64
58
  return { compacted: false };
@@ -74,14 +68,15 @@ export async function compactRunEvents(opts) {
74
68
  return { compacted: false };
75
69
  }
76
70
  const lastType = parsed.type;
77
- if (typeof lastType !== "string" || !TERMINAL_EVENT_TYPES.has(lastType)) {
71
+ if (typeof lastType !== "string" ||
72
+ !classifyTerminalEvent(lastType).terminal) {
78
73
  return { compacted: false };
79
74
  }
80
75
  const prefix = `${RUNS_PREFIX}/${opts.runId}/${EVENTS_DIR}/`;
81
76
  const combinedPath = `${RUNS_PREFIX}/${opts.runId}/${WORKFLOW_RUN_EVENTS_FILE}`;
82
77
  const principal = {
83
78
  kind: SUPERVISOR_PRINCIPAL_KIND,
84
- deploymentId: opts.deploymentId,
79
+ anchorRunId: opts.anchorRunId,
85
80
  };
86
81
  let sealed = false;
87
82
  await opts.substrate.writeTreePreservingPrefix(principal, opts.repoId, opts.ref, {
@@ -90,11 +85,11 @@ export async function compactRunEvents(opts) {
90
85
  const entries = [];
91
86
  for (const [filepath, bytes] of existing) {
92
87
  const name = filepath.slice(prefix.length);
93
- const match = EVENT_FILENAME_RE.exec(name);
94
- if (match === null || match[1] === undefined) {
88
+ const seq = parseEventSeq(name);
89
+ if (seq === null) {
95
90
  throw new Error(`supervisor run-event-compaction: unexpected non-event file ${filepath} under run ${opts.runId}; refusing to compact`);
96
91
  }
97
- entries.push({ seq: Number.parseInt(match[1], 10), bytes });
92
+ entries.push({ seq, bytes });
98
93
  }
99
94
  if (entries.length === 0)
100
95
  return {};
@@ -0,0 +1,34 @@
1
+ import type { RepoId, RepoStore as SubstrateRepoStore } from "@intx/hub-sessions/substrate";
2
+ export type RecoverInterruptedCompactionsOpts = {
3
+ /** Substrate handle the supervisor writes through. */
4
+ substrate: SubstrateRepoStore;
5
+ /** Workflow-run repo for this deployment. */
6
+ repoId: RepoId;
7
+ /** Events ref the workflow-run repo writes to. */
8
+ ref: string;
9
+ /** Anchor run id used to construct the supervisor principal. */
10
+ anchorRunId: string;
11
+ /** Runs the boot scan proposes as terminal-but-per-event. */
12
+ pendingSealRunIds: readonly string[];
13
+ };
14
+ /** A run whose recovery fold threw, paired with the failure cause. */
15
+ export type RecoveryFoldFailure = {
16
+ runId: string;
17
+ message: string;
18
+ };
19
+ /**
20
+ * Re-seal runs a crash left terminal but still in per-event form, by re-running
21
+ * the idempotent `compactRunEvents` for each proposed run. `compactRunEvents`
22
+ * is authoritative: it no-ops a run that is already sealed or whose latest
23
+ * event is not terminal, so a stale or mistaken proposal is a harmless no-op.
24
+ *
25
+ * Folds run serially. Every fold contends the same per-repo write lock that
26
+ * live dispatch also takes, so folding one run at a time drains the backlog
27
+ * without a thundering herd on that lock. One run's failure is caught so it
28
+ * cannot abort the rest; the failed run id and its cause are returned -- not
29
+ * logged here -- so the caller owns how to surface the aggregate.
30
+ */
31
+ export declare function recoverInterruptedCompactions(opts: RecoverInterruptedCompactionsOpts): Promise<{
32
+ sealed: number;
33
+ failed: RecoveryFoldFailure[];
34
+ }>;
@@ -0,0 +1,45 @@
1
+ // Bounded recovery for run-event compaction folds a crash interrupted.
2
+ //
3
+ // When a run terminates, the supervisor fires `compactRunEvents` in the
4
+ // background. A crash between the terminal commit and that fold leaves the
5
+ // run terminal but still in per-event form, and the terminal signal never
6
+ // fires again for it. At the next spawn the boot scan proposes those runs;
7
+ // this sweep re-runs the idempotent fold for each so the leaked per-event
8
+ // file count is reclaimed.
9
+ import { compactRunEvents } from "./run-event-compaction.js";
10
+ /**
11
+ * Re-seal runs a crash left terminal but still in per-event form, by re-running
12
+ * the idempotent `compactRunEvents` for each proposed run. `compactRunEvents`
13
+ * is authoritative: it no-ops a run that is already sealed or whose latest
14
+ * event is not terminal, so a stale or mistaken proposal is a harmless no-op.
15
+ *
16
+ * Folds run serially. Every fold contends the same per-repo write lock that
17
+ * live dispatch also takes, so folding one run at a time drains the backlog
18
+ * without a thundering herd on that lock. One run's failure is caught so it
19
+ * cannot abort the rest; the failed run id and its cause are returned -- not
20
+ * logged here -- so the caller owns how to surface the aggregate.
21
+ */
22
+ export async function recoverInterruptedCompactions(opts) {
23
+ let sealed = 0;
24
+ const failed = [];
25
+ for (const runId of opts.pendingSealRunIds) {
26
+ try {
27
+ const { compacted } = await compactRunEvents({
28
+ substrate: opts.substrate,
29
+ repoId: opts.repoId,
30
+ ref: opts.ref,
31
+ anchorRunId: opts.anchorRunId,
32
+ runId,
33
+ });
34
+ if (compacted)
35
+ sealed += 1;
36
+ }
37
+ catch (cause) {
38
+ failed.push({
39
+ runId,
40
+ message: cause instanceof Error ? cause.message : String(cause),
41
+ });
42
+ }
43
+ }
44
+ return { sealed, failed };
45
+ }
@@ -19,8 +19,8 @@ export interface ChildSpawnEnvParts {
19
19
  hmacKey: Uint8Array;
20
20
  /** Supervisor's Ed25519 public key for this spawn's control channel. */
21
21
  hostPublicKey: Uint8Array;
22
- /** Deployment identity the supervisor manages. */
23
- deploymentId: string;
22
+ /** Anchor run id the supervisor manages. */
23
+ anchorRunId: string;
24
24
  /** Mail address the deployment registered on the bus. */
25
25
  deploymentMailAddress: string;
26
26
  /** Step count of the deployed workflow (`stepOrder.length`). */
@@ -20,7 +20,7 @@ export function buildChildSpawnEnv(parts) {
20
20
  IPC_CHANNEL_ID: parts.channelId,
21
21
  IPC_HMAC_KEY: hexEncode(parts.hmacKey),
22
22
  HOST_PUBKEY: hexEncode(parts.hostPublicKey),
23
- DEPLOYMENT_ID: parts.deploymentId,
23
+ DEPLOYMENT_ID: parts.anchorRunId,
24
24
  DEFINITION_HASH: parts.definitionHash,
25
25
  MAILBOX_ADDRESS: parts.deploymentMailAddress,
26
26
  STEP_COUNT: String(parts.stepCount),
@@ -1,3 +1,4 @@
1
+ import { type CredentialDelivery } from "@intx/types/sidecar";
1
2
  import type { InferenceSource } from "@intx/types/runtime";
2
3
  import type { CancelOrigin } from "@intx/workflow";
3
4
  import { type EventPayload } from "../ipc/index.js";
@@ -5,20 +6,57 @@ import { type CredentialsSnapshot } from "./credentials.js";
5
6
  import { type RecycleAttempt, type RecycleOrigin } from "./recycle.js";
6
7
  import type { WorkflowSupervisorBindings } from "./types.js";
7
8
  /**
8
- * Default watchdog timeout for the supervisor's
9
- * `synchronouslyDispatchTerminalWrite`. The handler holds the
10
- * `substrate.write.response` back to the child until the dispatch
11
- * loop's `markConsumed` settles for the matching terminal event; an
12
- * unbounded wait would chain into a child / runtime / dispatch loop
13
- * deadlock if `markConsumed` never armed (bug in the dispatch loop, a
14
- * torn-down cohort, a stalled inbox primitive). 30s sits between the
15
- * recycle path's `DEFAULT_KILL_TIMEOUT_MS` (5s, a hard process-level
16
- * kill cap) and `DEFAULT_DRAIN_TIMEOUT_MS` (60s, the per-deployment
17
- * drain budget) -- generous enough to absorb a slow legitimate
18
- * markConsumed, tight enough to surface a real deadlock long before
19
- * the drainTimeout would otherwise mask it.
9
+ * Default crash-loop bound: the supervisor stops respawning and latches
10
+ * the deployment once the workflow-process child exits unexpectedly this
11
+ * many times within `DEFAULT_CRASH_LOOP_WINDOW_MS`. Overridable via
12
+ * `WorkflowSupervisorBindings.crashLoopMaxCount`.
20
13
  */
21
- export declare const DEFAULT_TERMINAL_WRITE_WATCHDOG_MS = 30000;
14
+ export declare const DEFAULT_CRASH_LOOP_MAX_COUNT = 3;
15
+ /**
16
+ * Default sliding window (ms) over which `DEFAULT_CRASH_LOOP_MAX_COUNT`
17
+ * unexpected exits latch the deployment. Overridable via
18
+ * `WorkflowSupervisorBindings.crashLoopWindowMs`.
19
+ */
20
+ export declare const DEFAULT_CRASH_LOOP_WINDOW_MS = 60000;
21
+ /**
22
+ * Default stable-run duration (ms): once a respawned child stays up this
23
+ * long, the crash counter resets so flapping followed by stability does
24
+ * not permanently latch. Overridable via
25
+ * `WorkflowSupervisorBindings.crashLoopStableResetMs`.
26
+ */
27
+ export declare const DEFAULT_CRASH_LOOP_STABLE_RESET_MS = 60000;
28
+ /**
29
+ * Default initial respawn backoff (ms): the wait before the first respawn
30
+ * after an unexpected exit. Overridable via
31
+ * `WorkflowSupervisorBindings.respawnBackoffInitialMs`.
32
+ */
33
+ export declare const DEFAULT_RESPAWN_BACKOFF_INITIAL_MS = 1000;
34
+ /**
35
+ * Default cap (ms) on the exponential respawn backoff. Kept below
36
+ * `DEFAULT_CRASH_LOOP_WINDOW_MS` so a slow flapper's crashes still fall
37
+ * within the window and latch the guard. Overridable via
38
+ * `WorkflowSupervisorBindings.respawnBackoffMaxMs`.
39
+ */
40
+ export declare const DEFAULT_RESPAWN_BACKOFF_MAX_MS = 30000;
41
+ /**
42
+ * Default watchdog for `reEmitParkedCorrelations`' wait on the child's
43
+ * `parked-correlations.response`. 30s is generous enough for a healthy child
44
+ * to enumerate its in-flight runs and load each parked snapshot, tight
45
+ * enough that a wedged-but-alive child does not hang the reconnect caller
46
+ * until some coarser timeout intervenes.
47
+ */
48
+ export declare const DEFAULT_PARKED_QUERY_WATCHDOG_MS = 30000;
49
+ /**
50
+ * Backstop for `waitForRunTerminalOrPark`. A dispatch waits here for the child
51
+ * to park or terminate the run before releasing `markConsumed`; a lost park
52
+ * wake or a wedged child would otherwise hang the deployment's dispatch loop
53
+ * forever. Five minutes is far beyond any healthy per-message dispatch (which
54
+ * settles in well under a second), so this never fires on a legitimately long
55
+ * run without also being a genuine fault -- and when it does fire it is logged
56
+ * loudly and fails the dispatch (the mail is left reclaimable, never consumed
57
+ * on the assumption the run made progress), not silently swallowed.
58
+ */
59
+ export declare const TERMINAL_OR_PARK_BACKSTOP_MS = 300000;
22
60
  /**
23
61
  * Public surface returned by `createWorkflowSupervisor`. Each method
24
62
  * advances the supervisor through one lifecycle transition; the
@@ -89,6 +127,42 @@ export interface WorkflowSupervisor {
89
127
  * closing pipe. Throws otherwise.
90
128
  */
91
129
  deliverSources(opts: DeliverSourcesOpts): Promise<void>;
130
+ /**
131
+ * Push refreshed credential material to the child's in-memory cell. Mirrors
132
+ * `deliverSources`: the supervisor is the single producer of
133
+ * `credentials-updated` control frames, phase-guarded to starting/running so
134
+ * a frame is never written into a recycling child's closing pipe. A revoked
135
+ * credential is delivered by omitting its material so the child evicts it.
136
+ */
137
+ deliverCredentials(opts: DeliverCredentialsOpts): Promise<void>;
138
+ /**
139
+ * Refresh a live run's grant floor mid-run by re-reading its durable
140
+ * `runs/<runId>/grants.json` and pushing it as a `grants-updated` frame. The
141
+ * enforcement path for a standing (`scope: "always"`) approval that lowers a
142
+ * tool's `ask` to `allow` in that file. Unlike `deliverSignal`/
143
+ * `deliverSources`, a refresh for a non-live child is normal, so this
144
+ * NO-OPS (`skipped`) instead of throwing, and a send failure to a live child
145
+ * is logged loudly but stays non-fatal -- the durable file governs the next
146
+ * barrier/respawn. It pushes only that file's contents, never caller-supplied
147
+ * grants, so it can only tighten or refresh a floor. Returns whether a live
148
+ * push happened.
149
+ */
150
+ deliverGrants(runId: string): Promise<"pushed" | "skipped">;
151
+ /**
152
+ * Re-register every correlation the child is currently parked on by
153
+ * querying it for its parked correlations and re-emitting each through
154
+ * `onSuspensionRegister`. Recovers a `park.notify` register the hub may have
155
+ * missed while it was down at suspend time.
156
+ *
157
+ * Best-effort and safe to call at any time, including concurrently. Unlike
158
+ * `deliverSignal`/`deliverSources`, which throw when the child is not
159
+ * addressable, this NO-OPS on a non-addressable phase (idle / stopping /
160
+ * stopped / recycling): a re-establishment landing mid-recycle must not
161
+ * crash, and the next spawn re-drives it. A query that fails or times out is
162
+ * logged and dropped; the hub co-write is idempotent, so the next
163
+ * re-establishment re-drives it. The caller need not guard the call site.
164
+ */
165
+ reEmitParkedCorrelations(): Promise<void>;
92
166
  /**
93
167
  * Current snapshot of the credentials pushed to the child. Surfaced
94
168
  * so the host can audit the per-step contentHash without
@@ -97,7 +171,11 @@ export interface WorkflowSupervisor {
97
171
  getCredentialsSnapshot(): CredentialsSnapshot | null;
98
172
  }
99
173
  export type SpawnOpts = {
100
- /** Step ids in this deployment's `stepOrder` for credentials assembly. */
174
+ /**
175
+ * Every step id in this deployment's flat step-id namespace -- the
176
+ * definition's own `stepOrder` plus the step ids of every `loop` body it
177
+ * carries -- for credentials assembly.
178
+ */
101
179
  stepOrder: readonly string[];
102
180
  /** Content hash of the deployment's workflow definition. */
103
181
  definitionHash: string;
@@ -172,6 +250,20 @@ export type DeliverSourcesOpts = {
172
250
  /** The default source id; the wire boundary requires it to equal `sources[0].id`. */
173
251
  defaultSource: string;
174
252
  };
253
+ export type DeliverCredentialsOpts = {
254
+ /**
255
+ * The refreshed credential material and per-handle descriptors. The child
256
+ * MERGES this into its cell (materials upsert by credentialId, bindings by
257
+ * consumer-and-handle); it does not evict by omission.
258
+ */
259
+ delivery: CredentialDelivery;
260
+ /**
261
+ * CredentialIds to drop from the child's cell (a deletion or a deliberate
262
+ * revocation). The child removes each id's material and any binding that
263
+ * references it. A pure revocation pairs an empty `delivery` with these ids.
264
+ */
265
+ revoke?: string[];
266
+ };
175
267
  export type RecycleOpts = {
176
268
  reason: string;
177
269
  /**
@@ -182,18 +274,6 @@ export type RecycleOpts = {
182
274
  */
183
275
  origin?: RecycleOrigin;
184
276
  };
185
- /**
186
- * Raised when a `pendingMerges` entry or a
187
- * `markConsumedCompletionWaiters` waiter is rejected because the
188
- * cohort it was registered against has been aborted (cohort transition
189
- * during a recycle, or a supervisor shutdown). Callers awaiting the
190
- * resolved value receive an instance of this error so the failure mode
191
- * is recognisable from a generic substrate-merge or markConsumed
192
- * failure.
193
- */
194
- export declare class MergeAbortedError extends Error {
195
- constructor(reason: string);
196
- }
197
277
  /**
198
278
  * Construct a per-deployment supervisor. All host-specific
199
279
  * dependencies are pulled in via `bindings`; nothing in the