@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,126 @@
1
+ // IPC crypto primitives: raw Ed25519 sign/verify and HMAC-SHA256 sign/verify.
2
+ //
3
+ // The control channel uses Ed25519 because the supervisor is the only
4
+ // signer and the child must not be able to forge supervisor commands.
5
+ // The event channel uses HMAC-SHA256 because both sides hold the same
6
+ // 32-byte secret and the cost-per-frame of HMAC over per-frame Ed25519
7
+ // is what keeps the event stream affordable at InferenceEvent rates.
8
+ //
9
+ // Ed25519 sign/verify come from `@intx/crypto`, whose raw
10
+ // `signEd25519`/`verifyEd25519` primitives produce and check the bare
11
+ // 64-byte RFC 8032 signature without the PGP packet framing and ASCII
12
+ // armor the package's envelope helpers add — exactly the wire format
13
+ // this channel wants. HMAC-SHA256 uses the Web Crypto `subtle` API; its
14
+ // tag is verified by recomputing the tag with `subtle.sign` and
15
+ // comparing under an explicit constant-time XOR-accumulate rather than
16
+ // `subtle.verify`, because the Web Crypto spec does not guarantee
17
+ // `verify` runs in constant time and this channel keeps ownership of
18
+ // that property. The wire format here is the raw 64-byte Ed25519
19
+ // signature and the raw 32-byte HMAC-SHA256 tag concatenated with the
20
+ // canonical-JSON payload bytes. Anything fancier would just pay PGP
21
+ // overhead per frame.
22
+ import { signEd25519 as ed25519Sign, verifyEd25519 as ed25519Verify, } from "@intx/crypto";
23
+ import { hexEncode } from "@intx/types";
24
+ const ED25519_SIGNATURE_BYTES = 64;
25
+ const ED25519_KEY_BYTES = 32;
26
+ const HMAC_KEY_BYTES = 32;
27
+ const HMAC_TAG_BYTES = 32;
28
+ const CHANNEL_ID_BYTES = 16;
29
+ /**
30
+ * Mint a fresh control-channel HMAC key. Used by the supervisor at
31
+ * spawn time. The child never derives its own key; it receives the
32
+ * 32-byte secret in spawn-time env and never sees the Ed25519 private
33
+ * key the supervisor uses on the control channel.
34
+ */
35
+ export function generateHmacKey() {
36
+ return crypto.getRandomValues(new Uint8Array(HMAC_KEY_BYTES));
37
+ }
38
+ /**
39
+ * Mint a fresh channelId per the channel-identity contract: 16 bytes
40
+ * from `crypto.getRandomValues`, hex-encoded. The supervisor mints one at
41
+ * every spawn and every recycle, passes it to the child in spawn-time
42
+ * env, and rotates it on the next respawn. The hex encoding keeps the
43
+ * value safe to log and round-trips cleanly through JSON.
44
+ */
45
+ export function generateChannelId() {
46
+ return hexEncode(crypto.getRandomValues(new Uint8Array(CHANNEL_ID_BYTES)));
47
+ }
48
+ /**
49
+ * Sign the canonicalized envelope bytes with the supervisor's
50
+ * Ed25519 private key. Caller is responsible for canonicalization;
51
+ * this primitive does not see the structured envelope.
52
+ *
53
+ * The private-key bytes are the 32-byte Ed25519 seed. The raw signing
54
+ * primitive lives in `@intx/crypto`; this module wraps it with the
55
+ * channel's fixed-length validation.
56
+ */
57
+ export async function signEd25519(bytes, privateKeySeed) {
58
+ if (privateKeySeed.length !== ED25519_KEY_BYTES) {
59
+ throw new Error(`IPC Ed25519 private key seed must be ${ED25519_KEY_BYTES} bytes, got ${privateKeySeed.length}`);
60
+ }
61
+ return ed25519Sign(privateKeySeed, bytes);
62
+ }
63
+ export async function verifyEd25519(bytes, signature, publicKey) {
64
+ if (signature.length !== ED25519_SIGNATURE_BYTES) {
65
+ throw new Error(`IPC Ed25519 signature must be ${ED25519_SIGNATURE_BYTES} bytes, got ${signature.length}`);
66
+ }
67
+ if (publicKey.length !== ED25519_KEY_BYTES) {
68
+ throw new Error(`IPC Ed25519 public key must be ${ED25519_KEY_BYTES} bytes, got ${publicKey.length}`);
69
+ }
70
+ return ed25519Verify(bytes, signature, publicKey);
71
+ }
72
+ /**
73
+ * Produce the 32-byte HMAC-SHA256 tag for the given canonicalized
74
+ * envelope bytes under the shared key. Same primitive on both sides
75
+ * of the event channel.
76
+ */
77
+ export async function signHmac(bytes, key) {
78
+ if (key.length !== HMAC_KEY_BYTES) {
79
+ throw new Error(`IPC HMAC key must be ${HMAC_KEY_BYTES} bytes, got ${key.length}`);
80
+ }
81
+ const cryptoKey = await crypto.subtle.importKey("raw",
82
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ArrayBuffer-backed at the call site; Web Crypto's BufferSource type rejects Uint8Array<ArrayBufferLike> under TS 5.9 (microsoft/TypeScript#62240)
83
+ key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
84
+ const tag = await crypto.subtle.sign("HMAC", cryptoKey,
85
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ArrayBuffer-backed at the call site; Web Crypto's BufferSource type rejects Uint8Array<ArrayBufferLike> under TS 5.9 (microsoft/TypeScript#62240)
86
+ bytes);
87
+ return new Uint8Array(tag);
88
+ }
89
+ /**
90
+ * Constant-time byte comparison. A non-constant comparison would leak
91
+ * the position of the first mismatched byte through a timing side
92
+ * channel. The XOR accumulate is branch-free over the byte range; the
93
+ * only early return is on a length mismatch, which is not secret-
94
+ * dependent.
95
+ */
96
+ function constantTimeEqual(a, b) {
97
+ if (a.length !== b.length) {
98
+ return false;
99
+ }
100
+ let acc = 0;
101
+ for (let i = 0; i < a.length; i++) {
102
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- i is bounds-checked by the loop guard and a.length === b.length above
103
+ acc |= a[i] ^ b[i];
104
+ }
105
+ return acc === 0;
106
+ }
107
+ /**
108
+ * Verify an HMAC tag by recomputing it and comparing in constant time.
109
+ * Deliberately avoids `subtle.verify`, whose constant-time behavior the
110
+ * Web Crypto spec does not guarantee; this channel owns that property
111
+ * via `constantTimeEqual`.
112
+ */
113
+ export async function verifyHmac(bytes, tag, key) {
114
+ if (tag.length !== HMAC_TAG_BYTES) {
115
+ throw new Error(`IPC HMAC tag must be ${HMAC_TAG_BYTES} bytes, got ${tag.length}`);
116
+ }
117
+ const expected = await signHmac(bytes, key);
118
+ return constantTimeEqual(expected, tag);
119
+ }
120
+ export const IPC_CRYPTO = Object.freeze({
121
+ ED25519_SIGNATURE_BYTES,
122
+ ED25519_KEY_BYTES,
123
+ HMAC_KEY_BYTES,
124
+ HMAC_TAG_BYTES,
125
+ CHANNEL_ID_BYTES,
126
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Validator for the inner envelope shape. `payload` is `unknown` here
3
+ * because each channel narrows it further via its own typed schema
4
+ * (control payload union vs. InferenceEvent forwarded over event).
5
+ */
6
+ export declare const FrameEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
7
+ seq: number;
8
+ channelId: string;
9
+ payload: unknown;
10
+ }, {}>;
11
+ export type FrameEnvelope = typeof FrameEnvelope.infer;
12
+ /**
13
+ * Validator for the signed envelope wire shape. `sig` carries the
14
+ * hex-encoded Ed25519 signature (128 hex chars / 64 bytes).
15
+ */
16
+ export declare const SignedEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
17
+ envelope: {
18
+ seq: number;
19
+ channelId: string;
20
+ payload: unknown;
21
+ };
22
+ sig: string;
23
+ }, {}>;
24
+ export type SignedEnvelope = typeof SignedEnvelope.infer;
25
+ /**
26
+ * Validator for the MACed envelope wire shape. `mac` carries the
27
+ * hex-encoded HMAC-SHA256 tag (64 hex chars / 32 bytes).
28
+ */
29
+ export declare const MacedEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
30
+ envelope: {
31
+ seq: number;
32
+ channelId: string;
33
+ payload: unknown;
34
+ };
35
+ mac: string;
36
+ }, {}>;
37
+ export type MacedEnvelope = typeof MacedEnvelope.infer;
38
+ /**
39
+ * Produce the canonical byte serialization of an envelope. The
40
+ * sender signs these bytes; the receiver verifies against these
41
+ * bytes. Both sides reach the same bytestring deterministically
42
+ * because the JSON serialization runs in insertion order over a
43
+ * fixed-shape object.
44
+ */
45
+ export declare function encodeEnvelope(envelope: FrameEnvelope): Uint8Array;
46
+ /**
47
+ * Parse a canonical envelope byte serialization back into the
48
+ * structured shape. Used on the receiver side after the per-frame
49
+ * MAC/signature check passes -- a structural failure on a frame
50
+ * whose authentication tag matched is a programming bug at the
51
+ * sender, not a tampering signal.
52
+ */
53
+ export declare function decodeEnvelope(bytes: Uint8Array): FrameEnvelope;
@@ -0,0 +1,88 @@
1
+ // Frame envelope shared by both IPC channels.
2
+ //
3
+ // Every signed/HMACed frame carries `{ seq, channelId, payload }`
4
+ // inside the bytes the receiver authenticates. `seq` is a monotonic
5
+ // u64 counter the sender maintains per channel; `channelId` is the
6
+ // 16-byte hex identity the supervisor mints at every spawn and rotates
7
+ // at every recycle; `payload` is the channel-specific JSON value.
8
+ //
9
+ // Wire shape on disk for the control channel: one NDJSON line per
10
+ // frame, each line `{ envelope: <canonical-json-of-envelope>, sig:
11
+ // <hex Ed25519 signature> }`. For the event channel: a 4-byte big-
12
+ // endian length prefix, then `{ envelope, mac }` JSON in the same
13
+ // shape with `mac` carrying the hex HMAC tag. Both wires sign the
14
+ // canonical-JSON serialization of the envelope as a single bytestring.
15
+ // The on-wire byte representation of the envelope is exactly what the
16
+ // signer signs and the verifier verifies.
17
+ //
18
+ // Canonical JSON: keys appear in fixed insertion order (seq,
19
+ // channelId, payload). The payload's internal structure is preserved
20
+ // as the sender produced it; no recursive canonicalization is
21
+ // performed because the verifier never compares two structurally
22
+ // different serializations of the same logical value. Senders and
23
+ // receivers see the same bytes by construction (sender computes the
24
+ // signature over the exact serialization it then transmits; receiver
25
+ // verifies the signature over the exact bytes it received).
26
+ import { type } from "arktype";
27
+ /**
28
+ * Validator for the inner envelope shape. `payload` is `unknown` here
29
+ * because each channel narrows it further via its own typed schema
30
+ * (control payload union vs. InferenceEvent forwarded over event).
31
+ */
32
+ export const FrameEnvelope = type({
33
+ seq: "number",
34
+ channelId: "string",
35
+ payload: "unknown",
36
+ });
37
+ /**
38
+ * Validator for the signed envelope wire shape. `sig` carries the
39
+ * hex-encoded Ed25519 signature (128 hex chars / 64 bytes).
40
+ */
41
+ export const SignedEnvelope = type({
42
+ envelope: FrameEnvelope,
43
+ sig: "string",
44
+ });
45
+ /**
46
+ * Validator for the MACed envelope wire shape. `mac` carries the
47
+ * hex-encoded HMAC-SHA256 tag (64 hex chars / 32 bytes).
48
+ */
49
+ export const MacedEnvelope = type({
50
+ envelope: FrameEnvelope,
51
+ mac: "string",
52
+ });
53
+ /**
54
+ * Produce the canonical byte serialization of an envelope. The
55
+ * sender signs these bytes; the receiver verifies against these
56
+ * bytes. Both sides reach the same bytestring deterministically
57
+ * because the JSON serialization runs in insertion order over a
58
+ * fixed-shape object.
59
+ */
60
+ export function encodeEnvelope(envelope) {
61
+ const ordered = {
62
+ seq: envelope.seq,
63
+ channelId: envelope.channelId,
64
+ payload: envelope.payload,
65
+ };
66
+ return new TextEncoder().encode(JSON.stringify(ordered));
67
+ }
68
+ /**
69
+ * Parse a canonical envelope byte serialization back into the
70
+ * structured shape. Used on the receiver side after the per-frame
71
+ * MAC/signature check passes -- a structural failure on a frame
72
+ * whose authentication tag matched is a programming bug at the
73
+ * sender, not a tampering signal.
74
+ */
75
+ export function decodeEnvelope(bytes) {
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
79
+ }
80
+ catch (cause) {
81
+ throw new Error("IPC envelope bytes are not valid JSON", { cause });
82
+ }
83
+ const validated = FrameEnvelope(parsed);
84
+ if (validated instanceof type.errors) {
85
+ throw new Error(`IPC envelope failed validation: ${validated.summary}`);
86
+ }
87
+ return validated;
88
+ }