@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.
- package/README.md +77 -14
- package/dist/adapters/mail-part-store.d.ts +46 -0
- package/dist/adapters/mail-part-store.js +251 -0
- package/dist/adapters/repo-store.d.ts +22 -1
- package/dist/adapters/repo-store.js +56 -65
- package/dist/adapters/spawn-child.d.ts +109 -44
- package/dist/adapters/spawn-child.js +77 -81
- package/dist/adapters/step-invoker.d.ts +52 -2
- package/dist/adapters/step-invoker.js +284 -37
- package/dist/adapters/substrate-mailbox-store.d.ts +80 -0
- package/dist/adapters/substrate-mailbox-store.js +404 -0
- package/dist/child/child-mailbox-reader.d.ts +10 -0
- package/dist/child/child-mailbox-reader.js +23 -0
- package/dist/child/credential-cell.d.ts +8 -0
- package/dist/child/credential-cell.js +66 -0
- package/dist/child/env-bootstrap.d.ts +20 -6
- package/dist/child/env-bootstrap.js +9 -1
- package/dist/child/from-process-env.d.ts +12 -0
- package/dist/child/from-process-env.js +6 -0
- package/dist/child/index.d.ts +6 -2
- package/dist/child/index.js +4 -1
- package/dist/child/mailbox-mutation-bridge.d.ts +61 -0
- package/dist/child/mailbox-mutation-bridge.js +101 -0
- package/dist/child/mailbox-watch-registry.d.ts +17 -0
- package/dist/child/mailbox-watch-registry.js +61 -0
- package/dist/child/outbound-mail-bridge.d.ts +3 -2
- package/dist/child/outbound-mail-bridge.js +20 -32
- package/dist/child/parked-correlations.d.ts +42 -0
- package/dist/child/parked-correlations.js +80 -0
- package/dist/child/pending-request.d.ts +89 -0
- package/dist/child/pending-request.js +80 -0
- package/dist/child/proxy-repo-store.d.ts +3 -2
- package/dist/child/proxy-repo-store.js +2 -0
- package/dist/child/run-child.d.ts +170 -14
- package/dist/child/run-child.js +569 -155
- package/dist/child/self-discovery.d.ts +10 -0
- package/dist/child/self-discovery.js +25 -1
- package/dist/child/substrate-write-bridge.d.ts +3 -2
- package/dist/child/substrate-write-bridge.js +21 -38
- package/dist/child/supervisor-backed-transport.d.ts +52 -6
- package/dist/child/supervisor-backed-transport.js +205 -62
- package/dist/child/verified-definition-loader.d.ts +33 -0
- package/dist/child/verified-definition-loader.js +43 -0
- package/dist/child/warm-agent-cache.d.ts +44 -4
- package/dist/child/warm-agent-cache.js +41 -10
- package/dist/index.d.ts +6 -4
- package/dist/index.js +6 -4
- package/dist/ipc/control-channel.d.ts +151 -2
- package/dist/ipc/control-channel.js +222 -29
- package/dist/ipc/event-channel.d.ts +32 -1
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/mail-bus/hub-transport-adapter.d.ts +12 -7
- package/dist/mail-bus/hub-transport-adapter.js +9 -5
- package/dist/run-body-then-cleanup.d.ts +17 -0
- package/dist/run-body-then-cleanup.js +38 -0
- package/dist/seams/scheduler.d.ts +16 -6
- package/dist/seams/scheduler.js +87 -97
- package/dist/supervisor/cancel-signing.d.ts +2 -2
- package/dist/supervisor/cancel-signing.js +4 -8
- package/dist/supervisor/credentials.d.ts +28 -15
- package/dist/supervisor/credentials.js +7 -7
- package/dist/supervisor/dispatch-attribution.js +1 -1
- package/dist/supervisor/drain-timeout.d.ts +2 -2
- package/dist/supervisor/drain-timeout.js +1 -1
- package/dist/supervisor/index.d.ts +3 -3
- package/dist/supervisor/index.js +2 -2
- package/dist/supervisor/recycle.d.ts +10 -3
- package/dist/supervisor/recycle.js +18 -7
- package/dist/supervisor/run-event-compaction.d.ts +5 -5
- package/dist/supervisor/run-event-compaction.js +14 -19
- package/dist/supervisor/run-event-recovery.d.ts +34 -0
- package/dist/supervisor/run-event-recovery.js +45 -0
- package/dist/supervisor/spawn-env.d.ts +2 -2
- package/dist/supervisor/spawn-env.js +1 -1
- package/dist/supervisor/supervisor.d.ts +106 -26
- package/dist/supervisor/supervisor.js +1903 -414
- package/dist/supervisor/terminal-commit.d.ts +36 -0
- package/dist/supervisor/terminal-commit.js +130 -0
- package/dist/supervisor/types.d.ts +180 -23
- package/dist/testing/change-notifier.d.ts +12 -0
- package/dist/testing/change-notifier.js +63 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/log-capture.d.ts +52 -0
- package/dist/testing/log-capture.js +124 -0
- package/dist/testing/mail-bus.d.ts +22 -0
- package/dist/testing/mail-bus.js +78 -0
- package/dist/testing/memory-streams.d.ts +43 -0
- package/dist/testing/memory-streams.js +211 -0
- package/dist/testing/spawn-observer.d.ts +12 -0
- package/dist/testing/spawn-observer.js +36 -0
- package/dist/testing/stub-repo-store.d.ts +10 -0
- package/dist/testing/stub-repo-store.js +39 -0
- package/dist/testing/supervisor-reaper.d.ts +24 -0
- package/dist/testing/supervisor-reaper.js +49 -0
- package/dist/testing/upstream-frames.d.ts +47 -0
- package/dist/testing/upstream-frames.js +94 -0
- package/dist/workflow-definition-loader.d.ts +187 -0
- package/dist/workflow-definition-loader.js +422 -0
- 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
|
|
8
|
-
// per-step credential repos). It is STRICTLY ORTHOGONAL
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
10
|
-
|
|
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
|
|
23
|
-
* run terminates;
|
|
24
|
-
*
|
|
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 `
|
|
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
|
|
29
|
-
* run terminates;
|
|
30
|
-
*
|
|
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
|
|
59
|
-
if (
|
|
52
|
+
const seq = parseEventSeq(name);
|
|
53
|
+
if (seq === null)
|
|
60
54
|
continue;
|
|
61
|
-
seqs.push(
|
|
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" ||
|
|
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
|
-
|
|
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
|
|
94
|
-
if (
|
|
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
|
|
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
|
-
/**
|
|
23
|
-
|
|
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.
|
|
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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|