@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.
- package/LICENSE +176 -0
- package/README.md +287 -0
- package/dist/adapters/blob-substrate.d.ts +49 -0
- package/dist/adapters/blob-substrate.js +140 -0
- package/dist/adapters/repo-store.d.ts +39 -0
- package/dist/adapters/repo-store.js +344 -0
- package/dist/adapters/spawn-child.d.ts +74 -0
- package/dist/adapters/spawn-child.js +152 -0
- package/dist/adapters/step-invoker.d.ts +114 -0
- package/dist/adapters/step-invoker.js +360 -0
- package/dist/child/env-bootstrap.d.ts +56 -0
- package/dist/child/env-bootstrap.js +120 -0
- package/dist/child/from-process-env.d.ts +127 -0
- package/dist/child/from-process-env.js +183 -0
- package/dist/child/index.d.ts +9 -0
- package/dist/child/index.js +9 -0
- package/dist/child/outbound-mail-bridge.d.ts +36 -0
- package/dist/child/outbound-mail-bridge.js +143 -0
- package/dist/child/proxy-repo-store.d.ts +27 -0
- package/dist/child/proxy-repo-store.js +200 -0
- package/dist/child/run-child.d.ts +320 -0
- package/dist/child/run-child.js +900 -0
- package/dist/child/self-discovery.d.ts +29 -0
- package/dist/child/self-discovery.js +57 -0
- package/dist/child/substrate-write-bridge.d.ts +72 -0
- package/dist/child/substrate-write-bridge.js +188 -0
- package/dist/child/supervisor-backed-transport.d.ts +10 -0
- package/dist/child/supervisor-backed-transport.js +113 -0
- package/dist/child/warm-agent-cache.d.ts +78 -0
- package/dist/child/warm-agent-cache.js +112 -0
- package/dist/drain-controller.d.ts +37 -0
- package/dist/drain-controller.js +46 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/ipc/control-channel.d.ts +336 -0
- package/dist/ipc/control-channel.js +532 -0
- package/dist/ipc/crypto.d.ts +46 -0
- package/dist/ipc/crypto.js +126 -0
- package/dist/ipc/envelope.d.ts +53 -0
- package/dist/ipc/envelope.js +88 -0
- package/dist/ipc/event-channel.d.ts +677 -0
- package/dist/ipc/event-channel.js +278 -0
- package/dist/ipc/index.d.ts +4 -0
- package/dist/ipc/index.js +143 -0
- package/dist/mail-bus/hub-transport-adapter.d.ts +30 -0
- package/dist/mail-bus/hub-transport-adapter.js +76 -0
- package/dist/mail-bus/index.d.ts +1 -0
- package/dist/mail-bus/index.js +1 -0
- package/dist/seams/index.d.ts +3 -0
- package/dist/seams/index.js +3 -0
- package/dist/seams/scheduler-adapter.d.ts +3 -0
- package/dist/seams/scheduler-adapter.js +24 -0
- package/dist/seams/scheduler.d.ts +94 -0
- package/dist/seams/scheduler.js +397 -0
- package/dist/seams/signal-channel.d.ts +74 -0
- package/dist/seams/signal-channel.js +304 -0
- package/dist/supervisor/cancel-signing.d.ts +68 -0
- package/dist/supervisor/cancel-signing.js +144 -0
- package/dist/supervisor/child-termination.d.ts +51 -0
- package/dist/supervisor/child-termination.js +76 -0
- package/dist/supervisor/credentials.d.ts +101 -0
- package/dist/supervisor/credentials.js +153 -0
- package/dist/supervisor/dispatch-attribution.d.ts +37 -0
- package/dist/supervisor/dispatch-attribution.js +114 -0
- package/dist/supervisor/drain-timeout.d.ts +127 -0
- package/dist/supervisor/drain-timeout.js +231 -0
- package/dist/supervisor/index.d.ts +7 -0
- package/dist/supervisor/index.js +6 -0
- package/dist/supervisor/recycle.d.ts +212 -0
- package/dist/supervisor/recycle.js +440 -0
- package/dist/supervisor/run-event-compaction.d.ts +34 -0
- package/dist/supervisor/run-event-compaction.js +115 -0
- package/dist/supervisor/spawn-env.d.ts +39 -0
- package/dist/supervisor/spawn-env.js +36 -0
- package/dist/supervisor/supervisor.d.ts +202 -0
- package/dist/supervisor/supervisor.js +2244 -0
- package/dist/supervisor/terminal-broadcaster.d.ts +45 -0
- package/dist/supervisor/terminal-broadcaster.js +184 -0
- package/dist/supervisor/types.d.ts +542 -0
- package/dist/supervisor/types.js +10 -0
- 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
|
+
}
|