@intx/workflow-host 0.2.2

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 (81) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +287 -0
  3. package/dist/adapters/blob-substrate.d.ts +49 -0
  4. package/dist/adapters/blob-substrate.js +140 -0
  5. package/dist/adapters/repo-store.d.ts +39 -0
  6. package/dist/adapters/repo-store.js +344 -0
  7. package/dist/adapters/spawn-child.d.ts +74 -0
  8. package/dist/adapters/spawn-child.js +152 -0
  9. package/dist/adapters/step-invoker.d.ts +114 -0
  10. package/dist/adapters/step-invoker.js +360 -0
  11. package/dist/child/env-bootstrap.d.ts +56 -0
  12. package/dist/child/env-bootstrap.js +120 -0
  13. package/dist/child/from-process-env.d.ts +127 -0
  14. package/dist/child/from-process-env.js +183 -0
  15. package/dist/child/index.d.ts +9 -0
  16. package/dist/child/index.js +9 -0
  17. package/dist/child/outbound-mail-bridge.d.ts +36 -0
  18. package/dist/child/outbound-mail-bridge.js +143 -0
  19. package/dist/child/proxy-repo-store.d.ts +27 -0
  20. package/dist/child/proxy-repo-store.js +200 -0
  21. package/dist/child/run-child.d.ts +320 -0
  22. package/dist/child/run-child.js +900 -0
  23. package/dist/child/self-discovery.d.ts +29 -0
  24. package/dist/child/self-discovery.js +57 -0
  25. package/dist/child/substrate-write-bridge.d.ts +72 -0
  26. package/dist/child/substrate-write-bridge.js +188 -0
  27. package/dist/child/supervisor-backed-transport.d.ts +10 -0
  28. package/dist/child/supervisor-backed-transport.js +113 -0
  29. package/dist/child/warm-agent-cache.d.ts +78 -0
  30. package/dist/child/warm-agent-cache.js +112 -0
  31. package/dist/drain-controller.d.ts +37 -0
  32. package/dist/drain-controller.js +46 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.js +10 -0
  35. package/dist/ipc/control-channel.d.ts +336 -0
  36. package/dist/ipc/control-channel.js +532 -0
  37. package/dist/ipc/crypto.d.ts +46 -0
  38. package/dist/ipc/crypto.js +126 -0
  39. package/dist/ipc/envelope.d.ts +53 -0
  40. package/dist/ipc/envelope.js +88 -0
  41. package/dist/ipc/event-channel.d.ts +677 -0
  42. package/dist/ipc/event-channel.js +278 -0
  43. package/dist/ipc/index.d.ts +4 -0
  44. package/dist/ipc/index.js +143 -0
  45. package/dist/mail-bus/hub-transport-adapter.d.ts +30 -0
  46. package/dist/mail-bus/hub-transport-adapter.js +76 -0
  47. package/dist/mail-bus/index.d.ts +1 -0
  48. package/dist/mail-bus/index.js +1 -0
  49. package/dist/seams/index.d.ts +3 -0
  50. package/dist/seams/index.js +3 -0
  51. package/dist/seams/scheduler-adapter.d.ts +3 -0
  52. package/dist/seams/scheduler-adapter.js +24 -0
  53. package/dist/seams/scheduler.d.ts +94 -0
  54. package/dist/seams/scheduler.js +397 -0
  55. package/dist/seams/signal-channel.d.ts +74 -0
  56. package/dist/seams/signal-channel.js +304 -0
  57. package/dist/supervisor/cancel-signing.d.ts +68 -0
  58. package/dist/supervisor/cancel-signing.js +144 -0
  59. package/dist/supervisor/child-termination.d.ts +51 -0
  60. package/dist/supervisor/child-termination.js +76 -0
  61. package/dist/supervisor/credentials.d.ts +101 -0
  62. package/dist/supervisor/credentials.js +153 -0
  63. package/dist/supervisor/dispatch-attribution.d.ts +37 -0
  64. package/dist/supervisor/dispatch-attribution.js +114 -0
  65. package/dist/supervisor/drain-timeout.d.ts +127 -0
  66. package/dist/supervisor/drain-timeout.js +231 -0
  67. package/dist/supervisor/index.d.ts +7 -0
  68. package/dist/supervisor/index.js +6 -0
  69. package/dist/supervisor/recycle.d.ts +212 -0
  70. package/dist/supervisor/recycle.js +440 -0
  71. package/dist/supervisor/run-event-compaction.d.ts +34 -0
  72. package/dist/supervisor/run-event-compaction.js +115 -0
  73. package/dist/supervisor/spawn-env.d.ts +39 -0
  74. package/dist/supervisor/spawn-env.js +36 -0
  75. package/dist/supervisor/supervisor.d.ts +202 -0
  76. package/dist/supervisor/supervisor.js +2244 -0
  77. package/dist/supervisor/terminal-broadcaster.d.ts +45 -0
  78. package/dist/supervisor/terminal-broadcaster.js +184 -0
  79. package/dist/supervisor/types.d.ts +542 -0
  80. package/dist/supervisor/types.js +10 -0
  81. package/package.json +35 -0
@@ -0,0 +1,542 @@
1
+ import type { DequeueToProcessingResult, EnqueueInboxArgs, EnqueueInboxResult, MarkConsumedArgs, MarkConsumedResult, Principal, RepoId, RepoStore as SubstrateRepoStore, ReplayProcessingToInboxOpts, ReplayProcessingToInboxResult } from "@intx/hub-sessions/substrate";
2
+ import type { OutboundMessage, SendReceipt } from "@intx/types/runtime";
3
+ import type { RunCancelled, RunCompleted, RunFailed } from "@intx/workflow";
4
+ import type { FrameReader, NdjsonReader, NdjsonWriter } from "../ipc/index.js";
5
+ /**
6
+ * Terminal workflow-run event the supervisor's drain accumulators
7
+ * consume to settle ahead of the drainTimeout deadline. The shape is
8
+ * the discriminated union of every terminal kind the workflow-run
9
+ * state machine emits; downstream wiring can switch on `kind` without
10
+ * having to import the workflow package directly.
11
+ */
12
+ export type TerminalRunEvent = RunCompleted | RunFailed | RunCancelled;
13
+ /**
14
+ * Per-runId terminal-event source the supervisor consumes to settle
15
+ * armed drainTimeout accumulators and the inbox dispatch loop when the
16
+ * run reaches a terminal phase. Each invocation returns an
17
+ * `AsyncIterable` scoped to one `runId`; the consumer pulls until the
18
+ * first terminal event arrives or the iterator is finalised (via
19
+ * `return()` on the iterator, which the accumulator calls during
20
+ * `stop`).
21
+ *
22
+ * The supervisor owns a per-cohort broadcaster that implements this
23
+ * shape against the `terminal.event` upstream control frames the
24
+ * workflow-process child emits when its runtime body settles a run.
25
+ * The accumulator and the dispatch loop borrow the broadcaster's
26
+ * subscribe surface; consumers therefore do not have to round-trip
27
+ * the workflow-run substrate from the supervisor's address space.
28
+ */
29
+ export type TerminalEventSource = (runId: string) => AsyncIterable<TerminalRunEvent>;
30
+ /**
31
+ * Workflow-side principal kinds the supervisor signs on behalf of.
32
+ * Mirrors the kinds the workflow-run kind handler binds to specific
33
+ * CancelRequested origins.
34
+ *
35
+ * - `supervisor`: the supervisor's own identity. Used for every
36
+ * CancelRequested origin in the Q3 map except `hub-admin`, plus
37
+ * drain audit frames in later commits.
38
+ */
39
+ export type WorkflowSupervisorPrincipalKind = "supervisor";
40
+ /**
41
+ * Output of a `signAsPrincipal` invocation. Carries the raw 64-byte
42
+ * Ed25519 signature plus the principal kind the supervisor asked the
43
+ * host to sign as so a downstream verifier can map the signature to
44
+ * the right public key without consulting external metadata.
45
+ */
46
+ export type SignedPayload = {
47
+ /** Raw signature bytes from Ed25519 (64 bytes per RFC 8032). */
48
+ readonly sig: Uint8Array;
49
+ /** Principal kind the host signed as. */
50
+ readonly principalKind: WorkflowSupervisorPrincipalKind;
51
+ };
52
+ /**
53
+ * Host-supplied per-principal signing callback. The supervisor never
54
+ * holds the principal's private key; it asks the host to mint a
55
+ * signature for the supplied canonical payload bytes under the
56
+ * named principal's key.
57
+ *
58
+ * The host wires this against its own key inventory (the sidecar's
59
+ * existing Ed25519 signing keypair, the integration harness's test
60
+ * key). The supervisor surfaces nothing about how the key is held;
61
+ * the callback is the entire surface.
62
+ */
63
+ export type PrincipalSigner = (kind: WorkflowSupervisorPrincipalKind, payload: Uint8Array) => Promise<SignedPayload>;
64
+ /**
65
+ * Mail-bus interface the supervisor needs. The shape is the minimal
66
+ * subset of an existing mail-bus API the supervisor's spawn / mail-
67
+ * trigger / teardown lifecycle reaches into; it does not pin the
68
+ * supervisor to `InMemoryTransport` or any other concrete bus.
69
+ *
70
+ * `subscribeMailForAddress` returns a disposer the supervisor calls
71
+ * during teardown. The supplied handler is invoked with the raw RFC
72
+ * 2822 message bytes of each inbound message at the address.
73
+ *
74
+ * `sendOutbound` is the OUTBOUND half of mailbox ownership (§3a). The
75
+ * supervisor is the sole mail owner: the workflow-process child never
76
+ * holds the agent's signing key and never calls `transport.send`
77
+ * itself. When a step agent produces a reply (or invokes a mail-send
78
+ * tool), the child forwards the structured `OutboundMessage` plus the
79
+ * sender (agent) address up over the control IPC; the supervisor calls
80
+ * `sendOutbound` to perform the actual signed send through the host's
81
+ * real transport. The host implementation signs the message with the
82
+ * sender's `CryptoProvider` exactly as the in-process path does
83
+ * (`@intx/mail-memory`'s `executeSend`), so the outbound mail carries
84
+ * the AGENT's signature with full parity to the pre-supervisor path.
85
+ *
86
+ * The signing identity lives at the host transport (registered per
87
+ * address via `register(address, crypto)`); the supervisor does not
88
+ * hold the agent's key and the child does not either. A send for an
89
+ * address with no registered crypto surfaces loudly (the host
90
+ * implementation throws) rather than emitting unsigned mail.
91
+ */
92
+ export interface MailBusBindings {
93
+ registerAddress(address: string): void;
94
+ unregisterAddress(address: string): void;
95
+ subscribeMailForAddress(address: string, handler: (rawMessage: Uint8Array) => void): () => void;
96
+ sendOutbound(senderAddress: string, message: OutboundMessage): Promise<SendReceipt>;
97
+ }
98
+ /**
99
+ * Handle the subprocess spawner returns to the supervisor. The
100
+ * fields mirror what `Bun.spawn` returns; tests substitute an
101
+ * in-process implementation that fulfills the same shape.
102
+ *
103
+ * `stdin`/`stdout` carry the control channel (NDJSON over stdio,
104
+ * Ed25519-signed by the supervisor). `eventSocket` carries the
105
+ * event channel (HMAC-authenticated). `kill()` terminates the
106
+ * process; `exited` resolves when the process exits with the
107
+ * terminal exit code.
108
+ */
109
+ export interface SubprocessHandle {
110
+ readonly pid: number;
111
+ /**
112
+ * Writer for the supervisor-to-child control channel (stdin on the
113
+ * child). The supervisor's control-channel sender feeds NDJSON
114
+ * lines through this writer.
115
+ */
116
+ readonly controlWriter: NdjsonWriter;
117
+ /**
118
+ * Reader for the child-to-supervisor control channel (stdout on
119
+ * the child). Carries the child's `ready` frame and the rare
120
+ * upstream control messages the child sends back.
121
+ */
122
+ readonly controlReader: NdjsonReader;
123
+ /**
124
+ * Supervisor-side handle on the inherited event-channel
125
+ * socketpair. The supervisor's event-channel receiver consumes
126
+ * authenticated InferenceEvent frames from here.
127
+ */
128
+ readonly eventReader: FrameReader;
129
+ kill(signal?: number | string): void;
130
+ exited: Promise<number>;
131
+ }
132
+ /**
133
+ * Subprocess spawner the supervisor invokes to spawn the workflow-
134
+ * process. The host injects `Bun.spawn` in production; tests inject
135
+ * a deterministic mock. The supervisor invokes this with the
136
+ * resolved binary path, the spawn-time env (carrying only the IPC
137
+ * trust anchors plus substrate-config keys -- never the supervisor's
138
+ * private key), and the prepared event-channel socketpair handle.
139
+ */
140
+ export type SubprocessSpawner = (args: {
141
+ /** Absolute path to the host-owned `bin/workflow-child` script. */
142
+ binaryPath: string;
143
+ /** Fresh env object containing IPC trust anchors + substrate-config keys. */
144
+ env: Record<string, string>;
145
+ }) => SubprocessHandle;
146
+ /**
147
+ * Logical pointer to the raw mail bytes the inbox claim-check
148
+ * envelope stamps. The substrate stores this as the `mailAuditRef`
149
+ * on every inbox/processing/consumed envelope; the substrate itself
150
+ * never dereferences it. The boot edge supplies a derivation
151
+ * coherent with wherever the deployment's mail audit actually lives
152
+ * (a sidecar's session-mail audit, an alternative host's audit
153
+ * store, etc.).
154
+ */
155
+ export type MailAuditRef = {
156
+ store: string;
157
+ path: string;
158
+ };
159
+ /**
160
+ * Host-supplied derivation that maps a parsed `messageId` plus the
161
+ * raw mail bytes into the logical audit reference the inbox claim-
162
+ * check envelope carries. Pure -- the supervisor invokes it on the
163
+ * mail-arrival hot path and expects no I/O. Absent binding falls
164
+ * back to a deterministic in-process derivation that synthesizes
165
+ * `{ store: "in-process", path: <messageId> }` so the supervisor's
166
+ * library tests that do not stand up an audit store still work; the
167
+ * production sidecar boot edge supplies a real derivation.
168
+ */
169
+ export type DeriveMailAuditRef = (messageId: string, rawMessage: Uint8Array) => MailAuditRef;
170
+ /**
171
+ * Inbox claim-check primitives the supervisor's mail-arrival path
172
+ * and dispatch loop reach into. Production wires this against the
173
+ * concrete `enqueueInbox` / `dequeueToProcessing` / `markConsumed`
174
+ * / `replayProcessingToInbox` functions exported from
175
+ * `@intx/hub-sessions`; tests inject a deterministic in-memory
176
+ * stub so the supervisor's dispatch loop is observable without a
177
+ * real git substrate.
178
+ *
179
+ * The shape mirrors the upstream functions exactly so a binding
180
+ * miss surfaces as a structural type error rather than a runtime
181
+ * surprise.
182
+ */
183
+ export interface InboxPrimitives {
184
+ enqueueInbox(store: SubstrateRepoStore, principal: Principal, repoId: RepoId, args: EnqueueInboxArgs): Promise<EnqueueInboxResult>;
185
+ dequeueToProcessing(store: SubstrateRepoStore, principal: Principal, repoId: RepoId, address: string): Promise<DequeueToProcessingResult>;
186
+ markConsumed(store: SubstrateRepoStore, principal: Principal, repoId: RepoId, args: MarkConsumedArgs): Promise<MarkConsumedResult>;
187
+ replayProcessingToInbox(store: SubstrateRepoStore, principal: Principal, repoId: RepoId, address: string, opts?: ReplayProcessingToInboxOpts): Promise<ReplayProcessingToInboxResult>;
188
+ }
189
+ /**
190
+ * Constructor arguments for `createWorkflowSupervisor`. One
191
+ * `RepoStore` handle plus a `signAsPrincipal` callback that mints
192
+ * signatures on demand per principal, rather than pre-minting
193
+ * per-principal `RepoStore` views. Every write-site is explicit
194
+ * about which principal it claims to be, and the supervisor never
195
+ * holds a private key in plaintext.
196
+ */
197
+ export interface WorkflowSupervisorBindings {
198
+ /** Substrate handle the supervisor reads grants from and commits events to. */
199
+ repoStore: SubstrateRepoStore;
200
+ /** Per-principal signing callback. See `PrincipalSigner`. */
201
+ signAsPrincipal: PrincipalSigner;
202
+ /** Mail-bus surface for address registration and inbound subscription. */
203
+ mailBus: MailBusBindings;
204
+ /** Subprocess spawner the supervisor invokes per spawn. */
205
+ subprocessSpawner: SubprocessSpawner;
206
+ /**
207
+ * Absolute path to the host-owned `bin/workflow-child` script
208
+ * the spawner invokes. Pre-resolved by the host so the supervisor
209
+ * does not have to consult `require.resolve` / `import.meta.resolve`
210
+ * itself (tests inject a sentinel path the spawner mock asserts on).
211
+ */
212
+ binaryPath: string;
213
+ /**
214
+ * Substrate-config keys the binary needs to construct a RepoStore.
215
+ * The supervisor carries these straight from the host into the
216
+ * child's spawn-time env without inspecting them; the binary's
217
+ * construction logic owns the shape.
218
+ */
219
+ substrateEnv: Record<string, string>;
220
+ /**
221
+ * Per-spawn dynamic substrate-env entries the host recomputes for every
222
+ * spawn AND every recycle respawn. Distinct from `substrateEnv`, which is
223
+ * frozen for the deployment's lifetime: this callback lets a value the
224
+ * host revised between spawns (a live inference-source rotation) reach the
225
+ * respawned child. Invoked by the spawn-env builder on each spawn/respawn;
226
+ * its keys layer over `substrateEnv` and under the IPC anchors. Like
227
+ * `substrateEnv`, the supervisor does not inspect the returned keys -- the
228
+ * host owns their shape. A host with no dynamic entries returns `{}`.
229
+ */
230
+ dynamicSpawnEnv: () => Record<string, string>;
231
+ /**
232
+ * Workflow-run repo identity for the deployment. The supervisor
233
+ * commits its own CancelRequested / drain events here.
234
+ */
235
+ workflowRunRepoId: import("@intx/hub-sessions").RepoId;
236
+ /** Workflow-run repo ref the supervisor commits events to. */
237
+ workflowRunRef: string;
238
+ /** Deployment id baked into the supervisor's principal claims. */
239
+ deploymentId: string;
240
+ /**
241
+ * Number of steps in the deployed `WorkflowDefinition`
242
+ * (`stepOrder.length`). The supervisor threads this into the child's
243
+ * spawn-time env (`STEP_COUNT`) so the child's deploy-tree read
244
+ * (`resolveStepAddress` in the sidecar step tools) collapses onto the
245
+ * head for a single-step deployment exactly as the host's producer
246
+ * push does -- one source of truth for the head/step collapse across
247
+ * the two processes. Fixed for the deployment's lifetime.
248
+ */
249
+ stepCount: number;
250
+ /**
251
+ * Mail address the deployment registers on the bus. The
252
+ * supervisor registers this on spawn and unregisters on teardown;
253
+ * inbound mail at this address flows through the supervisor's
254
+ * trigger.fire path.
255
+ */
256
+ deploymentMailAddress: string;
257
+ /**
258
+ * The supervisor's substrate principal for read-only operations
259
+ * the supervisor performs in its own right (e.g. enumerating
260
+ * step grants). Cancel-signing constructs its own principal at
261
+ * commit time; this binding is for non-write operations.
262
+ */
263
+ readPrincipal: Principal;
264
+ /**
265
+ * Per-step mail-address derivation the supervisor uses while
266
+ * assembling the credentialsSnapshot. See `credentials.ts`.
267
+ */
268
+ deriveStepAddress: import("./credentials.js").DeriveStepAddress;
269
+ /**
270
+ * Optional override for the step's agent-state repo identity. The
271
+ * default convention is `<deploymentId>-<stepId>`.
272
+ */
273
+ deriveStepRepoId?: import("./credentials.js").DeriveStepRepoId;
274
+ /**
275
+ * Optional override for the per-spawn IPC keypair factory. Each
276
+ * spawn mints a fresh control-channel Ed25519 keypair; this hook
277
+ * lets the host supply a deterministic factory (a test harness
278
+ * that needs to assert on the env's HOST_PUBKEY, or a wiring path
279
+ * that wants the keypair lifecycle to flow through its own crypto
280
+ * boundary). Production wires it against the same
281
+ * `@intx/crypto` generator the supervisor would have used.
282
+ */
283
+ ipcKeyPairFactory?: () => Promise<{
284
+ privateKey: Uint8Array;
285
+ publicKey: Uint8Array;
286
+ }>;
287
+ /**
288
+ * Operator-overridable per-deployment `drainTimeout` in
289
+ * milliseconds. The supervisor's `drain()` path threads this value
290
+ * into every drainTimeout accumulator it arms. Absent value defers
291
+ * to the accumulator's `DEFAULT_DRAIN_TIMEOUT_MS` constant.
292
+ */
293
+ drainTimeoutMs?: number;
294
+ /**
295
+ * Optional override for the supervisor's drainTimeout accumulator
296
+ * factory. Production wires this against
297
+ * `createDrainTimeoutAccumulator` directly; tests inject a mock
298
+ * factory so the supervisor's drain arming becomes observable
299
+ * without rigging a fake timer host. The factory shape matches
300
+ * `createDrainTimeoutAccumulator`'s public signature exactly.
301
+ */
302
+ drainTimeoutAccumulatorFactory?: import("./drain-timeout.js").DrainTimeoutAccumulatorFactory;
303
+ /**
304
+ * Clock the supervisor threads into the drainTimeout accumulator.
305
+ * Production wires `() => Date.now()`; tests inject a deterministic
306
+ * fake clock. Defaults to `Date.now` when omitted.
307
+ */
308
+ now?: () => number;
309
+ /**
310
+ * General scheduling primitive the supervisor threads into its timed
311
+ * waits: the drainTimeout accumulator, the spawn ready-handshake
312
+ * timeout, and that timeout's SIGTERM->SIGKILL kill escalation.
313
+ * Production wires `(cb, ms) => setTimeout(cb, ms)`; tests inject a
314
+ * deterministic timer host. Defaults to `setTimeout` when omitted.
315
+ */
316
+ setTimer?: (cb: () => void, ms: number) => unknown;
317
+ /**
318
+ * Disposer paired with `setTimer`. Production wires
319
+ * `(h) => clearTimeout(h as ReturnType<typeof setTimeout>)`;
320
+ * tests inject the matching disposer for their fake timer host.
321
+ * Defaults to `clearTimeout` when omitted.
322
+ */
323
+ clearTimer?: (handle: unknown) => void;
324
+ /**
325
+ * Optional recycle-policy configuration. Absent or all-`undefined`
326
+ * fields disable the periodic check; the supervisor's policy timer
327
+ * is not armed. Operator overrides land here via the host's
328
+ * per-deployment config.
329
+ */
330
+ recyclePolicy?: import("./recycle.js").RecyclePolicyBounds;
331
+ /**
332
+ * Optional RSS reader the policy consults per tick when
333
+ * `recyclePolicy.maxRssBytes` is set. Returns the workflow-process
334
+ * child's current resident-set size in bytes, or `undefined` if the
335
+ * host does not have a current sample.
336
+ */
337
+ readRssBytes?: () => number | undefined;
338
+ /**
339
+ * Optional grants-age reader the policy consults per tick when
340
+ * `recyclePolicy.maxGrantsAgeMs` is set. Returns the wall-clock age
341
+ * (ms) of the most recent grants refresh visible to this
342
+ * supervisor, or `undefined` if no refresh has been observed yet.
343
+ */
344
+ readGrantsAgeMs?: () => number | undefined;
345
+ /**
346
+ * Optional now-reader for the recycle policy. Defaults to
347
+ * `Date.now()`; tests inject a deterministic clock.
348
+ */
349
+ recyclePolicyNow?: () => number;
350
+ /**
351
+ * Optional setTimer/clearTimer pair for the recycle policy and the
352
+ * recycle path's SIGKILL escalation. Defaults to
353
+ * `setTimeout`/`clearTimeout`; tests inject a controllable timer.
354
+ */
355
+ recyclePolicySetTimer?: (cb: () => void, ms: number) => unknown;
356
+ recyclePolicyClearTimer?: (handle: unknown) => void;
357
+ /**
358
+ * Compute the logical audit reference for an inbox entry. The
359
+ * substrate stamps this onto the envelope; the substrate itself
360
+ * does not dereference it. The boot edge supplies a derivation
361
+ * coherent with wherever the deployment's mail audit lives.
362
+ *
363
+ * Absent binding falls back to `{ store: "in-process",
364
+ * path: <messageId> }`, a deterministic derivation that lets the
365
+ * supervisor's library tests run without a host-side audit store.
366
+ */
367
+ deriveMailAuditRef?: DeriveMailAuditRef;
368
+ /**
369
+ * Inbox claim-check primitives the dispatch loop reaches into.
370
+ * Production wires this against `@intx/hub-sessions`'s concrete
371
+ * `enqueueInbox` / `dequeueToProcessing` / `markConsumed` /
372
+ * `replayProcessingToInbox`; tests inject a deterministic
373
+ * in-memory stub so the dispatch loop is observable without a
374
+ * git substrate.
375
+ */
376
+ inboxPrimitives?: InboxPrimitives;
377
+ /**
378
+ * Workflow-run substrate principal the supervisor uses to author
379
+ * inbox/processing/consumed writes. The substrate's workflow-run
380
+ * kind handler accepts a `{ kind: "supervisor", deploymentId }`
381
+ * principal for claim-check writes; the supervisor constructs this
382
+ * value once at bindings construction and reuses it for every
383
+ * claim-check operation. Defaults to `{ kind: "supervisor",
384
+ * deploymentId }` derived from `bindings.deploymentId`; tests
385
+ * override it when they need to assert on a structurally distinct
386
+ * principal shape.
387
+ */
388
+ inboxWritePrincipal?: Principal;
389
+ /**
390
+ * Retention horizon for the consumed dedup index, in milliseconds.
391
+ * Threaded into every `markConsumed` so the per-address watermark
392
+ * advances to `consumedAt - consumedRetentionMs` and the consumed/
393
+ * index is pruned to a bounded steady state. This is an
394
+ * OPERATOR-policy value: the longest window in which the same
395
+ * message could legitimately be re-submitted and still must be
396
+ * caught as a duplicate. The boot edge resolves the operator's
397
+ * config and supplies it; absent, `DEFAULT_CONSUMED_RETENTION_MS`
398
+ * (24h) applies. The invariant the operator owns: the horizon must
399
+ * be >= the maximum redelivery window of any at-least-once source if
400
+ * one is ever added, or dedup breaks (a breach surfaces loudly as a
401
+ * refused stale enqueue, not silent double-processing).
402
+ */
403
+ consumedRetentionMs?: number;
404
+ /**
405
+ * Bound on the child's spawn-time `ready` handshake, in milliseconds.
406
+ * A spawned child that neither emits `ready` nor exits would block
407
+ * `spawn` forever; on expiry the supervisor kills the child (SIGTERM,
408
+ * then SIGKILL) and rejects the spawn. The boot edge resolves the
409
+ * operator's config and supplies it; absent, `DEFAULT_READY_TIMEOUT_MS`
410
+ * (30s) applies. Callers surface the rejection through their existing
411
+ * spawn-failure path, so a wedged child fails the deploy (or, on the
412
+ * sidecar, is skipped by boot-time restore) instead of hanging it.
413
+ */
414
+ readyTimeoutMs?: number;
415
+ /**
416
+ * Watchdog timeout (ms) for the supervisor's substrate-write
417
+ * handler's wait on the dispatch loop's `markConsumed` when a
418
+ * terminal-event blob lands in a proxied write. Defaults to
419
+ * `DEFAULT_TERMINAL_WRITE_WATCHDOG_MS`. Tests inject a small value
420
+ * so the watchdog path is observable without holding a test loop
421
+ * for the production duration.
422
+ */
423
+ terminalWriteWatchdogMs?: number;
424
+ /**
425
+ * Optional per-message dispatch-timing observer. When supplied, the
426
+ * dispatch loop invokes it twice per dispatched inbox entry: once with
427
+ * marker `"dispatch-start"` the instant the entry is dequeued for
428
+ * dispatch (claim-check read complete, `trigger.fire` about to be
429
+ * forwarded) and once with marker `"reply-produced"` the instant the
430
+ * child's terminal-event frame for that run lands back at the
431
+ * supervisor (the run's reply is produced and committed). Both marks
432
+ * carry the same monotonic `atMs` clock so an observer can compute the
433
+ * per-message infra round-trip the unified path adds over a bare
434
+ * in-process `agent.send`.
435
+ *
436
+ * This is a pure observability hook with no control-flow effect. It is
437
+ * absent in production and wired only by the Phase 4.7 latency-gate
438
+ * benchmark, which threads it through the sidecar host so the
439
+ * supervisor (running in the sidecar subprocess, where both ends of
440
+ * the IPC round-trip are visible in one process) emits a parseable
441
+ * timing line. A throwing observer is swallowed and logged so a
442
+ * benchmark hook bug cannot wedge dispatch.
443
+ */
444
+ onDispatchTiming?: (mark: DispatchTimingMark) => void;
445
+ /**
446
+ * D2 §10c forced-repack A/B toggle (measurement-only). When supplied
447
+ * with a non-zero `everyMessages`, the dispatch loop forces a
448
+ * `git gc`/repack of the workflow-run repo every `everyMessages`-th
449
+ * dispatched message (after `markConsumed`, under the single-writer
450
+ * discipline). This exists solely to discriminate pack-growth from
451
+ * tree-fan-out as the dominant per-message substrate cost: if forcing a
452
+ * repack flattens the per-leg slope the cost is loose-object/pack
453
+ * growth (cheap pack/gc fix); if it does not, the cost is the
454
+ * per-commit root-tree rewrite scaling with `runs/` + `consumed/`
455
+ * fan-out (run-model change). Absent in production -- the dispatch path
456
+ * forks no `git gc` when this is unset.
457
+ */
458
+ repackEveryMessages?: {
459
+ everyMessages: number;
460
+ };
461
+ }
462
+ /**
463
+ * The five per-message substrate legs the D2 attribution splits the
464
+ * unified path's substrate tax across. Each is a single git
465
+ * `writeTreePreservingPrefix` commit (or, for `runevent`, one of several
466
+ * commits per message) against the growing workflow-run repo:
467
+ *
468
+ * - `enqueue` — `enqueueInbox` in `onMailMessage`, BEFORE dispatch
469
+ * (paid OUTSIDE the dispatch-start..reply-produced
470
+ * window; its growth is invisible to the 4.7 bracket).
471
+ * - `dequeue` — `dequeueToProcessing`, the claim-check READ at the
472
+ * head of `dispatchOne` (inside the window).
473
+ * - `runevent` — a run-event bracket commit
474
+ * (`runs/<runId>/events/<seq>.json`), arriving as a
475
+ * child-proxied `substrate.write.request` (inside the
476
+ * window). One message may produce several; each is
477
+ * stamped and the D2 post-processing sums them and
478
+ * counts them per message.
479
+ * - `markconsumed` — `markConsumed` at the tail of `dispatchOne`, AFTER
480
+ * `reply-produced` (paid OUTSIDE the window).
481
+ * - `wal` — the D1 conversation WAL append / checkpoint
482
+ * (`agent-state/<key>/...`), arriving as a
483
+ * child-proxied `substrate.write.request`. The control
484
+ * leg: post-D1 it should be small and flat.
485
+ */
486
+ export type DispatchSubstrateLeg = "enqueue" | "dequeue" | "runevent" | "markconsumed" | "wal";
487
+ /**
488
+ * Structural counters sampled at a `leg` mark's `"end"` phase so the D2
489
+ * attribution can explain WHY a leg grows, not merely that it does
490
+ * (design §10b). All are cheap filesystem reads against the workflow-run
491
+ * repo's on-disk working tree, taken only when the observer is wired.
492
+ *
493
+ * - `runsFanOut` — entry count under `runs/` (one subdir per message;
494
+ * never pruned). The candidate-(i) "collapse runs"
495
+ * win is sized by this.
496
+ * - `consumedFanOut` — entry count under
497
+ * `addresses/<addr>/consumed/` (one dedup entry per
498
+ * message; never pruned). The candidate-(iv) "prune
499
+ * consumed" win is sized by this.
500
+ * - `looseObjects` — count of loose git objects under
501
+ * `.git/objects/<xx>/` (the pack-growth proxy; the
502
+ * §10c repack A/B targets this).
503
+ * - `gitBytes` — total byte size of the repo's `.git` directory
504
+ * (loose + pack), a coarse repo-size proxy.
505
+ */
506
+ export type DispatchStructuralCounters = {
507
+ runsFanOut: number;
508
+ consumedFanOut: number;
509
+ looseObjects: number;
510
+ gitBytes: number;
511
+ };
512
+ /**
513
+ * One observation emitted by `WorkflowSupervisorBindings.onDispatchTiming`.
514
+ *
515
+ * The `"roundtrip"` variant is the 4.7 latency-gate bracket: pair the
516
+ * `"dispatch-start"` and `"reply-produced"` marks for the same `runId` to
517
+ * recover the per-message round-trip. `atMs` is a high-resolution
518
+ * monotonic timestamp (`performance.now()`).
519
+ *
520
+ * The `"leg"` variant is the D2 per-leg attribution surface: a paired
521
+ * `start`/`end` mark around one of the five substrate commits
522
+ * (`DispatchSubstrateLeg`), so each leg's per-message slope (ms added per
523
+ * sustained message) and floor (intercept) can be fit independently. The
524
+ * `end` mark of a `runevent`/`markconsumed`/`enqueue`/`dequeue` leg also
525
+ * carries the structural counters sampled at commit time. Both variants
526
+ * flow through the same off-by-default observer; production leaves the
527
+ * observer unwired and samples no clock and no counter.
528
+ */
529
+ export type DispatchTimingMark = {
530
+ kind: "roundtrip";
531
+ runId: string;
532
+ marker: "dispatch-start" | "reply-produced";
533
+ atMs: number;
534
+ } | {
535
+ kind: "leg";
536
+ runId: string;
537
+ leg: DispatchSubstrateLeg;
538
+ phase: "start" | "end";
539
+ atMs: number;
540
+ /** Sampled only on the `"end"` phase; absent on `"start"`. */
541
+ counters?: DispatchStructuralCounters;
542
+ };
@@ -0,0 +1,10 @@
1
+ // Public type shapes for the per-deployment supervisor surface.
2
+ //
3
+ // The supervisor takes its bindings as constructor arguments: a
4
+ // `RepoStore` substrate handle, a per-principal signing callback,
5
+ // mail-bus bindings, a subprocess spawner. None of these shapes
6
+ // depend on a specific host implementation -- the sidecar, an
7
+ // alternative sidecar, an integration test harness, and a future CLI
8
+ // can each construct a supervisor by wiring its own concrete
9
+ // instances against the same interface.
10
+ export {};
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@intx/workflow-host",
3
+ "version": "0.2.2",
4
+ "license": "LGPL-2.1-only",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "intx-src": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "dependencies": {
14
+ "@intx/agent": "0.2.2",
15
+ "@intx/crypto": "0.2.2",
16
+ "@intx/hub-sessions": "0.2.2",
17
+ "@intx/inference": "0.2.2",
18
+ "@intx/log": "0.2.2",
19
+ "@intx/mail-memory": "0.2.2",
20
+ "@intx/mime": "0.2.2",
21
+ "@intx/storage-isogit": "0.2.2",
22
+ "@intx/types": "0.2.2",
23
+ "@intx/workflow": "0.2.2",
24
+ "arktype": "^2.1.29"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "sideEffects": false,
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }