@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
@@ -5,8 +5,9 @@ export interface CreateProxyWorkflowRunRepoStoreOpts {
5
5
  * Bare substrate handle the child opens against the shared on-disk
6
6
  * data dir. Used for the read-only methods that consult the
7
7
  * substrate's local state -- `getRepoDir` (path computation, no
8
- * I/O), `resolveRef`, `listRefs`, `resolveHead`, `createPack`. The
9
- * bare store is never used as a writer here; its
8
+ * I/O), `resolveRef`, `listRefs`, `resolveHead`, `openCommittedReads`,
9
+ * `openCommittedReadsAtCommit`, `createPack`. The bare store is never
10
+ * used as a writer here; its
10
11
  * `writeTreePreservingPrefix` / `writeTree` / `receivePack` are not
11
12
  * reachable through this proxy.
12
13
  */
@@ -117,6 +117,8 @@ export function createProxyWorkflowRunRepoStore(opts) {
117
117
  listRefs: bareStore.listRefs.bind(bareStore),
118
118
  resolveHead: bareStore.resolveHead.bind(bareStore),
119
119
  getRepoDir: bareStore.getRepoDir.bind(bareStore),
120
+ openCommittedReads: bareStore.openCommittedReads.bind(bareStore),
121
+ openCommittedReadsAtCommit: bareStore.openCommittedReadsAtCommit.bind(bareStore),
120
122
  subscribe(_principal, repoId, ref, subOpts) {
121
123
  // Synthesizing the subscribe surface in the proxy: the bare
122
124
  // store's `subscribe` would only fire from its own writes, but
@@ -1,14 +1,18 @@
1
1
  import type { Principal, RepoId, RepoStore as SubstrateRepoStore } from "@intx/hub-sessions/substrate";
2
- import type { DirectorRegistry } from "@intx/agent";
3
2
  import type { AuthzCallResult } from "@intx/inference";
4
- import type { RunResult, Scheduler, StepInvokeRequest, StepInvokeResult, SpawnChildWorkflow, WorkflowAuthorizeFn } from "@intx/workflow";
3
+ import type { RunResult, Scheduler, ReadParkedApprovalOps, StepInvokeRequest, StepInvokeResult, SpawnChildWorkflow, WorkflowAuthorizeFn, WorkflowDefinition, WorkflowPark } from "@intx/workflow";
5
4
  import { type WorkflowHostDrainController } from "../drain-controller.js";
6
- import type { InferenceSource } from "@intx/types/runtime";
5
+ import type { InferenceSource, MailPartReader } from "@intx/types/runtime";
6
+ import type { CredentialDelivery } from "@intx/types/sidecar";
7
+ import type { RunSuspendableChild, RunChildWorkflow } from "../adapters/spawn-child.js";
7
8
  import { type ControlChannelSender, type ControlPayload, type EventPayload, type FrameWriter, type NdjsonReader, type NdjsonWriter } from "../ipc/index.js";
8
9
  import type { CredentialsSnapshot } from "../supervisor/credentials.js";
9
10
  import { hashGrants } from "../supervisor/credentials.js";
10
11
  import type { SpawnTimeEnv } from "./env-bootstrap.js";
12
+ import { type LoadParkedApproval } from "./parked-correlations.js";
11
13
  import type { ChildOutboundMailBridge } from "./outbound-mail-bridge.js";
14
+ import type { ChildMailboxMutationBridge } from "./mailbox-mutation-bridge.js";
15
+ import type { MailboxWatchRegistry } from "./mailbox-watch-registry.js";
12
16
  import { type WarmAgentCache } from "./warm-agent-cache.js";
13
17
  /**
14
18
  * `WorkflowAuthorize` closure factory shape. The child's authorize
@@ -27,6 +31,18 @@ import { type WarmAgentCache } from "./warm-agent-cache.js";
27
31
  export type CredentialsSnapshotRef = {
28
32
  current: CredentialsSnapshot | null;
29
33
  };
34
+ /**
35
+ * The deployment's decrypted credential material and per-handle descriptors,
36
+ * held through a mutable reference. A `credentials-updated` frame MERGES into
37
+ * this cell (materials by credentialId, bindings by (consumer, handle); an
38
+ * explicit `revoke` list drops entries) rather than replacing it wholesale,
39
+ * because the cell has several independently-scoped producers. The secret lives
40
+ * ONLY here -- read at tool-invoke time through the gated capability -- and is
41
+ * never copied into a snapshot, event, or state.
42
+ */
43
+ export type CredentialMaterialRef = {
44
+ current: CredentialDelivery | null;
45
+ };
30
46
  /**
31
47
  * Per-step inference-source table the build path reads through a mutable
32
48
  * reference, keyed by stepId. Each value is the step's ordered failover
@@ -92,7 +108,24 @@ export type DrainController = WorkflowHostDrainController;
92
108
  * by the run-loop (`runWorkflowChild`), not the binding: the binding
93
109
  * only reads it through to the adapter.
94
110
  */
95
- export type ChildStepInvoker = (req: StepInvokeRequest, onEvent: (event: EventPayload) => void, authorize: WorkflowAuthorizeFn, warmCache: WarmAgentCache | undefined, sourcesRef: SourcesSnapshotRef) => Promise<StepInvokeResult>;
111
+ /**
112
+ * Per-run credential inputs the top-level step invoker carries to the
113
+ * substrate: the live material cell the control channel writes each delivery
114
+ * into, and a resolver for a step's grants (which the substrate gates
115
+ * credential use against). The substrate combines these with its own static
116
+ * provider registry to assemble each tool bundle's `credentials` capability.
117
+ *
118
+ * Grants are typed `readonly unknown[]` here: this package owns no grant
119
+ * grammar (the credentials snapshot's grants are `unknown[]` throughout), so
120
+ * the substrate casts to its `GrantRule` shape at its own boundary, exactly
121
+ * as the grant evaluator does. The cell is read live per use, so a rotation or
122
+ * a revoking re-push reaches an already-shaped handle without a rebuild.
123
+ */
124
+ export interface CredentialWiring {
125
+ readonly materialRef: CredentialMaterialRef;
126
+ readonly resolveStepGrants: (stepId: string) => readonly unknown[];
127
+ }
128
+ export type ChildStepInvoker = (req: StepInvokeRequest, onEvent: (event: EventPayload) => void, authorize: WorkflowAuthorizeFn, warmCache: WarmAgentCache | undefined, sourcesRef: SourcesSnapshotRef, credentialWiring: CredentialWiring, mailPartReader: MailPartReader) => Promise<StepInvokeResult>;
96
129
  /**
97
130
  * Bindings the binary owns: per-deployment substrate identity,
98
131
  * principal credentials, the runtime-supplied callbacks the
@@ -114,10 +147,6 @@ export interface RunWorkflowChildBindings {
114
147
  * the host's substrate accepts for `runs/<runId>/` writes.
115
148
  */
116
149
  principal: Principal;
117
- /** Workflow-asset repo identity (used to load `workflow.json`). */
118
- workflowDefinitionRepoId: RepoId;
119
- /** Workflow-asset ref the deploy orchestrator wrote to. */
120
- workflowDefinitionRef: string;
121
150
  /**
122
151
  * Step-invoker callback the runtime body invokes per step. The
123
152
  * shape is the workflow-runtime `StepInvoker` widened with an
@@ -128,11 +157,51 @@ export interface RunWorkflowChildBindings {
128
157
  */
129
158
  invokeStep: ChildStepInvoker;
130
159
  /**
131
- * Child-spawn callback the runtime body invokes for `childWorkflow`
132
- * primitives. The production binary wires this against
133
- * `createWorkflowSpawnChild`; tests inject a stub.
160
+ * Terminal child-spawn callback the runtime body invokes for a
161
+ * `childWorkflow` primitive when the deployment embeds NO inline child
162
+ * import (the map `run-child` lifts is empty). Optional and, in practice,
163
+ * only a test seam: a production deployment that carries a childWorkflow
164
+ * always has a non-empty lifted-body map and routes through the in-memory
165
+ * resolver built from `runChild` below, and one that carries none never
166
+ * invokes this. A workflow that reaches a childWorkflow with neither this
167
+ * nor `runChild` wired fails loud at spawn.
134
168
  */
135
- spawnChild: SpawnChildWorkflow;
169
+ spawnChild?: SpawnChildWorkflow;
170
+ /**
171
+ * Raw in-process terminal child executor. `run-child` builds the in-memory
172
+ * childWorkflow resolver from this executor plus the lifted-body map it
173
+ * extracts after loading the definition -- the parent's own re-verified
174
+ * closure -- so an owned inline child resolves with NO on-disk read. Parallel
175
+ * to `runSuspendableChild` for onTrigger bodies. Optional for the same
176
+ * reason: a child that embeds no childWorkflow import omits it.
177
+ */
178
+ runChild?: RunChildWorkflow;
179
+ /**
180
+ * Raw in-process suspendable-child executor. `run-child` builds the in-memory
181
+ * onTrigger-body resolver from this executor plus the bodies map it extracts
182
+ * AFTER re-evaluating the closure -- the substrate factory cannot build that
183
+ * resolver because the bodies map does not exist pre-eval. Optional: a child
184
+ * that runs no onTrigger section omits it.
185
+ */
186
+ runSuspendableChild?: RunSuspendableChild;
187
+ /**
188
+ * Materialize a loop iteration run's own `runs/<childRunId>/grants.json`,
189
+ * inheriting the parent (container) run's grants capped to the loop body's
190
+ * declared resources. A loop iteration runs through the workflow-host-local
191
+ * loop executor (the inherited env), NOT the sidecar's `buildChildRunEnv`, so
192
+ * it is the one body birth path that does not otherwise write its own grants
193
+ * file -- and without it the body's `childWorkflow` grandchild spawn is
194
+ * refused as under-authorized. Wired by the sidecar (the grant-cap helpers
195
+ * live there); optional so a test or an in-process host that never spawns a
196
+ * grandchild from a loop body can omit it. `definition` MUST be the PRE-rewrite
197
+ * loop body (grandchild still inline) so the cap keeps the grandchild's
198
+ * declared resources.
199
+ */
200
+ materializeLoopIterationGrants?: (args: {
201
+ parentRunId: string;
202
+ childRunId: string;
203
+ definition: WorkflowDefinition;
204
+ }) => Promise<void>;
136
205
  /** Host-process scheduler singleton. The child consumes the same instance. */
137
206
  scheduler: Scheduler;
138
207
  /** Grant evaluator wired against the host's grant-rule grammar. */
@@ -152,8 +221,45 @@ export interface RunWorkflowChildBindings {
152
221
  * adapter (which roots no per-run scratch of its own) can omit it.
153
222
  */
154
223
  cleanupRunStorage?: (runId: string) => Promise<void>;
155
- /** Optional director registry; defaults to the canonical built-ins. */
156
- directors?: DirectorRegistry;
224
+ /**
225
+ * Recover the durable approval snapshot for a parked control-plane
226
+ * correlation, so the child can answer a supervisor
227
+ * `parked-correlations.request` (the supervisor's re-registration path
228
+ * after a re-establishment). The child owns enumeration -- it walks its
229
+ * own reduced run state for `awaiting-signal` steps on control-plane
230
+ * channels -- but the snapshot lives in per-step durable storage whose
231
+ * on-disk layout (cold vs warm) the host owns, so the read is a host
232
+ * binding next to `cleanupRunStorage`. Optional so tests inject a stub and
233
+ * the recursive child-workflow adapter (which roots no per-step approval
234
+ * storage) can omit it; a production child that enumerates a parked
235
+ * control-plane step with no binding wired throws rather than silently
236
+ * dropping the correlation the hub is waiting to register.
237
+ */
238
+ loadParkedApproval?: LoadParkedApproval;
239
+ /**
240
+ * Enumerate the durable pending approval operations a crashed-mid-invocation
241
+ * step left behind, so the resume classifier can recover a step that crashed
242
+ * across the park boundary (durable `StepStarted`, unflushed `SignalAwaited`)
243
+ * as `awaiting-signal` rather than failing the run. Reads the same per-step
244
+ * durable storage as `loadParkedApproval` (cold isogit / warm substrate), so
245
+ * it is a host binding for the same reason. Optional so tests inject a stub
246
+ * and the recursive child-workflow adapter can omit it; absent, a crashed
247
+ * invocation step settles as a terminal failure, the pre-recovery behavior.
248
+ */
249
+ readParkedApprovalOps?: ReadParkedApprovalOps;
250
+ /**
251
+ * Mailbox watch registry backing the warm agent's `mail_wait` (INBOUND half
252
+ * of mailbox ownership, §3b). The host's substrate factory builds ONE
253
+ * instance at child boot, shares it with the step agent's supervisor-backed
254
+ * transport (whose `watch` registers callbacks into it), and exposes it here
255
+ * so the control loop routes each `mailbox.notify` frame to the same
256
+ * registry's `fire`. Optional: a deploy that wires no mail surface (and the
257
+ * recursive child-workflow adapter) omits it, and an inbound `mailbox.notify`
258
+ * frame is then logged and dropped. A test may instead inject a registry
259
+ * directly through `RunWorkflowChildOpts.mailboxWatchRegistry`, which takes
260
+ * precedence.
261
+ */
262
+ mailboxWatchRegistry?: MailboxWatchRegistry;
157
263
  /** Optional clock override; production wires `() => new Date()`. */
158
264
  clock?: () => Date;
159
265
  /** Optional id generator override; production wires a monotonic one. */
@@ -173,6 +279,14 @@ export interface RunWorkflowChildBindings {
173
279
  * rather than resolving a default.
174
280
  */
175
281
  initialSources?: Record<string, InferenceSource[]>;
282
+ /**
283
+ * Bootstrap credential material for the deployment's tools, decrypted
284
+ * hub-side and delivered on the deploy frame so it is resident before any
285
+ * step runs. Seeds the mutable `credentialMaterialRef` the gated capability
286
+ * reads. Absent when the deployment binds no credentials; a later
287
+ * `credentials-updated` control frame refreshes it on rotation.
288
+ */
289
+ initialCredentialMaterial?: CredentialDelivery;
176
290
  /**
177
291
  * Optional override for the child's Ed25519 keypair factory. The
178
292
  * child mints a fresh keypair at startup, holds the private half
@@ -249,6 +363,33 @@ export interface RunWorkflowChildOpts {
249
363
  * but no agent on the child side asked for an outbound send.
250
364
  */
251
365
  outboundMailBridge?: ChildOutboundMailBridge;
366
+ /**
367
+ * Optional mailbox-mutation bridge (INBOUND half of mailbox ownership,
368
+ * §3b). The step agent's mail tools mutate the INBOX -- flag writes and
369
+ * `expunge` -- through a transport whose write methods route through
370
+ * this bridge: it emits a `mailbox.mutate.request` upstream control
371
+ * frame and resolves once the supervisor's matching
372
+ * `mailbox.mutate.response` lands. The control loop routes the
373
+ * downstream response frame to the bridge's `handleResult` and invokes
374
+ * `cancelAll` on any exit path so a pending mutation does not leak an
375
+ * awaiter after the supervisor tears the IPC down. When omitted,
376
+ * inbound `mailbox.mutate.response` frames are logged at warn-level and
377
+ * dropped -- the wire shape is well-formed but no agent on the child
378
+ * side asked for a mutation.
379
+ */
380
+ mailboxMutationBridge?: ChildMailboxMutationBridge;
381
+ /**
382
+ * Optional mailbox watch registry (INBOUND half of mailbox ownership,
383
+ * design §3b). The supervisor -- the sole mail owner -- commits an arrived
384
+ * message to the workflow-run substrate mailbox and fires a `mailbox.notify`
385
+ * control frame; the control loop routes that frame to this registry's
386
+ * `fire`, which delivers a typed `exists` `MailboxEvent` to the callbacks the
387
+ * step agent's supervisor-backed transport registered through `watch`
388
+ * (backing `mail_wait`). When omitted, an inbound `mailbox.notify` frame is
389
+ * logged at warn-level and dropped -- the wire shape is well-formed but no
390
+ * watcher on the child side asked for inbound events.
391
+ */
392
+ mailboxWatchRegistry?: MailboxWatchRegistry;
252
393
  }
253
394
  /**
254
395
  * Narrow interface the child's control loop calls when downstream
@@ -286,6 +427,21 @@ export interface RunWorkflowChildResult {
286
427
  * exits cleanly).
287
428
  */
288
429
  export declare function runWorkflowChild(opts: RunWorkflowChildOpts): Promise<RunWorkflowChildResult>;
430
+ /**
431
+ * Forward a control-plane suspension to the supervisor over the upstream
432
+ * control channel. Fired from `env.onPark` each time a workflow agent step
433
+ * parks on a reserved `signalName(correlationId)` channel. The supervisor's
434
+ * `park.notify` arm stamps the deployment identity it owns and sends a
435
+ * `signal.correlation.register` frame to the hub.
436
+ *
437
+ * Best-effort like `emitTerminalEvent`'s send: a transport failure is logged,
438
+ * not rethrown. A lost frame means the correlation is not registered and the
439
+ * parked run cannot be resumed until it is re-registered; the failure surfaces
440
+ * structurally as a run that never resumes rather than a silent lifecycle
441
+ * corruption. The register at the hub is idempotent, so a re-park resume's
442
+ * re-emit is safe.
443
+ */
444
+ export declare function emitParkNotify(upstreamSender: ControlChannelSender, park: WorkflowPark): Promise<void>;
289
445
  /**
290
446
  * Mirror a run's terminal status back to the supervisor over the
291
447
  * upstream control channel. Fired once per run from the resume and