@intx/hub-sessions 0.1.2 → 0.3.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 (113) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +82 -1
  3. package/dist/agent-repo.d.ts +93 -0
  4. package/dist/agent-repo.js +109 -0
  5. package/dist/agent-state-kind.d.ts +12 -0
  6. package/dist/agent-state-kind.js +189 -0
  7. package/dist/asset-service.d.ts +104 -0
  8. package/dist/asset-service.js +267 -0
  9. package/dist/committed-source-tree.d.ts +10 -0
  10. package/dist/committed-source-tree.js +35 -0
  11. package/dist/credential-push.d.ts +33 -0
  12. package/dist/credential-push.js +109 -0
  13. package/dist/event-collector-registry.d.ts +20 -0
  14. package/dist/event-collector-registry.js +115 -0
  15. package/dist/event-collector.d.ts +39 -0
  16. package/dist/event-collector.js +365 -0
  17. package/dist/hub-session-lookups.d.ts +135 -0
  18. package/dist/hub-session-lookups.js +663 -0
  19. package/dist/hub-session-orchestrator.d.ts +25 -0
  20. package/dist/hub-session-orchestrator.js +87 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.js +24 -0
  23. package/dist/package-registry-kind.d.ts +70 -0
  24. package/dist/package-registry-kind.js +260 -0
  25. package/dist/repo-store/index.d.ts +4 -0
  26. package/dist/repo-store/index.js +3 -0
  27. package/dist/repo-store/store.d.ts +41 -0
  28. package/dist/repo-store/store.js +1829 -0
  29. package/dist/repo-store/subscribe-kind.d.ts +56 -0
  30. package/dist/repo-store/subscribe-kind.js +144 -0
  31. package/dist/repo-store/types.d.ts +571 -0
  32. package/dist/repo-store/types.js +42 -0
  33. package/dist/session-service.d.ts +416 -0
  34. package/dist/session-service.js +1191 -0
  35. package/dist/sidecar-allocation/contracts.d.ts +78 -0
  36. package/dist/sidecar-allocation/contracts.js +21 -0
  37. package/dist/sidecar-allocation/index.d.ts +4 -0
  38. package/dist/sidecar-allocation/index.js +3 -0
  39. package/dist/sidecar-allocation/placement-policy.d.ts +11 -0
  40. package/dist/sidecar-allocation/placement-policy.js +21 -0
  41. package/dist/sidecar-allocation/plugin-registry.d.ts +11 -0
  42. package/dist/sidecar-allocation/plugin-registry.js +37 -0
  43. package/dist/sidecar-allocation/reconciler.d.ts +42 -0
  44. package/dist/sidecar-allocation/reconciler.js +431 -0
  45. package/dist/skill-kind.d.ts +41 -0
  46. package/dist/skill-kind.js +292 -0
  47. package/dist/substrate.d.ts +8 -0
  48. package/dist/substrate.js +21 -0
  49. package/dist/workflow-allocation-service.d.ts +58 -0
  50. package/dist/workflow-allocation-service.js +239 -0
  51. package/dist/workflow-closure-resolution.d.ts +106 -0
  52. package/dist/workflow-closure-resolution.js +123 -0
  53. package/dist/workflow-definition-ensure.d.ts +24 -0
  54. package/dist/workflow-definition-ensure.js +75 -0
  55. package/dist/workflow-dispatch-service.d.ts +40 -0
  56. package/dist/workflow-dispatch-service.js +146 -0
  57. package/dist/workflow-dispatch-settlement.d.ts +29 -0
  58. package/dist/workflow-dispatch-settlement.js +140 -0
  59. package/dist/workflow-kind.d.ts +37 -0
  60. package/dist/workflow-kind.js +310 -0
  61. package/dist/workflow-probe-gate.d.ts +214 -0
  62. package/dist/workflow-probe-gate.js +207 -0
  63. package/dist/workflow-run-event-log.d.ts +21 -0
  64. package/dist/workflow-run-event-log.js +51 -0
  65. package/dist/workflow-run-kind.d.ts +440 -0
  66. package/dist/workflow-run-kind.js +2916 -0
  67. package/dist/workflow-run-reader.d.ts +47 -0
  68. package/dist/workflow-run-reader.js +153 -0
  69. package/dist/workflow-run-restore.d.ts +15 -0
  70. package/dist/workflow-run-restore.js +26 -0
  71. package/dist/workflow-source-closure.d.ts +35 -0
  72. package/dist/workflow-source-closure.js +342 -0
  73. package/dist/ws/index.d.ts +3 -0
  74. package/dist/ws/index.js +3 -0
  75. package/dist/ws/sidecar-events.d.ts +222 -0
  76. package/dist/ws/sidecar-events.js +72 -0
  77. package/dist/ws/sidecar-handler.d.ts +305 -0
  78. package/dist/ws/sidecar-handler.js +2537 -0
  79. package/dist/ws/sidecar-token-authenticator.d.ts +17 -0
  80. package/dist/ws/sidecar-token-authenticator.js +81 -0
  81. package/package.json +35 -12
  82. package/src/agent-repo.test.ts +0 -310
  83. package/src/agent-repo.ts +0 -165
  84. package/src/agent-state-kind.test.ts +0 -247
  85. package/src/agent-state-kind.ts +0 -204
  86. package/src/asset-service.test.ts +0 -540
  87. package/src/asset-service.ts +0 -378
  88. package/src/available-skills-stanza.test.ts +0 -87
  89. package/src/available-skills-stanza.ts +0 -47
  90. package/src/credential-push.ts +0 -65
  91. package/src/event-collector-registry.test.ts +0 -73
  92. package/src/event-collector-registry.ts +0 -171
  93. package/src/event-collector.test.ts +0 -1387
  94. package/src/event-collector.ts +0 -424
  95. package/src/hub-session-lookups.ts +0 -206
  96. package/src/hub-session-orchestrator.test.ts +0 -510
  97. package/src/hub-session-orchestrator.ts +0 -213
  98. package/src/index.ts +0 -78
  99. package/src/repo-store/index.ts +0 -15
  100. package/src/repo-store/store.test.ts +0 -1169
  101. package/src/repo-store/store.ts +0 -428
  102. package/src/repo-store/types.ts +0 -253
  103. package/src/session-service.test.ts +0 -895
  104. package/src/session-service.ts +0 -464
  105. package/src/skill-kind.test.ts +0 -599
  106. package/src/skill-kind.ts +0 -350
  107. package/src/ws/index.ts +0 -18
  108. package/src/ws/sidecar-events.test.ts +0 -96
  109. package/src/ws/sidecar-events.ts +0 -231
  110. package/src/ws/sidecar-handler.test.ts +0 -2217
  111. package/src/ws/sidecar-handler.ts +0 -1574
  112. package/tsconfig.json +0 -4
  113. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,440 @@
1
+ import { type AuthorizeFn, type CommittedReads, type KindHandler, type Principal, type RepoId, type RepoStore } from "./repo-store/index.js";
2
+ export type WorkflowRunHubPrincipal = {
3
+ readonly kind: "hub";
4
+ };
5
+ export type WorkflowRunSidecarPrincipal = {
6
+ readonly kind: "sidecar";
7
+ readonly agentId: string;
8
+ };
9
+ export type WorkflowRunWorkflowProcessPrincipal = {
10
+ readonly kind: "workflow-process";
11
+ readonly anchorRunId: string;
12
+ readonly runId?: string;
13
+ };
14
+ export type WorkflowRunSupervisorPrincipal = {
15
+ readonly kind: "supervisor";
16
+ readonly anchorRunId: string;
17
+ };
18
+ export type WorkflowRunPrincipal = WorkflowRunHubPrincipal | WorkflowRunSidecarPrincipal | WorkflowRunWorkflowProcessPrincipal | WorkflowRunSupervisorPrincipal;
19
+ export declare const WORKFLOW_RUN_GITIGNORE_PATH = ".gitignore";
20
+ export declare const WORKFLOW_RUN_RUNS_PREFIX = "runs";
21
+ export declare const WORKFLOW_RUN_EVENTS_DIR = "events";
22
+ export declare const WORKFLOW_RUN_BLOBS_DIR = "blobs";
23
+ export declare const WORKFLOW_RUN_GRANTS_FILE = "grants.json";
24
+ export declare const WORKFLOW_RUN_ADDRESSES_PREFIX = "addresses";
25
+ export declare const WORKFLOW_RUN_CONTROL_PREFIX = "control";
26
+ export declare const WORKFLOW_RUN_INBOX_DIR = "inbox";
27
+ export declare const WORKFLOW_RUN_PROCESSING_DIR = "processing";
28
+ export declare const WORKFLOW_RUN_CONSUMED_DIR = "consumed";
29
+ /**
30
+ * Filename of the per-address retention watermark blob, a direct child
31
+ * of `addresses/<urlEncoded>/` (a file, not a directory). Carries the
32
+ * monotonic `receivedAt`-horizon below which consumed entries may be
33
+ * pruned and at-or-below which inbound enqueues are refused as stale.
34
+ */
35
+ export declare const WORKFLOW_RUN_WATERMARK_FILE = "watermark.json";
36
+ /**
37
+ * Default retention horizon for the consumed dedup index, in
38
+ * milliseconds. The boot edge resolves the operator's
39
+ * `CONSUMED_RETENTION_MS` config to a concrete value and threads it
40
+ * into `markConsumed`; this default applies only when no operator
41
+ * value is supplied. 24 hours is the conservative default: long enough
42
+ * that a duplicate from a retrying upstream within a day is still
43
+ * deduped by a retained consumed entry, short enough that `consumed/`
44
+ * reaches a bounded steady state of one day's message volume.
45
+ *
46
+ * INVARIANT (operator-owned): the horizon must be >= the longest window in
47
+ * which the same `messageId` could legitimately be re-submitted and still must
48
+ * be caught as a duplicate. The hub now redelivers un-acked inbound mail
49
+ * (connected-window retry and reconnect-redelivery), so an at-least-once
50
+ * internal source DOES exist -- but the dedup guarantee against it does not
51
+ * rest on window arithmetic. It rests on a STRUCTURAL fact: `enqueueInbox` is
52
+ * only ever called with a freshly stamped `receivedAt` (a redelivery
53
+ * re-enters `onMailMessage` and re-stamps `Date.now()`, never carrying the
54
+ * original), and the watermark only ever advances to at most
55
+ * `consumedAt - retentionHorizonMs`, which is <= now, so a fresh `receivedAt`
56
+ * always sits a full horizon above the watermark and can never be stale-
57
+ * refused. A redelivery instead hits the `consumed/`/`processing/`/`inbox/`
58
+ * dedup index and is deduped there. The sole path that carries an original
59
+ * (old) `receivedAt` back into the queue is `replayProcessingToInbox`, which
60
+ * writes straight to `inbox/` and bypasses the stale gate entirely. So
61
+ * `claim_check_stale_enqueue` is unreachable via redelivery today. If any
62
+ * redelivery source is ever changed to carry the ORIGINAL `receivedAt` into
63
+ * `enqueueInbox`, stale becomes reachable, the horizon must then be >= that
64
+ * source's maximum redelivery window, and `StaleInboxEnqueueError`'s
65
+ * withhold-not-ack handling becomes load-bearing; a breach surfaces LOUDLY (an
66
+ * old-`receivedAt` re-submission is refused at enqueue) rather than as silent
67
+ * double-processing.
68
+ */
69
+ export declare const DEFAULT_CONSUMED_RETENTION_MS: number;
70
+ /**
71
+ * Per-agent durable conversation-state subtree (design §3c). A
72
+ * long-lived single-step agent's multi-turn conversation context is
73
+ * committed under `agent-state/<agentKey>/...` so it survives child
74
+ * respawn: on respawn the rebuilt warm agent reads its prior
75
+ * conversation back from here before the resumed run replays.
76
+ *
77
+ * Unlike `runs/` (append-only events, immutable blobs) this subtree is
78
+ * MUTABLE: each run boundary overwrites the agent's conversation
79
+ * snapshot with the latest turns. It is therefore exempt from the
80
+ * append-only / deletion-direction walks `runs/` is subject to; the
81
+ * only push-time constraint is segment shape (a single round-trip-safe
82
+ * `<agentKey>` directory layer below the prefix).
83
+ */
84
+ export declare const WORKFLOW_RUN_AGENT_STATE_PREFIX = "agent-state";
85
+ /**
86
+ * Parse the seq from a per-event log filename `<seq>.json` under
87
+ * `runs/<runId>/events/`. Returns the non-negative integer seq, or
88
+ * `null` when the name is not a legal per-event filename. This is the
89
+ * one place the filename shape is defined; every reader of the event log
90
+ * narrows names through it rather than re-encoding the regex. Callers
91
+ * decide what an illegal name means -- a foreign entry to skip, or a
92
+ * substrate-invariant violation to surface -- since `validatePush` is
93
+ * the authority that keeps illegal names from landing in the first place.
94
+ */
95
+ export declare function parseEventSeq(filename: string): number | null;
96
+ /**
97
+ * Narrow a per-event filename to its seq, throwing when it is illegal.
98
+ * A reader that enumerates the committed event log to act on its entries
99
+ * uses this rather than `parseEventSeq`: `validatePush` is the authority
100
+ * that keeps an illegal name from ever landing under
101
+ * `runs/<runId>/events/`, so a name that reaches a reader is corruption,
102
+ * and silently skipping it would drop an event from processing. `context`
103
+ * is the repo-root-relative blob path, surfaced in the error so the
104
+ * offending entry is identifiable.
105
+ */
106
+ export declare function requireEventSeq(filename: string, context: string): number;
107
+ /**
108
+ * JSON envelope carried by inbox and processing entries. Keys:
109
+ * - `messageId`: dedup key for the inbound message.
110
+ * - `receivedAt`: epoch-ms timestamp the reactor accepted the
111
+ * message; sortable FIFO key prefix.
112
+ * - `address`: decoded canonical address (not URL-encoded).
113
+ * - `mailAuditRef`: pointer to the raw mail bytes in the mail-audit
114
+ * store. For the in-process single-agent path a separate
115
+ * `MailAuditStore` holds the authoritative bytes and this ref joins
116
+ * onto it.
117
+ * - `rawMessage`: base64 of the inbound mail's raw MIME bytes,
118
+ * inlined so the workflow-process child can read its step input by
119
+ * messageId at `trigger.fired` time. The supervisor is the sole
120
+ * mail owner under the unified-execution host (§3a); it has no
121
+ * separate durable byte store the child can read, so the bytes ride
122
+ * the claim-check envelope itself. Present whenever the supervisor
123
+ * enqueued the entry; omitted by callers that only stamp the audit
124
+ * ref. The bytes survive the inbox→processing transition verbatim
125
+ * (the dequeue copies the entry bytes), so a `trigger.fired` for a
126
+ * processing entry can always recover the input.
127
+ */
128
+ declare const ClaimCheckEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
129
+ messageId: string;
130
+ receivedAt: number;
131
+ address: string;
132
+ mailAuditRef: {
133
+ store: string;
134
+ path: string;
135
+ };
136
+ rawMessage?: string;
137
+ }, {}>;
138
+ /**
139
+ * JSON envelope carried by consumed entries. The consumed entry is the
140
+ * canonical dedup index keyed by messageId; the envelope preserves
141
+ * the originating receivedAt for audit and carries the runId that
142
+ * consumed the message.
143
+ */
144
+ declare const ConsumedEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
145
+ messageId: string;
146
+ receivedAt: number;
147
+ address: string;
148
+ runId: string;
149
+ consumedAt: number;
150
+ mailAuditRef: {
151
+ store: string;
152
+ path: string;
153
+ };
154
+ rejection?: {
155
+ code: string;
156
+ message: string;
157
+ };
158
+ }, {}>;
159
+ /**
160
+ * JSON envelope carried by the per-address `watermark.json` blob. The
161
+ * `watermark` is a `receivedAt` horizon (epoch ms): the oldest
162
+ * `receivedAt` a consumed entry may still retain. It only ever
163
+ * advances. A retention prune drops consumed entries strictly below
164
+ * it; `enqueueInbox` refuses any inbound strictly below it.
165
+ */
166
+ declare const WatermarkEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
167
+ watermark: number;
168
+ }, {}>;
169
+ export type ClaimCheckEnvelope = typeof ClaimCheckEnvelope.infer;
170
+ export type ConsumedEnvelope = typeof ConsumedEnvelope.infer;
171
+ export type WatermarkEnvelope = typeof WatermarkEnvelope.infer;
172
+ export declare const workflowRunKindHandler: KindHandler;
173
+ export declare const workflowRunAuthorize: AuthorizeFn;
174
+ export type EnqueueInboxArgs = {
175
+ address: string;
176
+ messageId: string;
177
+ receivedAt: number;
178
+ mailAuditRef: {
179
+ store: string;
180
+ path: string;
181
+ };
182
+ /**
183
+ * Base64 of the inbound mail's raw MIME bytes. Inlined on the
184
+ * claim-check envelope so the workflow-process child can recover its
185
+ * step input by messageId at `trigger.fired` time (§3a -- the
186
+ * supervisor is the sole mail owner and has no separate durable byte
187
+ * store the child reads). Omit to stamp only the audit ref.
188
+ */
189
+ rawMessage?: string;
190
+ };
191
+ export type EnqueueInboxResult = {
192
+ commitSha: string;
193
+ inboxKey: string;
194
+ envelope: ClaimCheckEnvelope;
195
+ };
196
+ /**
197
+ * Which already-present state an `enqueueInbox` call found the messageId
198
+ * in. Every value is POSITIVE evidence the message's bytes are durably on
199
+ * disk (inbox/processing) or were already consumed -- so a caller gating a
200
+ * receipt on the enqueue may safely acknowledge on any of them.
201
+ */
202
+ export type EnqueueAlreadyPresentReason = "duplicate" | "already_inbox" | "processing" | "consumed";
203
+ /**
204
+ * Outcome of an `enqueueInbox` call. Modeled as a value (not an exception)
205
+ * precisely because the return/throw boundary is the ack/withhold boundary
206
+ * for a caller gating a durable-receipt ack: a returned outcome is safe to
207
+ * acknowledge (the bytes are on disk -- freshly written or already present),
208
+ * a throw is not (the write could not complete or its disposition cannot be
209
+ * decided). `enqueued` is the only outcome that added a new inbox entry, so
210
+ * it is the only one a dispatch-driving caller wakes its loop on.
211
+ */
212
+ export type EnqueueInboxOutcome = ({
213
+ outcome: "enqueued";
214
+ } & EnqueueInboxResult) | {
215
+ outcome: "already-present";
216
+ reason: EnqueueAlreadyPresentReason;
217
+ };
218
+ /**
219
+ * Thrown by `enqueueInbox` when the inbound's `receivedAt` is strictly below
220
+ * the address's retention watermark. This is refusal under UNCERTAINTY, not
221
+ * proof of prior receipt: the consumed dedup entry that would rule out a
222
+ * duplicate may have been pruned, so the substrate can no longer tell a
223
+ * duplicate from a never-processed message and refuses rather than risk
224
+ * reprocessing. A caller gating a durable-receipt ack MUST NOT acknowledge on
225
+ * this -- acking an "I cannot tell" would terminally drop a message that was
226
+ * never written. It is its own type (not a generic Error) so that a caller,
227
+ * and monitoring, can surface it as a distinct loud signal rather than
228
+ * blending it into ordinary I/O-failure noise.
229
+ *
230
+ * Structurally unreachable on the mail-inbound path today: `enqueueInbox` is
231
+ * only ever called with a freshly stamped `receivedAt` (a redelivery
232
+ * re-stamps `Date.now()` rather than carrying the original), and the watermark
233
+ * only ever advances to at most `consumedAt - retentionHorizonMs <= now`, so a
234
+ * fresh `receivedAt` sits a full horizon above it. The sole path that carries
235
+ * an original (old) `receivedAt` back into the queue is
236
+ * `replayProcessingToInbox`, which writes straight to `inbox/` and bypasses
237
+ * this gate entirely. If any redelivery source is ever changed to carry the
238
+ * original `receivedAt` into `enqueueInbox`, this becomes reachable and its
239
+ * withhold-not-ack handling becomes load-bearing.
240
+ */
241
+ export declare class StaleInboxEnqueueError extends Error {
242
+ constructor(message: string);
243
+ }
244
+ /**
245
+ * Append a new inbox entry for `address`. The merge callback reads
246
+ * the address subtree under the per-repo lock, augments the inbox
247
+ * with the new entry, and returns the full set of address files. The
248
+ * substrate replaces the address subtree wholesale.
249
+ *
250
+ * Rejects if a same-messageId entry already exists in any queue
251
+ * state at the address — including a prior inbox entry at a
252
+ * different `receivedAt`. The caller is expected to consult the
253
+ * dedup index (consumed/) before calling, but enforcing the
254
+ * invariant here also catches the concurrent-enqueue race that the
255
+ * per-repo lock alone cannot surface.
256
+ */
257
+ export declare function enqueueInbox(store: RepoStore, principal: Principal, repoId: RepoId, args: EnqueueInboxArgs): Promise<EnqueueInboxOutcome>;
258
+ export type DequeueToProcessingResult = {
259
+ commitSha: string;
260
+ key: string;
261
+ envelope: ClaimCheckEnvelope;
262
+ } | null;
263
+ /**
264
+ * Move the FIFO-first inbox entry for `address` to processing.
265
+ * Returns `null` when the inbox is empty so the caller can
266
+ * distinguish "nothing to do" from "operation failed".
267
+ *
268
+ * FIFO is keyed on the parsed numeric `receivedAt` prefix of the
269
+ * inbox filename, with a lexicographic messageId tiebreak. The
270
+ * substrate does NOT rely on uniform digit widths — sorting raw
271
+ * filenames would put `"100-…"` ahead of `"99-…"` since `'1' < '9'`,
272
+ * which violates the FIFO invariant.
273
+ */
274
+ export declare function dequeueToProcessing(store: RepoStore, principal: Principal, repoId: RepoId, address: string): Promise<DequeueToProcessingResult>;
275
+ export type ReadProcessingEntryResult = {
276
+ envelope: ClaimCheckEnvelope;
277
+ } | null;
278
+ /**
279
+ * Read the processing-queue entry for `messageId` at `address` without
280
+ * mutating the tree. Returns the decoded claim-check envelope (carrying
281
+ * `mailAuditRef` and, when the enqueuer inlined them, the base64
282
+ * `rawMessage` bytes) or `null` when no processing entry exists for the
283
+ * messageId.
284
+ *
285
+ * This is the read half of mailbox ownership (§3a): the supervisor's
286
+ * dispatch loop moves an inbox entry to processing and forwards a
287
+ * `trigger.fired{messageId}` to the workflow-process child; the child
288
+ * calls this to recover the inbound message bytes that become its step
289
+ * input.
290
+ *
291
+ * The read is a flat working-tree read of
292
+ * `addresses/<seg>/processing/`. The substrate materializes each
293
+ * claim-check commit's touched paths into the repo's working tree (the
294
+ * delta write removes each deleted path and writes each put after
295
+ * validation passes), so a read issued after `dequeueToProcessing`
296
+ * committed -- which is exactly when the supervisor forwards
297
+ * `trigger.fired` -- observes the processing entry. Reading the working tree (rather than walking the
298
+ * committed git tree) matches the workflow-process child's sibling
299
+ * read of `runs/<runId>/events/`. Because the
300
+ * read issues no commit it cannot race the supervisor's `markConsumed`
301
+ * write; it returns a point-in-time snapshot of the directory.
302
+ */
303
+ export declare function readProcessingEntry(store: RepoStore, _principal: Principal, repoId: RepoId, address: string, messageId: string): Promise<ReadProcessingEntryResult>;
304
+ export type MarkConsumedArgs = {
305
+ address: string;
306
+ messageId: string;
307
+ runId: string;
308
+ consumedAt: number;
309
+ /**
310
+ * Present when the supervisor deliberately refused the message instead of
311
+ * delivering it to the run. The consumed entry remains the durable dedup
312
+ * record, while Hub projection uses this detail to fail (rather than settle)
313
+ * an exclusive-dispatch row.
314
+ */
315
+ rejection?: {
316
+ code: string;
317
+ message: string;
318
+ };
319
+ /**
320
+ * Retention horizon for the consumed dedup index, in milliseconds.
321
+ * The commit advances the per-address watermark to
322
+ * `consumedAt - retentionHorizonMs` (never backward, never past the
323
+ * entry being written) and prunes consumed entries below it. The
324
+ * boot edge resolves the operator's `CONSUMED_RETENTION_MS` config
325
+ * to a concrete value and threads it here. Omit to apply
326
+ * `DEFAULT_CONSUMED_RETENTION_MS` (24h).
327
+ */
328
+ retentionHorizonMs?: number;
329
+ };
330
+ export type MarkConsumedResult = {
331
+ commitSha: string;
332
+ envelope: ConsumedEnvelope;
333
+ /** Watermark the commit advanced to (epoch-ms `receivedAt` horizon). */
334
+ watermark: number;
335
+ /** messageIds whose consumed entries this commit pruned. */
336
+ prunedMessageIds: string[];
337
+ };
338
+ /**
339
+ * Atomically remove the processing entry for `messageId` at `address`,
340
+ * write the canonical `consumed/<messageId>.json` dedup index entry,
341
+ * advance the per-address retention watermark, and prune consumed
342
+ * entries the watermark has passed. The caller is expected to have
343
+ * called `dequeueToProcessing` for this messageId; calling
344
+ * `markConsumed` without a matching processing entry throws.
345
+ *
346
+ * The consumed envelope preserves the original `receivedAt` and
347
+ * `mailAuditRef` from the processing entry so the dedup index doubles
348
+ * as an audit record.
349
+ *
350
+ * Retention (the bounded-`consumed/` contract): the watermark advances
351
+ * to `max(priorWatermark, min(consumedAt - retentionHorizonMs,
352
+ * thisEntry.receivedAt))` -- monotonic, and never past the entry being
353
+ * written so the new entry is always retained. Every consumed entry
354
+ * whose `receivedAt` is strictly below the new watermark is dropped
355
+ * (the oldest age-ordered tail). `consumed/` therefore reaches a
356
+ * bounded steady state of roughly one horizon's worth of entries
357
+ * instead of growing one entry per message forever.
358
+ */
359
+ export declare function markConsumed(store: RepoStore, principal: Principal, repoId: RepoId, args: MarkConsumedArgs): Promise<MarkConsumedResult>;
360
+ export type ReplayProcessingToInboxResult = {
361
+ commitSha: string;
362
+ replayedKeys: string[];
363
+ };
364
+ /**
365
+ * Read the run event logs under `runs/` and return the set of
366
+ * `consumedMessageId`s belonging to NON-terminal runs -- the messages a
367
+ * live run still owns. The caller (the supervisor's spawn-time replay)
368
+ * feeds this into `replayProcessingToInbox`'s `ownedMessageIds` so a
369
+ * parked run's message is not re-admitted to inbox and dispatched a
370
+ * second time while the run is recovered by re-driving its durable log.
371
+ * Without this, the re-drive AND the re-triggered fresh run both re-park
372
+ * the same awaitSignal gate on the same runId, and the two concurrent
373
+ * runtime bodies race to a corrupt terminal.
374
+ *
375
+ * Reads the substrate's working tree via `getRepoDir`, mirroring the
376
+ * child's `discoverInFlightRuns`. The working tree tracks the run-event
377
+ * ref (`refs/heads/main`); the claim-check ref (`refs/heads/events`)
378
+ * cannot see it, which is why this lives at the caller rather than inside
379
+ * `replayProcessingToInbox`'s single-ref delta. A run whose log is sealed
380
+ * (combined `events.json`, only permitted for a terminated run) or
381
+ * carries a terminal event is excluded; an absent `runs/` directory
382
+ * yields an empty set.
383
+ */
384
+ export declare function readOwnedMessageIds(store: RepoStore, repoId: RepoId): Promise<Set<string>>;
385
+ export type WorkflowRunLifecycle = "absent" | "live" | "terminal";
386
+ /** Read one run's lifecycle from a committed workflow-run tree. */
387
+ export declare function readCommittedWorkflowRunLifecycle(reads: CommittedReads | null, runId: string): Promise<WorkflowRunLifecycle>;
388
+ /**
389
+ * Read the durable lifecycle of one run from the workflow-run working tree.
390
+ * `grants.json` alone is still an absent run: grants are staged before the
391
+ * first trigger, while the first event is the durable proof that the run was
392
+ * fired. A sealed event log is terminal by the kind handler's compaction
393
+ * invariant.
394
+ *
395
+ * The supervisor uses this when in-memory cohort membership is empty. That
396
+ * happens both for a genuinely new deployment and briefly during recovery,
397
+ * so treating both states as "fire" would start a second driver for a live
398
+ * log or reuse a terminal run.
399
+ */
400
+ export declare function readWorkflowRunLifecycle(store: RepoStore, repoId: RepoId, runId: string): Promise<WorkflowRunLifecycle>;
401
+ export type ReplayProcessingToInboxOpts = {
402
+ /**
403
+ * MessageIds whose run is still LIVE (non-terminal) and therefore owns
404
+ * its inbound message: recovery re-drives that run against the durable
405
+ * log, so re-admitting the message to `inbox/` would dispatch a SECOND
406
+ * run for it, colliding with the re-drive on the shared runId. Entries
407
+ * in this set are left in `processing/` untouched; the run's eventual
408
+ * `markConsumed` clears them. The caller computes this by reading the
409
+ * run event logs, which live on a DIFFERENT ref (`refs/heads/main`)
410
+ * than the claim-check subtree this operation commits to
411
+ * (`refs/heads/events`). Empty/absent means replay every processing
412
+ * entry (the pre-existing behaviour: recover all orphans).
413
+ */
414
+ ownedMessageIds?: ReadonlySet<string>;
415
+ };
416
+ /**
417
+ * Recovery path: move every processing entry at `address` back to
418
+ * inbox preserving the original `<receivedAt>-<messageId>` filename
419
+ * key so FIFO ordering survives a workflow-process crash. Returns
420
+ * the set of keys that were moved; when nothing was in processing
421
+ * the returned `replayedKeys` is empty (and the commit is a no-op
422
+ * rewrite of the same tree).
423
+ *
424
+ * The replay is atomic across all processing entries — a partial
425
+ * replay that left some entries in processing would corrupt the
426
+ * FIFO discipline (the next dequeue would pull the wrong entry).
427
+ *
428
+ * Watermark carve-out (load-bearing — do NOT "tighten" this): the
429
+ * replay deliberately does NOT apply the `receivedAt < watermark`
430
+ * stale-reject that `enqueueInbox` applies. A `processing/` entry was
431
+ * already dequeued past the dedup index, so re-admitting it to
432
+ * `inbox/` even when its `receivedAt` has fallen below an advanced
433
+ * watermark is correct — the message is a legitimately in-flight one
434
+ * recovered after a crash, not a fresh inbound that could be a
435
+ * duplicate. Applying the stale-reject here would silently LOSE that
436
+ * message. The watermark only ever gates fresh inbound at the enqueue
437
+ * boundary; the recovery replay is exempt by design.
438
+ */
439
+ export declare function replayProcessingToInbox(store: RepoStore, principal: Principal, repoId: RepoId, address: string, opts?: ReplayProcessingToInboxOpts): Promise<ReplayProcessingToInboxResult>;
440
+ export {};