@yielded/agent-storage-memory 0.1.0-beta.167 → 0.1.0-beta.169

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.
@@ -1,18 +1,24 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { Cause, Clock, DateTime, Duration, Effect, Exit, Fiber, Layer, Option, Ref, Schema, Stream } from "effect";
2
+ import { n as MemoryThreadStoreKernel, t as boundedWorkSelection } from "./WorkSelection-CmqTuws3.mjs";
3
+ import { Cause, Clock, Context, Crypto, DateTime, Effect, Exit, Fiber, Layer, MutableRef, Option, Ref, Result, Schema, Stream } from "effect";
3
4
  import { AttemptId, ReceiptId, SubmissionId } from "@yielded/agent/identifiers";
5
+ import { SettlementPublicationResult, SettlementPublisher, validateCanonicalSettlement, validateJoinedSettlement, validatePublication } from "@yielded/agent/settlement-publisher";
6
+ import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, FundingOwner, FundingOwnerRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReservedChildBudget, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WorkerLedgerState, WorkerStopCommand, approvalDecisionsCover, settlementFailureFromRecord, submissionSettlementRecordId, unknownResolutionKind, unknownResolutionMatchesResult, workerTerminalFromRecord } from "@yielded/agent/submission-ledger";
7
+ import { RebuiltSubmission, ThreadImportRejected, invalidThreadArchive, rebuildClosedSubmission } from "@yielded/agent/thread-import";
8
+ import { ThreadAbortFact, ThreadAdmission, ThreadApprovalFact, ThreadResolutionFact, ThreadStoreError, makeTransferFactCheck, transferPageFits } from "@yielded/agent/thread-store";
9
+ import { admissionWork } from "@yielded/agent/thread-work";
10
+ import { canonicalJson, utf8ByteLength } from "@yielded/agent/digest";
4
11
  import { InputMessage } from "@yielded/agent/messaging";
5
- import { PersistedJson, ProducerEpoch, WorkerAdmission } from "@yielded/agent/records";
6
- import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WorkerLedgerState, WorkerStopCommand, settlementFailureFromRecord, workerTerminalFromRecord } from "@yielded/agent/submission-ledger";
12
+ import { MAX_RUN_EVIDENCE_BYTES, MAX_RUN_EVIDENCE_RECORDS, PersistedJson, WorkerAdmission } from "@yielded/agent/records";
13
+ import { runIdForSubmission } from "@yielded/agent/run-journal";
7
14
  //#region src/MemorySubmissionLedger.ts
8
15
  var MemorySubmissionLedger_exports = /* @__PURE__ */ __exportAll({
9
16
  MemorySubmissionLedgerLive: () => MemorySubmissionLedgerLive,
10
17
  memorySubmissionLedgerLayer: () => memorySubmissionLedgerLayer
11
18
  });
12
- const MAX_SUBMISSIONS = 65536;
13
19
  /**
14
20
  * Lifecycle ordering used to advance-but-never-regress the operational state marker: a reclaimed
15
- * Attempt must not erase progress markers (input-applied, terminalizing) that an earlier Attempt
21
+ * Attempt must not erase progress markers (input-applied) that an earlier Attempt
16
22
  * already committed.
17
23
  */
18
24
  const STATE_RANK = {
@@ -20,12 +26,10 @@ const STATE_RANK = {
20
26
  ready: 1,
21
27
  joining: 2,
22
28
  joined: 3,
23
- running: 4,
24
29
  "input-applied": 5,
25
30
  suspended: 6,
26
31
  unknown: 7,
27
- terminalizing: 8,
28
- settled: 9
32
+ settled: 8
29
33
  };
30
34
  /**
31
35
  * States in which `claim` never grants the head: the lane is host-owned (`joining`/`joined`)
@@ -37,6 +41,25 @@ const BLOCKED_HEAD_STATES = /* @__PURE__ */ new Set([
37
41
  "joined",
38
42
  "suspended"
39
43
  ]);
44
+ const commandPosition = (entries, key) => {
45
+ let low = 0;
46
+ let high = entries.length;
47
+ while (low < high) {
48
+ const middle = Math.floor((low + high) / 2);
49
+ if (entries[middle].key < key) low = middle + 1;
50
+ else high = middle;
51
+ }
52
+ return low;
53
+ };
54
+ const retainCommand = (entries, submissionId, toolCallId, resolutionKey) => {
55
+ const key = String(entries.length + 1).padStart(16, "0");
56
+ entries.push({
57
+ key,
58
+ submissionId,
59
+ ...toolCallId === void 0 ? {} : { toolCallId },
60
+ ...resolutionKey === void 0 ? {} : { resolutionKey }
61
+ });
62
+ };
40
63
  const failure = (error) => ({
41
64
  _tag: "failure",
42
65
  error
@@ -53,15 +76,19 @@ const ledgerError = (operation, message, cause) => cause === void 0 ? LedgerErro
53
76
  message,
54
77
  cause
55
78
  });
56
- const validate = Effect.fn("MemorySubmissionLedger.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => ledgerError(operation, `Invalid ${operation} request`, error))));
79
+ const validate = (schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => ledgerError(operation, `Invalid ${operation} request`, error)));
57
80
  const decodeSubmissionId = Schema.decodeSync(SubmissionId);
58
81
  const decodeReceiptId = Schema.decodeSync(ReceiptId);
59
82
  const decodeAttemptId = Schema.decodeSync(AttemptId);
60
- const decodeOwnershipToken = Schema.decodeSync(OwnershipToken);
83
+ const MemoryIdentityNonce = Schema.String.check(Schema.isUUID(4));
61
84
  const decodeQueueSequence = Schema.decodeSync(QueueSequence);
62
- const decodeProducerEpoch = Schema.decodeSync(ProducerEpoch);
63
85
  const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
64
86
  const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
87
+ const admissionFitsTransfer = makeTransferFactCheck(ThreadAdmission);
88
+ const abortFitsTransfer = makeTransferFactCheck(ThreadAbortFact);
89
+ const approvalFitsTransfer = makeTransferFactCheck(ThreadApprovalFact);
90
+ const resolutionFitsTransfer = makeTransferFactCheck(ThreadResolutionFact);
91
+ const transferCapacityError = (operation) => ledgerError(operation, "Accepted fact exceeds its bounded transfer representation");
65
92
  const utc = (millis) => DateTime.toUtc(DateTime.makeUnsafe(millis));
66
93
  const admissionKey = (threadId, principal, idempotencyKey) => JSON.stringify([
67
94
  threadId,
@@ -103,90 +130,222 @@ const toReservationSnapshot = (row) => ChildBudgetReservationSnapshot.make({
103
130
  ...row.releaseBeganAtMillis === void 0 ? {} : { releaseBeganAt: utc(row.releaseBeganAtMillis) },
104
131
  ...row.releasedAtMillis === void 0 ? {} : { releasedAt: utc(row.releasedAtMillis) }
105
132
  });
133
+ const fromRebuilt = (rebuilt) => {
134
+ const admission = rebuilt.admission;
135
+ const threadId = admission.threadId;
136
+ const canonicalSettlement = rebuilt.settlement?.payload;
137
+ const settlement = canonicalSettlement?._tag === "SubmissionSettled" ? canonicalSettlement : void 0;
138
+ return {
139
+ row: {
140
+ submissionId: admission.submissionId,
141
+ threadId,
142
+ queueSequence: admission.queueSequence,
143
+ principal: admission.principal,
144
+ idempotencyKey: admission.idempotencyKey,
145
+ agentId: admission.agentId,
146
+ agentDigests: admission.agentDigests,
147
+ deploymentId: admission.deploymentId,
148
+ inputPayload: admission.inputPayload,
149
+ inputDigest: admission.inputDigest,
150
+ receiptId: admission.receiptId,
151
+ state: rebuilt.state,
152
+ settledOutcome: settlement?.outcome,
153
+ createdAtMillis: DateTime.toEpochMillis(admission.createdAt),
154
+ readyAtMillis: rebuilt.state === "ready" ? DateTime.toEpochMillis(admission.createdAt) : void 0,
155
+ parentLinkage: admission.parentLinkage,
156
+ ...admission.workerAdmission === void 0 ? {} : { workerAdmissionJson: Schema.encodeSync(Schema.fromJsonString(WorkerAdmission))(admission.workerAdmission) },
157
+ ...admission.messageAdmission === void 0 ? {} : { messageAdmissionJson: Schema.encodeSync(Schema.fromJsonString(InputMessage))(admission.messageAdmission) },
158
+ ...admission.admissionGroup === void 0 ? {} : { admissionGroup: admission.admissionGroup },
159
+ ...admission.admissionFence === void 0 ? {} : { admissionFence: admission.admissionFence }
160
+ },
161
+ inputApplied: rebuilt.inputApplied,
162
+ finalization: settlement === void 0 || rebuilt.settlement === void 0 || admission.settledAt === void 0 ? void 0 : {
163
+ settlementId: settlement.settlementId,
164
+ recordId: rebuilt.settlement.recordId,
165
+ finalizedAtMillis: DateTime.toEpochMillis(admission.settledAt)
166
+ },
167
+ abortIntent: rebuilt.abort,
168
+ joinedHostSubmissionId: rebuilt.joinedHostSubmissionId,
169
+ suspension: rebuilt.suspension === void 0 ? void 0 : {
170
+ reason: rebuilt.suspension.reason,
171
+ suspendedAtMillis: DateTime.toEpochMillis(rebuilt.suspension.suspendedAt)
172
+ },
173
+ unknownMark: rebuilt.unknownToolCallIds.length === 0 ? void 0 : {
174
+ reason: "Unresolved Tool execution recovered from the canonical log",
175
+ toolCallIds: rebuilt.unknownToolCallIds
176
+ },
177
+ approvalDecisions: new Map(rebuilt.approvals.map((intent) => [intent.toolCallId, intent])),
178
+ unknownResolutions: new Map(rebuilt.resolutions.map((intent) => [JSON.stringify([intent.toolCallId, unknownResolutionKind(intent.resolution)]), { intent }]))
179
+ };
180
+ };
106
181
  /** Linkage equality: both absent, or both present naming the same parent Tool Call. */
107
182
  const sameParentLinkage = (left, right) => left === void 0 ? right === void 0 : right !== void 0 && left.parentSubmissionId === right.parentSubmissionId && left.parentToolCallId === right.parentToolCallId;
108
- const laneEpoch = (state, threadId) => state.lanes.get(threadId)?.producerEpoch ?? 0;
109
- const ownershipLost = (state, stored) => OwnershipLost.make({
110
- submissionId: stored.row.submissionId,
111
- actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.threadId))
112
- });
113
- /** A retained row token cannot outlive a newer owner of the same Thread. */
114
- const ownsLane = (state, stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken && stored.ownership.producerEpoch === laneEpoch(state, stored.row.threadId);
183
+ const ownershipLost = (stored) => OwnershipLost.make({ submissionId: stored.row.submissionId });
115
184
  const withSubmission = (state, stored) => {
116
- const active = new Set(state.activeByThread.get(stored.row.threadId));
117
- if (stored.row.state === "settled") active.delete(stored.row.submissionId);
118
- else active.add(stored.row.submissionId);
119
- return {
120
- ...state,
121
- submissions: new Map(state.submissions).set(stored.row.submissionId, stored),
122
- activeByThread: new Map(state.activeByThread).set(stored.row.threadId, active)
123
- };
185
+ const row = stored.row;
186
+ const previous = state.submissions.get(row.submissionId);
187
+ let directory = state.byThread.get(row.threadId);
188
+ if (directory === void 0) {
189
+ directory = {
190
+ ordered: [],
191
+ commands: {
192
+ aborts: [],
193
+ approvals: [],
194
+ resolutions: []
195
+ },
196
+ children: 0,
197
+ workers: 0,
198
+ reservations: 0
199
+ };
200
+ state.byThread.set(row.threadId, directory);
201
+ }
202
+ if (previous === void 0) {
203
+ let low = 0;
204
+ let high = directory.ordered.length;
205
+ while (low < high) {
206
+ const middle = Math.floor((low + high) / 2);
207
+ const candidate = state.submissions.get(directory.ordered[middle]);
208
+ if (candidate !== void 0 && candidate.row.queueSequence < row.queueSequence) low = middle + 1;
209
+ else high = middle;
210
+ }
211
+ directory.ordered.splice(low, 0, row.submissionId);
212
+ state.receipts.set(row.receiptId, row.submissionId);
213
+ state.admissionIndex.set(admissionKey(row.threadId, row.principal, row.idempotencyKey), row.submissionId);
214
+ const latest = state.latestByThread.get(row.threadId);
215
+ if (latest === void 0 || (state.submissions.get(latest)?.row.queueSequence ?? -1) < row.queueSequence) state.latestByThread.set(row.threadId, row.submissionId);
216
+ }
217
+ directory.children += Number(row.state !== "settled" && row.parentLinkage !== void 0) - Number(previous !== void 0 && previous.row.state !== "settled" && previous.row.parentLinkage !== void 0);
218
+ directory.workers += Number(row.state !== "settled" && row.workerAdmissionJson !== void 0) - Number(previous !== void 0 && previous.row.state !== "settled" && previous.row.workerAdmissionJson !== void 0);
219
+ if (stored.abortIntent !== void 0 && previous?.abortIntent === void 0) retainCommand(directory.commands.aborts, row.submissionId);
220
+ if (stored.approvalDecisions !== previous?.approvalDecisions) {
221
+ for (const id of stored.approvalDecisions.keys()) if (!previous?.approvalDecisions.has(id)) retainCommand(directory.commands.approvals, row.submissionId, id);
222
+ }
223
+ if (stored.unknownResolutions !== previous?.unknownResolutions) {
224
+ for (const [id, value] of stored.unknownResolutions) if (!previous?.unknownResolutions.has(id)) retainCommand(directory.commands.resolutions, row.submissionId, value.intent.toolCallId, id);
225
+ }
226
+ if (previous?.joinedHostSubmissionId !== void 0 && previous.joinedHostSubmissionId !== stored.joinedHostSubmissionId) state.joinsByHost.get(previous.joinedHostSubmissionId)?.delete(row.submissionId);
227
+ if (stored.joinedHostSubmissionId !== void 0) {
228
+ let joins = state.joinsByHost.get(stored.joinedHostSubmissionId);
229
+ if (joins === void 0) state.joinsByHost.set(stored.joinedHostSubmissionId, joins = /* @__PURE__ */ new Set());
230
+ joins.add(row.submissionId);
231
+ }
232
+ let active = state.activeByThread.get(row.threadId);
233
+ if (active === void 0) state.activeByThread.set(row.threadId, active = /* @__PURE__ */ new Set());
234
+ if (row.state === "settled") active.delete(row.submissionId);
235
+ else active.add(row.submissionId);
236
+ state.submissions.set(row.submissionId, stored);
237
+ return state;
238
+ };
239
+ const withChildReservation = (state, reservation) => {
240
+ const previous = state.childReservations.get(reservation.reservationId);
241
+ state.childReservations.set(reservation.reservationId, reservation);
242
+ let children = state.childrenByParent.get(reservation.parentSubmissionId);
243
+ if (children === void 0) state.childrenByParent.set(reservation.parentSubmissionId, children = /* @__PURE__ */ new Set());
244
+ children.add(reservation.reservationId);
245
+ const parent = state.submissions.get(reservation.parentSubmissionId);
246
+ if (parent !== void 0) {
247
+ const directory = state.byThread.get(parent.row.threadId);
248
+ if (directory !== void 0) directory.reservations += Number(reservation.status !== "released") - Number(previous !== void 0 && previous.status !== "released");
249
+ }
250
+ return state;
124
251
  };
125
- const withChildReservation = (state, reservation) => ({
126
- ...state,
127
- childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
128
- });
129
252
  const findHead = (state, threadId) => {
130
253
  let head;
131
- for (const stored of state.submissions.values()) {
132
- if (stored.row.threadId !== threadId || stored.row.state === "settled" || stored.row.state === "unknown" && stored.abortIntent === void 0) continue;
254
+ for (const id of state.activeByThread.get(threadId) ?? []) {
255
+ const stored = state.submissions.get(id);
256
+ if (stored === void 0 || stored.row.state === "unknown" && stored.abortIntent === void 0) continue;
133
257
  if (head === void 0 || stored.row.queueSequence < head.row.queueSequence) head = stored;
134
258
  }
135
259
  return head;
136
260
  };
261
+ function* threadRows(state, threadId, after = -1) {
262
+ const ordered = state.byThread.get(threadId)?.ordered ?? [];
263
+ let low = 0;
264
+ let high = ordered.length;
265
+ while (low < high) {
266
+ const middle = Math.floor((low + high) / 2);
267
+ if ((state.submissions.get(ordered[middle])?.row.queueSequence ?? -1) <= after) low = middle + 1;
268
+ else high = middle;
269
+ }
270
+ for (let index = low; index < ordered.length; index++) {
271
+ const stored = state.submissions.get(ordered[index]);
272
+ if (stored !== void 0) yield stored;
273
+ }
274
+ }
137
275
  /**
138
- * Reference in-memory SubmissionLedger. It implements the full port contract — atomic idempotent
139
- * admission, eligible FIFO claims, producer-epoch fencing, Clock-driven ownership leases, idempotent
140
- * settlement reservation/finalization, and durable abort intent — with every transition applied
141
- * as one atomic `Ref.modify`, but its state does not survive the process (`non-durable`).
276
+ * Reference in-memory SubmissionLedger. Admission, ordered selection and canonical settlement
277
+ * share the Memory kernel's mutation gate. The kernel Scope owns local Run capabilities;
278
+ * selecting work does not write execution ownership into the ledger.
142
279
  *
143
- * Adapter-specific semantics within the port's latitude:
144
- *
145
- * - Time comes exclusively from the Effect `Clock` service, so `TestClock` drives lease expiry
146
- * deterministically; no wall clock is consulted.
147
- * - The ownership lease is pinned to `DEFAULT_OWNERSHIP_LEASE_DURATION` (D5); durable adapters
148
- * own the configuration seam.
149
- * - Any live lease in the Thread blocks every new claim, including the same `producerId`.
150
- * Unresolved unknown work is skipped only after ownership is released or expires.
151
- * - Claiming advances `ready` to `running` and otherwise preserves the recorded state, so
152
- * progress markers from an earlier Attempt survive a reclaim.
153
- * - `renewOwnership` keeps the token stable (the port allows rotation); a replayed admission
154
- * reports the Submission's current state alongside the original identities.
155
- * - `claimJoining` walks the strictly-later queue: rows already `joining`/`joined` to the
156
- * SAME host extend the claimed prefix and are skipped, and an aborted-settled row is a
157
- * closed obligation that is also skipped (P7 §7(c)); any other non-`ready` row (an
158
- * `admitted` gap, a non-aborted settled row, foreign-host linkage) breaks the prefix
159
- * conservatively.
160
- * - `markJoined` verifies the token against the HOST's live ownership (the lane is
161
- * host-owned), so a later host Attempt can repair a lost marker from history (DUR-016). The
162
- * join marker reuses the input-applied marker: the joined input IS `input:{sid}`.
163
- * - `suspend` and `markUnknown` refuse when an exact settlement is already reserved
164
- * (`SettlementConflict` with the reserved outcome) — DUR-011's reservation wins.
165
- * - `resolveAdmission` derives its answer from the single strongly consistent store, so it
166
- * never answers `Indeterminate` on its own; the test-only `resolveAdmissionFault` option
167
- * injects the `Indeterminate` classification so SUB-031 callers can be conformance-tested.
168
- * - `recordChildSettled` and `suspend(WaitingForChild)` observe child settlement directly from
169
- * the child rows (single-store latitude); no separate notification marker is stored.
280
+ * Joining retains the contiguous ready prefix and exact host linkage. Canonical settlement
281
+ * authorizes finalization and child wake repair, while bound sessions reject late commands.
282
+ * Child wake checks read the child rows directly, without a separate notification marker.
283
+ * Admission resolution is strongly consistent; resolveAdmissionFault injects the
284
+ * Indeterminate outcome for existing caller conformance workflows.
170
285
  */
171
286
  const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
287
+ const journal = yield* MemoryThreadStoreKernel;
288
+ const crypto = yield* Crypto.Crypto;
289
+ const mintIdentity = (operation) => crypto.randomUUIDv4().pipe(Effect.flatMap(Schema.decodeEffect(MemoryIdentityNonce)), Effect.mapError((cause) => ledgerError(operation, "Cannot mint a Memory identity", cause)));
172
290
  const state = yield* Ref.make({
291
+ byThread: /* @__PURE__ */ new Map(),
292
+ receipts: /* @__PURE__ */ new Map(),
293
+ joinsByHost: /* @__PURE__ */ new Map(),
294
+ childrenByParent: /* @__PURE__ */ new Map(),
173
295
  submissions: /* @__PURE__ */ new Map(),
174
296
  admissionIndex: /* @__PURE__ */ new Map(),
175
297
  lanes: /* @__PURE__ */ new Map(),
176
298
  childReservations: /* @__PURE__ */ new Map(),
177
- mintCounter: 0,
299
+ freshAdmissionSequence: 0,
178
300
  stoppedWorkers: /* @__PURE__ */ new Map(),
179
301
  latestByThread: /* @__PURE__ */ new Map(),
180
302
  activeByThread: /* @__PURE__ */ new Map()
181
303
  });
304
+ yield* journal.registerWorkOwner("admissions", {
305
+ threads: (request) => Ref.get(state).pipe(Effect.map((current) => {
306
+ const candidates = boundedWorkSelection(request.limit, (id) => id, request.afterThreadId);
307
+ for (const [threadId, ids] of current.activeByThread) if (ids.size > 0) candidates.add(threadId);
308
+ const threadIds = candidates.values;
309
+ const afterThreadId = threadIds.length === request.limit ? threadIds.at(-1) : void 0;
310
+ return {
311
+ threadIds,
312
+ ...afterThreadId === void 0 ? {} : { afterThreadId }
313
+ };
314
+ })),
315
+ page: (threadId, after, limit) => Effect.gen(function* () {
316
+ const current = yield* Ref.get(state);
317
+ const candidates = boundedWorkSelection(limit + 1, (id) => id, after);
318
+ for (const id of current.activeByThread.get(threadId) ?? []) candidates.add(id);
319
+ const ids = candidates.values;
320
+ const entries = [];
321
+ for (const id of ids.slice(0, limit)) {
322
+ const stored = current.submissions.get(id);
323
+ if (stored === void 0 || stored.row.threadId !== threadId || stored.row.state === "settled") return yield* ThreadStoreError.make({
324
+ operation: "admission work",
325
+ message: "Active admission index is incomplete or corrupt"
326
+ });
327
+ const entry = admissionWork(stored.row);
328
+ entries.push(stored.suspension === void 0 ? entry : {
329
+ ...entry,
330
+ wait: stored.suspension.reason._tag === "WaitingForChild" ? "child" : "approval"
331
+ });
332
+ }
333
+ const next = ids.length > limit ? ids[limit - 1] : void 0;
334
+ return {
335
+ entries,
336
+ ...next === void 0 ? {} : { after: next }
337
+ };
338
+ })
339
+ });
182
340
  const admissionFence = yield* SubmissionAdmissionFence;
183
- const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
184
341
  const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "non-durable" }));
185
- const admit = Effect.fn("MemorySubmissionLedger.admit")((unvalidated) => Effect.gen(function* () {
342
+ const admit = Effect.fnUntraced(function* (unvalidated) {
186
343
  const request = yield* validate(AdmissionRequest, "admit", unvalidated);
187
344
  const workerAdmissionJson = request.workerAdmission === void 0 ? void 0 : yield* Schema.encodeEffect(Schema.fromJsonString(WorkerAdmission))(request.workerAdmission).pipe(Effect.mapError(() => ledgerError("admit", "Invalid worker admission metadata")));
188
345
  const messageAdmissionJson = request.messageAdmission === void 0 ? void 0 : yield* Schema.encodeEffect(Schema.fromJsonString(InputMessage))(request.messageAdmission).pipe(Effect.mapError(() => ledgerError("admit", "Invalid message admission metadata")));
346
+ yield* journal.checkThreadCapacity(request.threadId).pipe(Effect.mapError((cause) => ledgerError("admit", cause.message, cause)));
189
347
  const nowMillis = yield* Clock.currentTimeMillis;
348
+ const identity = (yield* Ref.get(state)).admissionIndex.has(admissionKey(request.threadId, request.principal, request.idempotencyKey)) ? Result.fail(ledgerError("admit", "Retained admission disappeared")) : yield* Effect.result(mintIdentity("admit"));
190
349
  const services = yield* Effect.context();
191
350
  const decision = yield* Ref.modify(state, (current) => {
192
351
  const key = admissionKey(request.threadId, request.principal, request.idempotencyKey);
@@ -213,7 +372,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
213
372
  reason: "refused",
214
373
  code: "worker-stopped"
215
374
  })), current];
216
- const first = [...current.submissions.values()].find(({ row }) => row.threadId === request.threadId);
375
+ const first = threadRows(current, request.threadId).next().value;
217
376
  if (first !== void 0) {
218
377
  const previous = first.row.workerAdmissionJson === void 0 ? void 0 : Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(first.row.workerAdmissionJson);
219
378
  if (!Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin))(previous?.origin, request.workerAdmission?.origin)) return [failure(AdmissionPolicyError.make({
@@ -226,18 +385,20 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
226
385
  _tag: "cause",
227
386
  cause: checked.cause
228
387
  }, current];
229
- if (request.admissionGroup !== void 0 && [...current.submissions.values()].some(({ row }) => row.threadId === request.threadId && row.admissionGroup === request.admissionGroup && row.state !== "settled")) return [failure(AdmissionPolicyError.make({
388
+ if (request.admissionGroup !== void 0 && Array.from(current.activeByThread.get(request.threadId) ?? []).some((id) => current.submissions.get(id)?.row.admissionGroup === request.admissionGroup)) return [failure(AdmissionPolicyError.make({
230
389
  reason: "occupied",
231
390
  code: "admission-group"
232
391
  })), current];
233
- if (current.submissions.size >= MAX_SUBMISSIONS) return [failure(ledgerError("admit", `In-memory submission limit ${MAX_SUBMISSIONS} exceeded`)), current];
234
- const lane = current.lanes.get(request.threadId) ?? {
235
- nextQueueSequence: 1,
236
- producerEpoch: 0
237
- };
238
- const mintCounter = current.mintCounter + 1;
392
+ if (Result.isFailure(identity)) return [failure(identity.failure), current];
393
+ const lane = current.lanes.get(request.threadId) ?? { nextQueueSequence: 1 };
394
+ const freshAdmissionSequence = current.freshAdmissionSequence + 1;
395
+ if (!Number.isSafeInteger(freshAdmissionSequence)) return [failure(ledgerError("admit", "Fresh admission sequence is exhausted")), current];
396
+ const orderedIdentity = `${String(freshAdmissionSequence).padStart(16, "0")}-${identity.success}`;
397
+ const submissionId = decodeSubmissionId(`submission-memory-${orderedIdentity}`);
398
+ const receiptId = decodeReceiptId(`receipt-memory-${orderedIdentity}`);
399
+ if (current.submissions.has(submissionId) || current.receipts.has(receiptId)) return [failure(ledgerError("admit", "Fresh admission identity already exists")), current];
239
400
  const row = {
240
- submissionId: decodeSubmissionId(`submission-memory-${mintCounter}`),
401
+ submissionId,
241
402
  threadId: request.threadId,
242
403
  queueSequence: decodeQueueSequence(lane.nextQueueSequence),
243
404
  principal: request.principal,
@@ -247,7 +408,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
247
408
  deploymentId: request.deploymentId,
248
409
  inputPayload: request.inputPayload,
249
410
  inputDigest: request.inputDigest,
250
- receiptId: decodeReceiptId(`receipt-memory-${mintCounter}`),
411
+ receiptId,
251
412
  state: "admitted",
252
413
  settledOutcome: void 0,
253
414
  createdAtMillis: nowMillis,
@@ -258,23 +419,21 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
258
419
  ...request.admissionGroup === void 0 ? {} : { admissionGroup: request.admissionGroup },
259
420
  ...request.admissionFence === void 0 ? {} : { admissionFence: request.admissionFence }
260
421
  };
261
- const submissions = new Map(current.submissions).set(row.submissionId, {
422
+ const stored = {
262
423
  row,
263
- ownership: void 0,
264
424
  inputApplied: void 0,
265
- reservation: void 0,
425
+ finalization: void 0,
266
426
  abortIntent: void 0,
267
427
  joinedHostSubmissionId: void 0,
268
428
  suspension: void 0,
269
429
  unknownMark: void 0,
270
430
  approvalDecisions: /* @__PURE__ */ new Map(),
271
431
  unknownResolutions: /* @__PURE__ */ new Map()
272
- });
273
- const admissionIndex = new Map(current.admissionIndex).set(key, row.submissionId);
274
- const lanes = new Map(current.lanes).set(request.threadId, {
275
- nextQueueSequence: lane.nextQueueSequence + 1,
276
- producerEpoch: lane.producerEpoch
277
- });
432
+ };
433
+ if (!admissionFitsTransfer(request.threadId, toAdmission(row))) return [failure(transferCapacityError("admit")), current];
434
+ journal.initializeWork(request.threadId);
435
+ const admissionIndex = current.admissionIndex;
436
+ const lanes = current.lanes.set(request.threadId, { nextQueueSequence: lane.nextQueueSequence + 1 });
278
437
  return [success(AdmissionResult.make({
279
438
  submissionId: row.submissionId,
280
439
  receiptId: row.receiptId,
@@ -282,13 +441,10 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
282
441
  state: row.state,
283
442
  replayed: false
284
443
  })), {
285
- ...current,
286
- submissions,
444
+ ...withSubmission(current, stored),
287
445
  admissionIndex,
288
446
  lanes,
289
- mintCounter,
290
- latestByThread: new Map(current.latestByThread).set(request.threadId, row.submissionId),
291
- activeByThread: new Map(current.activeByThread).set(request.threadId, /* @__PURE__ */ new Set([...current.activeByThread.get(request.threadId) ?? [], row.submissionId]))
447
+ freshAdmissionSequence
292
448
  }];
293
449
  });
294
450
  if (decision._tag === "failure") return yield* decision.error;
@@ -303,8 +459,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
303
459
  return yield* Effect.failCause(decision.cause);
304
460
  }
305
461
  return decision.value;
306
- }), Effect.uninterruptible);
307
- const markReady = Effect.fn("MemorySubmissionLedger.markReady")((unvalidated) => Effect.gen(function* () {
462
+ }, Effect.uninterruptible);
463
+ const markReady = Effect.fnUntraced(function* (unvalidated) {
308
464
  const request = yield* validate(MarkReadyRequest, "markReady", unvalidated);
309
465
  const nowMillis = yield* Clock.currentTimeMillis;
310
466
  const decision = yield* Ref.modify(state, (current) => {
@@ -321,15 +477,15 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
321
477
  })];
322
478
  });
323
479
  if (decision._tag === "failure") return yield* decision.error;
324
- }));
325
- const lookup = Effect.fn("MemorySubmissionLedger.lookup")((unvalidated) => Effect.gen(function* () {
480
+ });
481
+ const lookup = Effect.fnUntraced(function* (unvalidated) {
326
482
  const request = yield* validate(SubmissionLookup, "lookup", unvalidated);
327
483
  const current = yield* Ref.get(state);
328
484
  const submissionId = request._tag === "SubmissionLookupById" ? request.submissionId : current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
329
485
  const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
330
486
  return stored === void 0 ? Option.none() : Option.some(toSnapshot(stored.row));
331
- }));
332
- const resolveAdmission = Effect.fn("MemorySubmissionLedger.resolveAdmission")((unvalidated) => Effect.gen(function* () {
487
+ });
488
+ const resolveAdmission = Effect.fnUntraced(function* (unvalidated) {
333
489
  const request = yield* validate(SubmissionLookupByKey, "resolveAdmission", unvalidated);
334
490
  if (options.resolveAdmissionFault !== void 0) {
335
491
  const fault = yield* options.resolveAdmissionFault;
@@ -339,17 +495,16 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
339
495
  const submissionId = current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
340
496
  const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
341
497
  return stored === void 0 ? AdmissionNotAdmitted.make() : AdmissionAdmitted.make({ submission: toSnapshot(stored.row) });
342
- }));
343
- const claim = Effect.fn("MemorySubmissionLedger.claim")((unvalidated) => Effect.gen(function* () {
498
+ });
499
+ const selectHead = Effect.fn("MemorySubmissionLedger.selectHead")((unvalidated) => Effect.gen(function* () {
344
500
  const request = yield* validate(ClaimRequest, "claim", unvalidated);
345
- const nowMillis = yield* Clock.currentTimeMillis;
501
+ const identity = yield* mintIdentity("claim");
346
502
  const decision = yield* Ref.modify(state, (current) => {
347
- for (const stored of current.submissions.values()) if (stored.row.threadId === request.threadId && stored.ownership !== void 0 && stored.ownership.leaseExpiresAtMillis > nowMillis) return [success(Option.none()), current];
503
+ if (journal.owner.hasOwner(request.threadId)) return [success(Option.none()), current];
348
504
  let head = findHead(current, request.threadId);
349
505
  if (request.handoff !== void 0) {
350
506
  const handoff = request.handoff;
351
- if (current.lanes.get(request.threadId)?.producerEpoch !== handoff.producerEpoch) return [success(Option.none()), current];
352
- const candidates = [...current.submissions.values()].filter((entry) => entry.row.threadId === request.threadId && entry.row.state !== "settled" && !(entry.row.state === "unknown" && entry.abortIntent === void 0)).sort((a, b) => a.row.queueSequence - b.row.queueSequence);
507
+ const candidates = Array.from(current.activeByThread.get(request.threadId) ?? [], (id) => current.submissions.get(id)).filter((entry) => entry !== void 0 && !(entry.row.state === "unknown" && entry.abortIntent === void 0)).sort((a, b) => a.row.queueSequence - b.row.queueSequence);
353
508
  for (const candidate of candidates) {
354
509
  if (candidate.row.submissionId === handoff.submissionId) {
355
510
  head = candidate;
@@ -357,91 +512,25 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
357
512
  }
358
513
  if (!handoff.deferredSubmissionIds.includes(candidate.row.submissionId) || candidate.row.state !== "input-applied" || candidate.abortIntent !== void 0) return [success(Option.none()), current];
359
514
  }
360
- if (head?.row.submissionId !== handoff.submissionId || head.row.state !== "ready" && head.row.state !== "running" && head.row.state !== "input-applied") return [success(Option.none()), current];
515
+ if (head?.row.submissionId !== handoff.submissionId || head.row.state !== "ready" && head.row.state !== "input-applied") return [success(Option.none()), current];
361
516
  }
362
517
  if (head === void 0) return [success(Option.none()), current];
363
518
  if (BLOCKED_HEAD_STATES.has(head.row.state)) return [success(Option.none()), current];
364
- const lane = current.lanes.get(request.threadId);
365
- if (lane === void 0) return [failure(ledgerError("claim", "Claimable head without a Thread lane")), current];
366
- const producerEpoch = decodeProducerEpoch(lane.producerEpoch + 1);
367
- const mintCounter = current.mintCounter + 1;
368
- const ownership = {
369
- attemptId: decodeAttemptId(`attempt-memory-${mintCounter}`),
370
- ownershipToken: decodeOwnershipToken(`ownership-memory-${mintCounter}`),
371
- producerEpoch,
372
- ownerProducerId: request.producerId,
373
- leaseExpiresAtMillis: nowMillis + leaseMillis
374
- };
375
- const row = head.row.state === "ready" ? {
376
- ...head.row,
377
- state: "running"
378
- } : head.row;
379
- const next = withSubmission(current, {
380
- ...head,
381
- row,
382
- ownership
383
- });
384
- const lanes = new Map(next.lanes).set(request.threadId, {
385
- nextQueueSequence: lane.nextQueueSequence,
386
- producerEpoch: lane.producerEpoch + 1
387
- });
388
519
  return [success(Option.some(Claim.make({
389
- submissionId: row.submissionId,
390
- attemptId: ownership.attemptId,
391
- ownershipToken: ownership.ownershipToken,
392
- producerEpoch,
393
- leaseExpiresAt: utc(ownership.leaseExpiresAtMillis),
394
- inputPayload: row.inputPayload
395
- }))), {
396
- ...next,
397
- lanes,
398
- mintCounter
399
- }];
400
- });
401
- if (decision._tag === "failure") return yield* decision.error;
402
- return decision.value;
403
- }));
404
- const renewOwnership = Effect.fn("MemorySubmissionLedger.renewOwnership")((unvalidated) => Effect.gen(function* () {
405
- const request = yield* validate(RenewOwnershipRequest, "renewOwnership", unvalidated);
406
- const nowMillis = yield* Clock.currentTimeMillis;
407
- const decision = yield* Ref.modify(state, (current) => {
408
- const stored = current.submissions.get(request.submissionId);
409
- if (stored === void 0) return [failure(ledgerError("renewOwnership", `Unknown Submission ${request.submissionId}`)), current];
410
- if (stored.ownership === void 0 || !ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
411
- const ownership = {
412
- ...stored.ownership,
413
- leaseExpiresAtMillis: nowMillis + leaseMillis
414
- };
415
- return [success(OwnershipRenewal.make({
416
- ownershipToken: ownership.ownershipToken,
417
- leaseExpiresAt: utc(ownership.leaseExpiresAtMillis)
418
- })), withSubmission(current, {
419
- ...stored,
420
- ownership
421
- })];
520
+ submissionId: head.row.submissionId,
521
+ attemptId: decodeAttemptId(`attempt-memory-${identity}`),
522
+ inputPayload: head.row.inputPayload
523
+ }))), current];
422
524
  });
423
525
  if (decision._tag === "failure") return yield* decision.error;
424
526
  return decision.value;
425
527
  }));
426
- const releaseOwnership = Effect.fn("MemorySubmissionLedger.releaseOwnership")((unvalidated) => Effect.gen(function* () {
427
- const request = yield* validate(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
428
- const decision = yield* Ref.modify(state, (current) => {
429
- const stored = current.submissions.get(request.submissionId);
430
- if (stored === void 0) return [failure(ledgerError("releaseOwnership", `Unknown Submission ${request.submissionId}`)), current];
431
- if (!ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
432
- return [success(void 0), withSubmission(current, {
433
- ...stored,
434
- ownership: void 0
435
- })];
436
- });
437
- if (decision._tag === "failure") return yield* decision.error;
438
- }));
439
- const markInputApplied = Effect.fn("MemorySubmissionLedger.markInputApplied")((unvalidated) => Effect.gen(function* () {
528
+ const markInputApplied = Effect.fnUntraced(function* (unvalidated) {
440
529
  const request = yield* validate(MarkInputAppliedRequest, "markInputApplied", unvalidated);
441
530
  const decision = yield* Ref.modify(state, (current) => {
442
531
  const stored = current.submissions.get(request.submissionId);
443
532
  if (stored === void 0) return [failure(ledgerError("markInputApplied", `Unknown Submission ${request.submissionId}`)), current];
444
- if (!ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
533
+ if (!journal.owner.hasOwner(stored.row.threadId, stored.row.submissionId)) return [failure(ownershipLost(stored)), current];
445
534
  if (stored.inputApplied !== void 0) {
446
535
  if (stored.inputApplied.recordId === request.recordId && stored.inputApplied.sequence === request.sequence) return [success(void 0), current];
447
536
  return [failure(ledgerError("markInputApplied", `A different canonical input marker is already recorded for Submission ${request.submissionId}`)), current];
@@ -461,103 +550,121 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
461
550
  })];
462
551
  });
463
552
  if (decision._tag === "failure") return yield* decision.error;
464
- }));
465
- const reserveSettlement = Effect.fn("MemorySubmissionLedger.reserveSettlement")((unvalidated) => Effect.gen(function* () {
466
- const request = yield* validate(SettlementReservation, "reserveSettlement", unvalidated);
467
- const decision = yield* Ref.modify(state, (current) => {
553
+ });
554
+ const canonicalSettlement = Effect.fnUntraced(function* (submission) {
555
+ const record = yield* journal.record(submission.threadId, submissionSettlementRecordId(submission.submissionId));
556
+ if (record === void 0) return void 0;
557
+ return {
558
+ record,
559
+ settlement: yield* validateCanonicalSettlement(record, submission)
560
+ };
561
+ });
562
+ const publish = Effect.fn("MemorySettlementPublisher.publish")(function* (input) {
563
+ const { request, record, settlement } = yield* validatePublication(input);
564
+ const prepared = yield* journal.prepareAppend(request.append);
565
+ return yield* journal.owner.withProgress(request.append.threadId, journal.withMutation(Effect.gen(function* () {
566
+ const current = yield* Ref.get(state);
468
567
  const stored = current.submissions.get(request.submissionId);
469
- if (stored === void 0) return [failure(ledgerError("reserveSettlement", `Unknown Submission ${request.submissionId}`)), current];
470
- const joinedSettlement = stored.row.state === "joined" && stored.joinedHostSubmissionId !== void 0;
471
- const queuedAbortSettlement = request.outcome === "aborted" && stored.abortIntent !== void 0 && stored.ownership === void 0 && (stored.row.state === "ready" || stored.row.state === "terminalizing");
472
- if (!joinedSettlement && !queuedAbortSettlement && !ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
473
- const existing = stored.reservation;
474
- if (existing !== void 0) {
475
- if (existing.settlementId !== request.settlementId || existing.outcome !== request.outcome || existing.recordDigest !== request.recordDigest) return [failure(SettlementConflict.make({
476
- submissionId: request.submissionId,
477
- existingOutcome: existing.outcome
478
- })), current];
479
- return [success(ReservedSettlement.make({
480
- submissionId: request.submissionId,
481
- settlementId: existing.settlementId,
482
- outcome: existing.outcome,
483
- record: existing.record,
484
- recordDigest: existing.recordDigest,
485
- replayed: true
486
- })), current];
568
+ if (stored === void 0) return yield* ledgerError("publish settlement", "Unknown Submission");
569
+ if (stored.row.threadId !== prepared.request.threadId) return yield* ledgerError("publish settlement", "Publication targets another Thread");
570
+ yield* validateCanonicalSettlement(record, stored.row);
571
+ const existing = yield* canonicalSettlement(stored.row);
572
+ const tail = yield* journal.tail(stored.row.threadId);
573
+ switch (request.authority._tag) {
574
+ case "Owned":
575
+ if (!journal.owner.hasOwner(stored.row.threadId, stored.row.submissionId)) return yield* ownershipLost(stored);
576
+ yield* validateCanonicalSettlement(record, {
577
+ submissionId: request.submissionId,
578
+ receiptId: stored.row.receiptId,
579
+ ...settlement.outcome === "aborted" && settlement.runId === void 0 ? {} : { runId: runIdForSubmission(request.submissionId) }
580
+ });
581
+ break;
582
+ case "Joined": {
583
+ if (stored.row.state !== "joined" && !(stored.row.state === "settled" && existing !== void 0) || stored.joinedHostSubmissionId !== request.authority.hostSubmissionId) return yield* ledgerError("publish settlement", "Submission is not joined to this host");
584
+ const host = current.submissions.get(request.authority.hostSubmissionId);
585
+ const canonicalHost = host === void 0 || host.row.threadId !== stored.row.threadId ? void 0 : yield* canonicalSettlement(host.row);
586
+ if (canonicalHost === void 0) return yield* ledgerError("publish settlement", "Joined host has no canonical settlement");
587
+ yield* validateJoinedSettlement(settlement, canonicalHost.settlement);
588
+ break;
589
+ }
590
+ case "QueuedAbort":
591
+ if (stored.row.state !== "ready" && !(stored.row.state === "settled" && existing !== void 0) || stored.abortIntent === void 0 || journal.owner.hasOwner(stored.row.threadId, stored.row.submissionId) || settlement.outcome !== "aborted") return yield* ledgerError("publish settlement", "Submission is not an unclaimed queued abort");
592
+ yield* validateCanonicalSettlement(record, {
593
+ submissionId: request.submissionId,
594
+ receiptId: stored.row.receiptId,
595
+ runId: void 0
596
+ });
487
597
  }
488
- const reservation = {
489
- settlementId: request.settlementId,
490
- outcome: request.outcome,
491
- record: request.record,
492
- recordDigest: request.recordDigest,
493
- finalizedAtMillis: void 0
494
- };
495
- const row = STATE_RANK[stored.row.state] < STATE_RANK.terminalizing ? {
496
- ...stored.row,
497
- state: "terminalizing"
498
- } : stored.row;
499
- return [success(ReservedSettlement.make({
500
- submissionId: request.submissionId,
501
- settlementId: reservation.settlementId,
502
- outcome: reservation.outcome,
503
- record: reservation.record,
504
- recordDigest: reservation.recordDigest,
598
+ if (existing !== void 0) return SettlementPublicationResult.make({
599
+ record: existing.record,
600
+ tailSequence: tail.tailSequence,
601
+ replayed: true
602
+ });
603
+ if (stored.row.state === "settled") return yield* ledgerError("publish settlement", "Finalized Submission has no canonical settlement");
604
+ const appended = yield* journal.appendPrepared(prepared);
605
+ return SettlementPublicationResult.make({
606
+ record,
607
+ tailSequence: appended.lastSequence,
505
608
  replayed: false
506
- })), withSubmission(current, {
507
- ...stored,
508
- row,
509
- reservation
510
- })];
511
- });
512
- if (decision._tag === "failure") return yield* decision.error;
513
- return decision.value;
514
- }));
609
+ });
610
+ })));
611
+ });
515
612
  const finalizeSettlement = Effect.fn("MemorySubmissionLedger.finalizeSettlement")((unvalidated) => Effect.gen(function* () {
516
613
  const request = yield* validate(SettlementFinalization, "finalizeSettlement", unvalidated);
614
+ const before = (yield* Ref.get(state)).submissions.get(request.submissionId);
615
+ const canonical = before === void 0 ? void 0 : yield* canonicalSettlement(before.row);
616
+ if (canonical === void 0) return yield* ledgerError("finalizeSettlement", "Submission has no canonical settlement");
617
+ const { record, settlement } = canonical;
517
618
  const nowMillis = yield* Clock.currentTimeMillis;
518
619
  const decision = yield* Ref.modify(state, (current) => {
519
620
  const stored = current.submissions.get(request.submissionId);
520
621
  if (stored === void 0) return [failure(ledgerError("finalizeSettlement", `Unknown Submission ${request.submissionId}`)), current];
521
- const reservation = stored.reservation;
522
- if (reservation === void 0) return [failure(ledgerError("finalizeSettlement", `No settlement reservation for Submission ${request.submissionId}`)), current];
523
- const settlementFailure = settlementFailureFromRecord(reservation.record);
524
- if (reservation.outcome === "failed" !== (settlementFailure !== void 0)) return [failure(ledgerError("finalizeSettlement", `Settlement reservation for Submission ${request.submissionId} has contradictory failure evidence`)), current];
525
- if (reservation.settlementId !== request.settlementId) return [failure(SettlementConflict.make({
622
+ const settlementFailure = settlementFailureFromRecord(record);
623
+ if (settlement.settlementId !== request.settlementId) return [failure(SettlementConflict.make({
526
624
  submissionId: request.submissionId,
527
- existingOutcome: reservation.outcome
528
- })), current];
529
- if (reservation.finalizedAtMillis !== void 0) return [success(Settlement.make({
530
- submissionId: stored.row.submissionId,
531
- settlementId: reservation.settlementId,
532
- receiptId: stored.row.receiptId,
533
- outcome: reservation.outcome,
534
- ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
535
- settledAt: utc(reservation.finalizedAtMillis)
625
+ existingOutcome: settlement.outcome
536
626
  })), current];
537
- const terminal = stored.row.workerAdmissionJson === void 0 ? void 0 : workerTerminalFromRecord(toSnapshot(stored.row), reservation.record);
627
+ if (stored.finalization !== void 0) {
628
+ if (stored.row.state !== "settled" || stored.row.settledOutcome !== settlement.outcome || stored.finalization.settlementId !== request.settlementId || stored.finalization.recordId !== record.recordId) return [failure(ledgerError("finalizeSettlement", "Finalized identity disagrees with canonical settlement")), current];
629
+ return [success(Settlement.make({
630
+ submissionId: stored.row.submissionId,
631
+ settlementId: settlement.settlementId,
632
+ receiptId: stored.row.receiptId,
633
+ outcome: settlement.outcome,
634
+ ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
635
+ settledAt: utc(stored.finalization.finalizedAtMillis)
636
+ })), current];
637
+ }
638
+ if (stored.row.state === "settled") return [failure(ledgerError("finalizeSettlement", "Finalized timestamp is missing")), current];
639
+ const terminal = stored.row.workerAdmissionJson === void 0 ? void 0 : workerTerminalFromRecord(toSnapshot(stored.row), record);
538
640
  const latestId = current.latestByThread.get(stored.row.threadId);
539
641
  const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
540
642
  const pendingNewer = terminal === "completed" && latest !== void 0 && latest.row.queueSequence > stored.row.queueSequence && !(latest.joinedHostSubmissionId === stored.row.submissionId && latest.inputApplied !== void 0);
541
643
  let sealed = current;
542
644
  if (terminal !== void 0 && !pendingNewer && !current.stoppedWorkers.has(stored.row.threadId)) {
543
- const submissions = new Map(current.submissions);
544
- for (const id of current.activeByThread.get(stored.row.threadId) ?? []) {
645
+ const submissions = current.submissions;
646
+ if (!transferPageFits(stored.row.threadId)) return [failure(transferCapacityError("finalizeSettlement")), current];
647
+ for (const apply of [false, true]) for (const id of current.activeByThread.get(stored.row.threadId) ?? []) {
545
648
  const other = submissions.get(id);
546
649
  if (other === void 0) continue;
547
- if (id !== stored.row.submissionId && !(other.joinedHostSubmissionId === stored.row.submissionId && other.inputApplied !== void 0) && other.abortIntent === void 0) submissions.set(id, {
548
- ...other,
549
- abortIntent: AbortIntent.make({
650
+ if (id !== stored.row.submissionId && !(other.joinedHostSubmissionId === stored.row.submissionId && other.inputApplied !== void 0) && other.abortIntent === void 0) {
651
+ const abortIntent = AbortIntent.make({
550
652
  submissionId: id,
551
653
  author: stored.row.principal,
552
654
  reason: `Worker assignment ${terminal}`,
553
655
  requestedAt: utc(nowMillis)
554
- })
555
- });
656
+ });
657
+ if (!apply && !abortFitsTransfer(other.row.threadId, abortIntent)) return [failure(transferCapacityError("finalizeSettlement")), current];
658
+ if (apply) withSubmission(current, {
659
+ ...other,
660
+ abortIntent
661
+ });
662
+ }
556
663
  }
557
664
  sealed = {
558
665
  ...current,
559
666
  submissions,
560
- stoppedWorkers: new Map(current.stoppedWorkers).set(stored.row.threadId, terminal)
667
+ stoppedWorkers: current.stoppedWorkers.set(stored.row.threadId, terminal)
561
668
  };
562
669
  }
563
670
  const next = withSubmission(sealed, {
@@ -565,27 +672,28 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
565
672
  row: {
566
673
  ...stored.row,
567
674
  state: "settled",
568
- settledOutcome: reservation.outcome
675
+ settledOutcome: settlement.outcome
569
676
  },
570
- ownership: void 0,
571
- reservation: {
572
- ...reservation,
677
+ finalization: {
678
+ settlementId: settlement.settlementId,
679
+ recordId: record.recordId,
573
680
  finalizedAtMillis: nowMillis
574
681
  }
575
682
  });
576
683
  return [success(Settlement.make({
577
684
  submissionId: stored.row.submissionId,
578
- settlementId: reservation.settlementId,
685
+ settlementId: settlement.settlementId,
579
686
  receiptId: stored.row.receiptId,
580
- outcome: reservation.outcome,
687
+ outcome: settlement.outcome,
581
688
  ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
582
689
  settledAt: utc(nowMillis)
583
690
  })), next];
584
691
  });
585
692
  if (decision._tag === "failure") return yield* decision.error;
693
+ if (before !== void 0) journal.owner.closeSubmission(before.row.threadId, request.submissionId);
586
694
  return decision.value;
587
695
  }));
588
- const inspectWorker = Effect.fn("MemorySubmissionLedger.inspectWorker")(function* (threadId) {
696
+ const inspectWorker = Effect.fnUntraced(function* (threadId) {
589
697
  const current = yield* Ref.get(state);
590
698
  const latestId = current.latestByThread.get(threadId);
591
699
  const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
@@ -601,34 +709,41 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
601
709
  ...terminal === void 0 ? {} : { terminal }
602
710
  });
603
711
  });
604
- const stopWorker = Effect.fn("MemorySubmissionLedger.stopWorker")(function* (unvalidated) {
712
+ const stopWorker = Effect.fnUntraced(function* (unvalidated) {
605
713
  const request = yield* validate(WorkerStopCommand, "stopWorker", unvalidated);
606
714
  const now = yield* Clock.currentTimeMillis;
607
- return yield* Ref.modify(state, (current) => {
608
- const submissions = new Map(current.submissions);
715
+ const decision = yield* Ref.modify(state, (current) => {
716
+ const submissions = current.submissions;
609
717
  let owned = 0;
610
- for (const id of current.activeByThread.get(request.threadId) ?? []) {
718
+ if (!current.stoppedWorkers.has(request.threadId) && !transferPageFits(request.threadId)) return [failure(transferCapacityError("stopWorker")), current];
719
+ for (const apply of [false, true]) for (const id of current.activeByThread.get(request.threadId) ?? []) {
611
720
  const stored = submissions.get(id);
612
721
  if (stored === void 0) continue;
613
- if (stored.ownership !== void 0) owned++;
614
- if (stored.abortIntent === void 0) submissions.set(id, {
615
- ...stored,
616
- abortIntent: AbortIntent.make({
722
+ if (!apply && journal.owner.hasOwner(stored.row.threadId, stored.row.submissionId)) owned++;
723
+ if (stored.abortIntent === void 0) {
724
+ const abortIntent = AbortIntent.make({
617
725
  submissionId: id,
618
726
  author: request.author,
619
727
  reason: "Worker owner stopped the worker",
620
728
  requestedAt: utc(now)
621
- })
622
- });
729
+ });
730
+ if (!apply && !abortFitsTransfer(stored.row.threadId, abortIntent)) return [failure(transferCapacityError("stopWorker")), current];
731
+ if (apply) withSubmission(current, {
732
+ ...stored,
733
+ abortIntent
734
+ });
735
+ }
623
736
  }
624
- return [owned, {
737
+ return [success(owned), {
625
738
  ...current,
626
739
  submissions,
627
- stoppedWorkers: current.stoppedWorkers.has(request.threadId) ? current.stoppedWorkers : new Map(current.stoppedWorkers).set(request.threadId, void 0)
740
+ stoppedWorkers: current.stoppedWorkers.has(request.threadId) ? current.stoppedWorkers : current.stoppedWorkers.set(request.threadId, void 0)
628
741
  }];
629
742
  });
743
+ if (decision._tag === "failure") return yield* decision.error;
744
+ return decision.value;
630
745
  });
631
- const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
746
+ const requestAbort = Effect.fnUntraced(function* (unvalidated) {
632
747
  const request = yield* validate(AbortCommand, "requestAbort", unvalidated);
633
748
  const nowMillis = yield* Clock.currentTimeMillis;
634
749
  const decision = yield* Ref.modify(state, (current) => {
@@ -655,6 +770,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
655
770
  reason: request.reason,
656
771
  requestedAt: utc(nowMillis)
657
772
  });
773
+ if (!abortFitsTransfer(stored.row.threadId, intent)) return [failure(transferCapacityError("requestAbort")), current];
658
774
  return [success(intent), withSubmission(current, {
659
775
  ...stored,
660
776
  abortIntent: intent
@@ -662,24 +778,35 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
662
778
  });
663
779
  if (decision._tag === "failure") return yield* decision.error;
664
780
  return decision.value;
665
- }));
666
- const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
781
+ });
782
+ const claimJoining = Effect.fnUntraced(function* (unvalidated) {
667
783
  const request = yield* validate(ClaimJoiningRequest, "claimJoining", unvalidated);
784
+ const published = /* @__PURE__ */ new Map();
785
+ const before = yield* Ref.get(state);
786
+ for (const id of before.activeByThread.get(request.threadId) ?? []) {
787
+ const stored = before.submissions.get(id);
788
+ if (stored === void 0 || stored.row.state !== "ready") continue;
789
+ const canonical = yield* canonicalSettlement(stored.row);
790
+ if (canonical !== void 0) published.set(stored.row.submissionId, canonical.settlement.outcome);
791
+ }
668
792
  const decision = yield* Ref.modify(state, (current) => {
669
793
  if (current.stoppedWorkers.has(request.threadId)) return [success([]), current];
670
794
  const host = current.submissions.get(request.hostSubmissionId);
671
795
  if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
672
796
  if (host.row.threadId !== request.threadId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`)), current];
673
- if (!ownsLane(current, host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
674
- const later = [...current.submissions.values()].filter((stored) => stored.row.threadId === request.threadId && stored.row.queueSequence > host.row.queueSequence).sort((left, right) => left.row.queueSequence - right.row.queueSequence);
797
+ if (!journal.owner.hasOwner(host.row.threadId, host.row.submissionId)) return [failure(ownershipLost(host)), current];
798
+ const later = threadRows(current, request.threadId, host.row.queueSequence);
675
799
  const claims = [];
676
- const submissions = new Map(current.submissions);
800
+ const submissions = current.submissions;
677
801
  for (const stored of later) {
678
802
  if (claims.length >= request.maxCount) break;
679
803
  if ((stored.row.state === "joining" || stored.row.state === "joined") && stored.joinedHostSubmissionId === request.hostSubmissionId) continue;
680
804
  if (stored.row.state === "settled" && stored.row.settledOutcome === "aborted") continue;
681
805
  if (stored.row.state !== "ready") break;
682
- submissions.set(stored.row.submissionId, {
806
+ const terminal = published.get(stored.row.submissionId);
807
+ if (terminal === "aborted") continue;
808
+ if (terminal !== void 0) break;
809
+ withSubmission(current, {
683
810
  ...stored,
684
811
  row: {
685
812
  ...stored.row,
@@ -700,8 +827,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
700
827
  });
701
828
  if (decision._tag === "failure") return yield* decision.error;
702
829
  return decision.value;
703
- }));
704
- const markJoined = Effect.fn("MemorySubmissionLedger.markJoined")((unvalidated) => Effect.gen(function* () {
830
+ });
831
+ const markJoined = Effect.fnUntraced(function* (unvalidated) {
705
832
  const request = yield* validate(MarkJoinedRequest, "markJoined", unvalidated);
706
833
  const decision = yield* Ref.modify(state, (current) => {
707
834
  const stored = current.submissions.get(request.submissionId);
@@ -709,7 +836,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
709
836
  if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("markJoined", `Submission ${request.submissionId} was never claimed for joining`)), current];
710
837
  const host = current.submissions.get(stored.joinedHostSubmissionId);
711
838
  if (host === void 0) return [failure(ledgerError("markJoined", `Host Submission ${stored.joinedHostSubmissionId} is missing`)), current];
712
- if (!ownsLane(current, host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
839
+ if (!journal.owner.hasOwner(host.row.threadId, host.row.submissionId)) return [failure(ownershipLost(host)), current];
713
840
  if (stored.inputApplied !== void 0) {
714
841
  if (stored.inputApplied.recordId === request.recordId && stored.inputApplied.sequence === request.sequence) return [success(void 0), current];
715
842
  return [failure(ledgerError("markJoined", `A different join marker is already recorded for Submission ${request.submissionId}`)), current];
@@ -729,13 +856,20 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
729
856
  })];
730
857
  });
731
858
  if (decision._tag === "failure") return yield* decision.error;
732
- }));
733
- const revertJoining = Effect.fn("MemorySubmissionLedger.revertJoining")((unvalidated) => Effect.gen(function* () {
859
+ });
860
+ const revertJoining = Effect.fnUntraced(function* (unvalidated) {
734
861
  const request = yield* validate(RevertJoiningRequest, "revertJoining", unvalidated);
735
862
  const decision = yield* Ref.modify(state, (current) => {
736
863
  const stored = current.submissions.get(request.submissionId);
737
864
  if (stored === void 0) return [failure(ledgerError("revertJoining", `Unknown Submission ${request.submissionId}`)), current];
738
865
  if (stored.row.state !== "joining") return [success(void 0), current];
866
+ const guard = request.guard;
867
+ if (guard !== void 0) {
868
+ if (stored.joinedHostSubmissionId !== guard.hostSubmissionId) return [success(void 0), current];
869
+ const host = current.submissions.get(guard.hostSubmissionId);
870
+ if (host === void 0 || host.row.threadId !== stored.row.threadId) return [failure(ledgerError("revertJoining", `Host Submission ${guard.hostSubmissionId} is missing or belongs to another Thread`)), current];
871
+ if (host.row.state !== "settled" && !journal.owner.hasOwner(host.row.threadId, host.row.submissionId)) return [failure(ledgerError("revertJoining", "The linked host has no active local session")), current];
872
+ }
739
873
  return [success(void 0), withSubmission(current, {
740
874
  ...stored,
741
875
  row: {
@@ -746,10 +880,20 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
746
880
  })];
747
881
  });
748
882
  if (decision._tag === "failure") return yield* decision.error;
749
- }));
750
- const suspend = Effect.fn("MemorySubmissionLedger.suspend")((unvalidated) => Effect.gen(function* () {
883
+ });
884
+ const suspend = Effect.fnUntraced(function* (unvalidated) {
751
885
  const request = yield* validate(SuspendRequest, "suspend", unvalidated);
752
886
  const nowMillis = yield* Clock.currentTimeMillis;
887
+ const target = (yield* Ref.get(state)).submissions.get(request.submissionId);
888
+ const canonical = target === void 0 ? void 0 : yield* canonicalSettlement(target.row);
889
+ const announcedChildren = /* @__PURE__ */ new Set();
890
+ if (request.reason._tag === "WaitingForChild") {
891
+ const current = yield* Ref.get(state);
892
+ for (const { childSubmissionId } of request.reason.children) {
893
+ const child = current.submissions.get(childSubmissionId);
894
+ if (child !== void 0 && (yield* canonicalSettlement(child.row)) !== void 0) announcedChildren.add(childSubmissionId);
895
+ }
896
+ }
753
897
  const decision = yield* Ref.modify(state, (current) => {
754
898
  const stored = current.submissions.get(request.submissionId);
755
899
  if (stored === void 0) return [failure(ledgerError("suspend", `Unknown Submission ${request.submissionId}`)), current];
@@ -760,19 +904,18 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
760
904
  existingOutcome: stored.row.settledOutcome
761
905
  })), current];
762
906
  }
763
- if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
907
+ if (canonical !== void 0) return [failure(SettlementConflict.make({
764
908
  submissionId: request.submissionId,
765
- existingOutcome: stored.reservation.outcome
909
+ existingOutcome: canonical.settlement.outcome
766
910
  })), current];
767
- if (!ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
768
- if (request.reason._tag === "ApprovalPending" ? request.reason.toolCallIds.every((toolCallId) => stored.approvalDecisions.has(toolCallId)) : request.reason.children.every((child) => current.submissions.get(child.childSubmissionId)?.row.state === "settled")) return [success("resume-immediately"), current];
911
+ if (!journal.owner.hasOwner(stored.row.threadId, stored.row.submissionId)) return [failure(ownershipLost(stored)), current];
912
+ if (request.reason._tag === "ApprovalPending" ? approvalDecisionsCover(request.reason.toolCallIds, (toolCallId) => stored.approvalDecisions.get(toolCallId)?.decision) : request.reason.children.every((child) => announcedChildren.has(child.childSubmissionId))) return [success("resume-immediately"), current];
769
913
  return [success("suspended"), withSubmission(current, {
770
914
  ...stored,
771
915
  row: {
772
916
  ...stored.row,
773
917
  state: "suspended"
774
918
  },
775
- ownership: void 0,
776
919
  suspension: {
777
920
  reason: request.reason,
778
921
  suspendedAtMillis: nowMillis
@@ -780,9 +923,10 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
780
923
  })];
781
924
  });
782
925
  if (decision._tag === "failure") return yield* decision.error;
926
+ if (decision.value === "suspended" && target !== void 0) journal.owner.closeSubmission(target.row.threadId, request.submissionId);
783
927
  return decision.value;
784
- }));
785
- const recordApprovalDecision = Effect.fn("MemorySubmissionLedger.recordApprovalDecision")((unvalidated) => Effect.gen(function* () {
928
+ });
929
+ const recordApprovalDecision = Effect.fnUntraced(function* (unvalidated) {
786
930
  const command = yield* validate(ApprovalDecisionCommand, "recordApprovalDecision", unvalidated);
787
931
  const nowMillis = yield* Clock.currentTimeMillis;
788
932
  const decision = yield* Ref.modify(state, (current) => {
@@ -812,8 +956,9 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
812
956
  reason: command.reason,
813
957
  decidedAt: utc(nowMillis)
814
958
  });
959
+ if (!approvalFitsTransfer(stored.row.threadId, intent)) return [failure(transferCapacityError("recordApprovalDecision")), current];
815
960
  const approvalDecisions = new Map(stored.approvalDecisions).set(command.toolCallId, intent);
816
- const wakes = stored.row.state === "suspended" && stored.suspension !== void 0 && stored.suspension.reason._tag === "ApprovalPending" && stored.suspension.reason.toolCallIds.every((toolCallId) => approvalDecisions.has(toolCallId));
961
+ const wakes = stored.row.state === "suspended" && stored.suspension !== void 0 && stored.suspension.reason._tag === "ApprovalPending" && approvalDecisionsCover(stored.suspension.reason.toolCallIds, (toolCallId) => approvalDecisions.get(toolCallId)?.decision);
817
962
  return [success(intent), withSubmission(current, {
818
963
  ...stored,
819
964
  row: wakes ? {
@@ -826,9 +971,11 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
826
971
  });
827
972
  if (decision._tag === "failure") return yield* decision.error;
828
973
  return decision.value;
829
- }));
830
- const markUnknown = Effect.fn("MemorySubmissionLedger.markUnknown")((unvalidated) => Effect.gen(function* () {
974
+ });
975
+ const markUnknown = Effect.fnUntraced(function* (unvalidated) {
831
976
  const request = yield* validate(MarkUnknownRequest, "markUnknown", unvalidated);
977
+ const target = (yield* Ref.get(state)).submissions.get(request.submissionId);
978
+ const canonical = target === void 0 ? void 0 : yield* canonicalSettlement(target.row);
832
979
  const decision = yield* Ref.modify(state, (current) => {
833
980
  const stored = current.submissions.get(request.submissionId);
834
981
  if (stored === void 0) return [failure(ledgerError("markUnknown", `Unknown Submission ${request.submissionId}`)), current];
@@ -839,9 +986,9 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
839
986
  existingOutcome: stored.row.settledOutcome
840
987
  })), current];
841
988
  }
842
- if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
989
+ if (canonical !== void 0) return [failure(SettlementConflict.make({
843
990
  submissionId: request.submissionId,
844
- existingOutcome: stored.reservation.outcome
991
+ existingOutcome: canonical.settlement.outcome
845
992
  })), current];
846
993
  const existing = stored.unknownMark;
847
994
  const known = new Set(existing?.toolCallIds ?? []);
@@ -859,22 +1006,35 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
859
1006
  })];
860
1007
  });
861
1008
  if (decision._tag === "failure") return yield* decision.error;
862
- }));
863
- const recordUnknownResolution = Effect.fn("MemorySubmissionLedger.recordUnknownResolution")((unvalidated) => Effect.gen(function* () {
1009
+ });
1010
+ const recordUnknownResolution = Effect.fnUntraced(function* (unvalidated) {
864
1011
  const command = yield* validate(UnknownResolutionCommand, "recordUnknownResolution", unvalidated);
865
1012
  const nowMillis = yield* Clock.currentTimeMillis;
1013
+ const before = (yield* Ref.get(state)).submissions.get(command.submissionId);
1014
+ const kind = unknownResolutionKind(command.resolution);
1015
+ if (before !== void 0 && kind === "factual" && !before.unknownResolutions.has(JSON.stringify([command.toolCallId, kind]))) {
1016
+ const runId = runIdForSubmission(before.row.submissionId);
1017
+ const outcomes = yield* journal.toolCallResults(before.row.threadId, runId, command.toolCallId);
1018
+ const result = outcomes[0]?.payload;
1019
+ if (outcomes.length > 1 || result !== void 0 && (result._tag !== "ToolCallSettled" || result.runId !== runId || result.toolCallId !== command.toolCallId)) return yield* ledgerError("recordUnknownResolution", "Canonical Tool result identity is inconsistent");
1020
+ if (result?._tag === "ToolCallSettled" && !unknownResolutionMatchesResult(command.resolution, result)) return yield* UnknownResolutionConflict.make({
1021
+ submissionId: command.submissionId,
1022
+ toolCallId: command.toolCallId
1023
+ });
1024
+ }
866
1025
  const decision = yield* Ref.modify(state, (current) => {
867
1026
  const stored = current.submissions.get(command.submissionId);
868
1027
  if (stored === void 0) return [failure(ledgerError("recordUnknownResolution", `Unknown Submission ${command.submissionId}`)), current];
869
1028
  if (stored.row.state === "settled") {
870
1029
  if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordUnknownResolution", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
871
- return [failure(SettlementConflict.make({
1030
+ if (command.resolution._tag !== "CompletedWithResult" && command.resolution._tag !== "NeverHappened") return [failure(SettlementConflict.make({
872
1031
  submissionId: command.submissionId,
873
1032
  existingOutcome: stored.row.settledOutcome
874
1033
  })), current];
875
1034
  }
876
- const existing = stored.unknownResolutions.get(command.toolCallId);
877
- if (existing !== void 0 && !equivalentUnknownResolution(existing.intent.resolution, command.resolution)) return [failure(UnknownResolutionConflict.make({
1035
+ const key = JSON.stringify([command.toolCallId, kind]);
1036
+ const existing = stored.unknownResolutions.get(key);
1037
+ if (existing !== void 0 && !equivalentUnknownResolution(existing.intent.resolution, command.resolution) || existing === void 0 && kind === "execution" && stored.unknownResolutions.has(JSON.stringify([command.toolCallId, "factual"]))) return [failure(UnknownResolutionConflict.make({
878
1038
  submissionId: command.submissionId,
879
1039
  toolCallId: command.toolCallId
880
1040
  })), current];
@@ -886,8 +1046,9 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
886
1046
  resolution: command.resolution,
887
1047
  resolvedAt: utc(nowMillis)
888
1048
  });
889
- const unknownResolutions = existing !== void 0 ? stored.unknownResolutions : new Map(stored.unknownResolutions).set(command.toolCallId, { intent });
890
- const wakes = stored.row.state === "unknown" && stored.unknownMark !== void 0 && stored.unknownMark.toolCallIds.every((toolCallId) => unknownResolutions.has(toolCallId));
1049
+ if (existing === void 0 && !resolutionFitsTransfer(stored.row.threadId, intent)) return [failure(transferCapacityError("recordUnknownResolution")), current];
1050
+ const unknownResolutions = existing !== void 0 ? stored.unknownResolutions : new Map(stored.unknownResolutions).set(key, { intent });
1051
+ const wakes = stored.row.state === "unknown" && stored.unknownMark !== void 0 && stored.unknownMark.toolCallIds.every((toolCallId) => unknownResolutions.has(JSON.stringify([toolCallId, "execution"])) || unknownResolutions.has(JSON.stringify([toolCallId, "factual"])));
891
1052
  return [success(intent), withSubmission(current, {
892
1053
  ...stored,
893
1054
  row: wakes ? {
@@ -900,21 +1061,25 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
900
1061
  });
901
1062
  if (decision._tag === "failure") return yield* decision.error;
902
1063
  return decision.value;
903
- }));
904
- const recordChildSettled = Effect.fn("MemorySubmissionLedger.recordChildSettled")((unvalidated) => Effect.gen(function* () {
1064
+ });
1065
+ const recordChildSettled = Effect.fnUntraced(function* (unvalidated) {
905
1066
  const request = yield* validate(ChildSettledNotification, "recordChildSettled", unvalidated);
1067
+ const before = yield* Ref.get(state);
1068
+ const waiting = before.submissions.get(request.parentSubmissionId)?.suspension?.reason;
1069
+ const ids = /* @__PURE__ */ new Set([request.childSubmissionId, ...waiting?._tag === "WaitingForChild" ? waiting.children.map((child) => child.childSubmissionId) : []]);
1070
+ const announcedChildren = /* @__PURE__ */ new Set();
1071
+ for (const id of ids) {
1072
+ const child = before.submissions.get(id);
1073
+ if (child !== void 0 && (yield* canonicalSettlement(child.row)) !== void 0) announcedChildren.add(id);
1074
+ }
906
1075
  const decision = yield* Ref.modify(state, (current) => {
907
1076
  const parent = current.submissions.get(request.parentSubmissionId);
908
1077
  if (parent === void 0) return [failure(ledgerError("recordChildSettled", `Unknown Submission ${request.parentSubmissionId}`)), current];
909
- const child = current.submissions.get(request.childSubmissionId);
910
- if (!(child !== void 0 && (child.row.state === "settled" || child.row.state === "terminalizing" && child.reservation !== void 0))) return [failure(ledgerError("recordChildSettled", `Child Submission ${request.childSubmissionId} has no recorded settlement`)), current];
1078
+ if (!announcedChildren.has(request.childSubmissionId)) return [failure(ledgerError("recordChildSettled", `Child Submission ${request.childSubmissionId} has no recorded settlement`)), current];
911
1079
  if (parent.row.state !== "suspended" || parent.suspension === void 0 || parent.suspension.reason._tag !== "WaitingForChild") return [success("not-waiting"), current];
912
1080
  const children = parent.suspension.reason.children;
913
1081
  if (!children.some((entry) => entry.childSubmissionId === request.childSubmissionId)) return [success("not-waiting"), current];
914
- if (!children.every((entry) => {
915
- const listed = current.submissions.get(entry.childSubmissionId);
916
- return listed?.row.state === "settled" || listed?.row.state === "terminalizing" && listed.reservation !== void 0;
917
- })) return [success("still-waiting"), current];
1082
+ if (!children.every((entry) => announcedChildren.has(entry.childSubmissionId))) return [success("still-waiting"), current];
918
1083
  return [success("woken"), withSubmission(current, {
919
1084
  ...parent,
920
1085
  row: {
@@ -926,8 +1091,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
926
1091
  });
927
1092
  if (decision._tag === "failure") return yield* decision.error;
928
1093
  return decision.value;
929
- }));
930
- const reserveChildBudget = Effect.fn("MemorySubmissionLedger.reserveChildBudget")((unvalidated) => Effect.gen(function* () {
1094
+ });
1095
+ const reserveChildBudget = Effect.fnUntraced(function* (unvalidated) {
931
1096
  const request = yield* validate(ChildBudgetReservationRequest, "reserveChildBudget", unvalidated);
932
1097
  const nowMillis = yield* Clock.currentTimeMillis;
933
1098
  const decision = yield* Ref.modify(state, (current) => {
@@ -943,14 +1108,18 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
943
1108
  replayed: true
944
1109
  })), current];
945
1110
  }
946
- for (const reservation of current.childReservations.values()) if (reservation.parentSubmissionId === request.parentSubmissionId && reservation.parentToolCallId === request.parentToolCallId) return [failure(ChildReservationConflict.make({
947
- reservationId: request.reservationId,
948
- status: reservation.status,
949
- message: `Parent Tool Call ${request.parentToolCallId} already owns reservation ${reservation.reservationId}.`
950
- })), current];
1111
+ for (const id of current.childrenByParent.get(request.parentSubmissionId) ?? []) {
1112
+ const reservation = current.childReservations.get(id);
1113
+ if (reservation === void 0) continue;
1114
+ if (reservation.parentSubmissionId === request.parentSubmissionId && reservation.parentToolCallId === request.parentToolCallId) return [failure(ChildReservationConflict.make({
1115
+ reservationId: request.reservationId,
1116
+ status: reservation.status,
1117
+ message: `Parent Tool Call ${request.parentToolCallId} already owns reservation ${reservation.reservationId}.`
1118
+ })), current];
1119
+ }
951
1120
  const parent = current.submissions.get(request.parentSubmissionId);
952
1121
  if (parent === void 0) return [failure(ledgerError("reserveChildBudget", `Unknown Submission ${request.parentSubmissionId}`)), current];
953
- if (!ownsLane(current, parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
1122
+ if (!journal.owner.hasOwner(parent.row.threadId, parent.row.submissionId)) return [failure(ownershipLost(parent)), current];
954
1123
  const reservation = {
955
1124
  reservationId: request.reservationId,
956
1125
  parentSubmissionId: request.parentSubmissionId,
@@ -971,8 +1140,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
971
1140
  });
972
1141
  if (decision._tag === "failure") return yield* decision.error;
973
1142
  return decision.value;
974
- }));
975
- const attachChildToReservation = Effect.fn("MemorySubmissionLedger.attachChildToReservation")((unvalidated) => Effect.gen(function* () {
1143
+ });
1144
+ const attachChildToReservation = Effect.fnUntraced(function* (unvalidated) {
976
1145
  const request = yield* validate(AttachChildToReservationRequest, "attachChildToReservation", unvalidated);
977
1146
  const decision = yield* Ref.modify(state, (current) => {
978
1147
  const reservation = current.childReservations.get(request.reservationId);
@@ -987,7 +1156,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
987
1156
  }
988
1157
  const parent = current.submissions.get(reservation.parentSubmissionId);
989
1158
  if (parent === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown Submission ${reservation.parentSubmissionId}`)), current];
990
- if (!ownsLane(current, parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
1159
+ if (!journal.owner.hasOwner(parent.row.threadId, parent.row.submissionId)) return [failure(ownershipLost(parent)), current];
991
1160
  if (reservation.status !== "reserved") return [failure(ChildReservationConflict.make({
992
1161
  reservationId: request.reservationId,
993
1162
  status: reservation.status,
@@ -1002,8 +1171,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
1002
1171
  });
1003
1172
  if (decision._tag === "failure") return yield* decision.error;
1004
1173
  return decision.value;
1005
- }));
1006
- const beginChildBudgetRelease = Effect.fn("MemorySubmissionLedger.beginChildBudgetRelease")((unvalidated) => Effect.gen(function* () {
1174
+ });
1175
+ const beginChildBudgetRelease = Effect.fnUntraced(function* (unvalidated) {
1007
1176
  const request = yield* validate(BeginChildBudgetReleaseRequest, "beginChildBudgetRelease", unvalidated);
1008
1177
  const nowMillis = yield* Clock.currentTimeMillis;
1009
1178
  const decision = yield* Ref.modify(state, (current) => {
@@ -1027,8 +1196,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
1027
1196
  });
1028
1197
  if (decision._tag === "failure") return yield* decision.error;
1029
1198
  return decision.value;
1030
- }));
1031
- const releaseChildBudget = Effect.fn("MemorySubmissionLedger.releaseChildBudget")((unvalidated) => Effect.gen(function* () {
1199
+ });
1200
+ const releaseChildBudget = Effect.fnUntraced(function* (unvalidated) {
1032
1201
  const request = yield* validate(ReleaseChildBudgetRequest, "releaseChildBudget", unvalidated);
1033
1202
  const nowMillis = yield* Clock.currentTimeMillis;
1034
1203
  const decision = yield* Ref.modify(state, (current) => {
@@ -1049,38 +1218,36 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
1049
1218
  });
1050
1219
  if (decision._tag === "failure") return yield* decision.error;
1051
1220
  return decision.value;
1221
+ });
1222
+ const scanNonterminal = Stream.paginate(void 0, (after) => Effect.sync(() => {
1223
+ const current = MutableRef.get(state.ref);
1224
+ const candidates = boundedWorkSelection(8, (stored) => `${stored.row.threadId}\u0000${String(stored.row.queueSequence).padStart(16, "0")}`, after);
1225
+ for (const ids of current.activeByThread.values()) for (const id of ids) {
1226
+ const stored = current.submissions.get(id);
1227
+ if (stored !== void 0) candidates.add(stored);
1228
+ }
1229
+ const items = candidates.values.map(({ row }) => SubmissionWorkItem.make(row));
1230
+ const last = candidates.values.at(-1)?.row;
1231
+ return [items, last === void 0 ? Option.none() : Option.some(`${last.threadId}\u0000${String(last.queueSequence).padStart(16, "0")}`)];
1052
1232
  }));
1053
- const scanNonterminal = Stream.unwrap(Ref.get(state).pipe(Effect.map((current) => {
1054
- const snapshots = [...current.submissions.values()].filter((stored) => stored.row.state !== "settled").sort((left, right) => left.row.threadId < right.row.threadId ? -1 : left.row.threadId > right.row.threadId ? 1 : left.row.queueSequence - right.row.queueSequence).map(({ row }) => SubmissionWorkItem.make({
1055
- submissionId: row.submissionId,
1056
- threadId: row.threadId,
1057
- queueSequence: row.queueSequence,
1058
- principal: row.principal,
1059
- idempotencyKey: row.idempotencyKey,
1060
- deploymentId: row.deploymentId,
1061
- receiptId: row.receiptId,
1062
- state: row.state
1063
- }));
1064
- return Stream.fromIterable(snapshots);
1065
- })));
1066
- const readAbortIntent = Effect.fn("MemorySubmissionLedger.readAbortIntent")(function* (unvalidated) {
1233
+ const readAbortIntent = Effect.fnUntraced(function* (unvalidated) {
1067
1234
  const request = yield* validate(AbortIntentRequest, "readAbortIntent", unvalidated);
1068
1235
  const stored = (yield* Ref.get(state)).submissions.get(request.submissionId);
1069
1236
  if (stored === void 0) return yield* ledgerError("readAbortIntent", `Unknown Submission ${request.submissionId}`);
1070
1237
  return stored.abortIntent;
1071
1238
  });
1072
- const loadRecoverySnapshot = Effect.fn("MemorySubmissionLedger.loadRecoverySnapshot")((unvalidated) => Effect.gen(function* () {
1239
+ const loadRecoverySnapshot = Effect.fnUntraced(function* (unvalidated) {
1073
1240
  const request = yield* validate(RecoverySnapshotRequest, "loadRecoverySnapshot", unvalidated);
1074
1241
  const current = yield* Ref.get(state);
1075
1242
  const stored = current.submissions.get(request.submissionId);
1076
1243
  if (stored === void 0) return yield* ledgerError("loadRecoverySnapshot", `Unknown Submission ${request.submissionId}`);
1077
- const joins = [...current.submissions.values()].filter((candidate) => candidate.joinedHostSubmissionId === request.submissionId).sort((left, right) => left.row.queueSequence - right.row.queueSequence).map((candidate) => JoinSnapshot.make({
1244
+ const joins = Array.from(current.joinsByHost.get(request.submissionId) ?? [], (id) => current.submissions.get(id)).filter((candidate) => candidate !== void 0).sort((left, right) => left.row.queueSequence - right.row.queueSequence).map((candidate) => JoinSnapshot.make({
1078
1245
  submissionId: candidate.row.submissionId,
1079
1246
  state: candidate.row.state,
1080
1247
  hostSubmissionId: request.submissionId
1081
1248
  }));
1082
1249
  const byToolCallId = (left, right) => left.toolCallId < right.toolCallId ? -1 : left.toolCallId > right.toolCallId ? 1 : 0;
1083
- const childReservations = [...current.childReservations.values()].filter((reservation) => reservation.parentSubmissionId === request.submissionId).sort((left, right) => left.parentToolCallId < right.parentToolCallId ? -1 : left.parentToolCallId > right.parentToolCallId ? 1 : 0);
1250
+ const childReservations = Array.from(current.childrenByParent.get(request.submissionId) ?? [], (id) => current.childReservations.get(id)).filter((reservation) => reservation !== void 0).sort((left, right) => left.parentToolCallId < right.parentToolCallId ? -1 : left.parentToolCallId > right.parentToolCallId ? 1 : 0);
1084
1251
  const childAttachments = [];
1085
1252
  for (const reservation of childReservations) {
1086
1253
  if (reservation.childSubmissionId === void 0) continue;
@@ -1106,60 +1273,277 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
1106
1273
  reason: stored.suspension.reason,
1107
1274
  suspendedAt: utc(stored.suspension.suspendedAtMillis)
1108
1275
  }) },
1109
- ...stored.ownership === void 0 ? {} : { ownership: OwnershipSnapshot.make({
1110
- attemptId: stored.ownership.attemptId,
1111
- ownerProducerId: stored.ownership.ownerProducerId,
1112
- producerEpoch: stored.ownership.producerEpoch,
1113
- leaseExpiresAt: utc(stored.ownership.leaseExpiresAtMillis)
1114
- }) },
1276
+ hasOwner: journal.owner.hasOwner(stored.row.threadId, stored.row.submissionId),
1115
1277
  ...stored.inputApplied === void 0 ? {} : { inputApplied: stored.inputApplied },
1116
- ...stored.reservation === void 0 ? {} : { reservation: SettlementReservationSnapshot.make({
1117
- settlementId: stored.reservation.settlementId,
1118
- outcome: stored.reservation.outcome,
1119
- record: stored.reservation.record,
1120
- recordDigest: stored.reservation.recordDigest,
1121
- finalized: stored.reservation.finalizedAtMillis !== void 0
1122
- }) },
1123
1278
  ...stored.abortIntent === void 0 ? {} : { abortIntent: stored.abortIntent }
1124
1279
  });
1125
- }));
1126
- return SubmissionLedger.of({
1280
+ });
1281
+ const ledger = SubmissionLedger.of({
1127
1282
  capabilities,
1128
- admit,
1129
- markReady,
1283
+ resolveFundingOwner: Effect.fnUntraced(function* (unvalidated) {
1284
+ const request = yield* validate(FundingOwnerRequest, "funding owner", unvalidated);
1285
+ const current = MutableRef.get(state.ref);
1286
+ const selected = current.submissions.get(request.submissionId);
1287
+ if (selected === void 0 || selected.row.threadId !== request.threadId) return yield* ledgerError("funding owner", "Selected admission belongs to another Thread or is missing");
1288
+ if (selected.row.state === "joining" || selected.joinedHostSubmissionId !== void 0 && selected.inputApplied === void 0) return yield* ledgerError("funding owner", "Joining funding owner is not yet frozen by exact applied input evidence");
1289
+ const owner = selected.joinedHostSubmissionId === void 0 ? selected : current.submissions.get(selected.joinedHostSubmissionId);
1290
+ if (owner === void 0 || owner.row.threadId !== request.threadId || owner !== selected && owner.row.queueSequence >= selected.row.queueSequence) return yield* ledgerError("funding owner", "Exact joined funding owner is missing or inconsistent");
1291
+ return FundingOwner.make({
1292
+ selected: toSnapshot(selected.row),
1293
+ owner: toSnapshot(owner.row)
1294
+ });
1295
+ }),
1296
+ admit: (request) => journal.withMutation(admit(request)),
1297
+ markReady: (request) => journal.withMutation(markReady(request)),
1130
1298
  lookup,
1131
1299
  resolveAdmission,
1132
- claim,
1133
- renewOwnership,
1134
- releaseOwnership,
1135
- markInputApplied,
1136
- reserveSettlement,
1137
- finalizeSettlement,
1138
- requestAbort,
1139
- stopWorker,
1300
+ selectHead: (request) => journal.withMutation(selectHead(request)),
1301
+ markInputApplied: (request) => journal.withMutation(markInputApplied(request)),
1302
+ finalizeSettlement: (request) => journal.withMutation(finalizeSettlement(request)),
1303
+ requestAbort: (request) => journal.withMutation(requestAbort(request)),
1304
+ stopWorker: (request) => journal.withMutation(stopWorker(request)),
1140
1305
  inspectWorker,
1141
- claimJoining,
1142
- markJoined,
1143
- revertJoining,
1144
- suspend,
1145
- recordApprovalDecision,
1146
- markUnknown,
1147
- recordUnknownResolution,
1148
- recordChildSettled,
1149
- reserveChildBudget,
1150
- attachChildToReservation,
1151
- beginChildBudgetRelease,
1152
- releaseChildBudget,
1306
+ claimJoining: (request) => journal.withMutation(claimJoining(request)),
1307
+ markJoined: (request) => journal.withMutation(markJoined(request)),
1308
+ revertJoining: (request) => journal.withMutation(revertJoining(request)),
1309
+ suspend: (request) => journal.withMutation(suspend(request)),
1310
+ recordApprovalDecision: (request) => journal.withMutation(recordApprovalDecision(request)),
1311
+ markUnknown: (request) => journal.withMutation(markUnknown(request)),
1312
+ recordUnknownResolution: (request) => journal.withMutation(recordUnknownResolution(request)),
1313
+ recordChildSettled: (request) => journal.withMutation(recordChildSettled(request)),
1314
+ reserveChildBudget: (request) => journal.withMutation(reserveChildBudget(request)),
1315
+ attachChildToReservation: (request) => journal.withMutation(attachChildToReservation(request)),
1316
+ beginChildBudgetRelease: (request) => journal.withMutation(beginChildBudgetRelease(request)),
1317
+ releaseChildBudget: (request) => journal.withMutation(releaseChildBudget(request)),
1153
1318
  scanNonterminal,
1154
1319
  loadRecoverySnapshot,
1155
1320
  readAbortIntent
1156
1321
  });
1322
+ const toAdmission = (row, settledAtMillis) => ThreadAdmission.make({
1323
+ ...toSnapshot(row),
1324
+ ...settledAtMillis === void 0 ? {} : { settledAt: utc(settledAtMillis) }
1325
+ });
1326
+ const cleanCommand = (value) => {
1327
+ const { canonicalRecordId: _, ...fact } = value;
1328
+ return fact;
1329
+ };
1330
+ const commands = (stored) => {
1331
+ return {
1332
+ aborts: stored?.abortIntent === void 0 ? [] : [cleanCommand(stored.abortIntent)],
1333
+ approvals: Array.from(stored?.approvalDecisions.values() ?? [], cleanCommand),
1334
+ resolutions: Array.from(stored?.unknownResolutions.values() ?? [], ({ intent }) => cleanCommand(intent))
1335
+ };
1336
+ };
1337
+ const readCommands = Effect.fnUntraced(function* (stored) {
1338
+ if ((stored?.approvalDecisions.size ?? 0) > MAX_RUN_EVIDENCE_RECORDS || (stored?.unknownResolutions.size ?? 0) > MAX_RUN_EVIDENCE_RECORDS) return yield* ThreadStoreError.make({
1339
+ operation: "read commands",
1340
+ message: "Accepted commands exceed Run record bounds"
1341
+ });
1342
+ let bytes = 0;
1343
+ const add = (schema, value) => Schema.encodeEffect(Schema.toCodecJson(schema))(value).pipe(Effect.mapError(() => ThreadStoreError.make({
1344
+ operation: "read commands",
1345
+ message: "Invalid accepted command wire"
1346
+ })), Effect.flatMap((wire) => {
1347
+ bytes += utf8ByteLength(canonicalJson(wire));
1348
+ return bytes > MAX_RUN_EVIDENCE_BYTES ? Effect.fail(ThreadStoreError.make({
1349
+ operation: "read commands",
1350
+ message: "Accepted commands exceed Run byte bounds"
1351
+ })) : Effect.void;
1352
+ }));
1353
+ if (stored?.abortIntent !== void 0) yield* add(AbortIntent, stored.abortIntent);
1354
+ for (const value of stored?.approvalDecisions.values() ?? []) yield* add(ApprovalDecisionIntent, value);
1355
+ for (const { intent } of stored?.unknownResolutions.values() ?? []) yield* add(UnknownResolutionIntent, intent);
1356
+ return commands(stored);
1357
+ });
1358
+ const lookupAdmission = (threadId, submissionId) => Effect.sync(() => {
1359
+ const stored = MutableRef.get(state.ref).submissions.get(submissionId);
1360
+ return stored?.row.threadId === threadId ? toAdmission(stored.row, stored.finalization?.finalizedAtMillis) : void 0;
1361
+ });
1362
+ yield* journal.registerLedgerTransfer({
1363
+ admission: lookupAdmission,
1364
+ commands: (id) => readCommands(MutableRef.get(state.ref).submissions.get(id)),
1365
+ snapshot: (threadId) => Effect.sync(() => {
1366
+ const current = MutableRef.get(state.ref);
1367
+ const directory = current.byThread.get(threadId);
1368
+ return {
1369
+ idle: (current.activeByThread.get(threadId)?.size ?? 0) === 0 && (directory?.reservations ?? 0) === 0,
1370
+ admissions: directory?.ordered.length ?? 0,
1371
+ aborts: directory?.commands.aborts.length ?? 0,
1372
+ approvals: directory?.commands.approvals.length ?? 0,
1373
+ resolutions: directory?.commands.resolutions.length ?? 0,
1374
+ ...current.stoppedWorkers.has(threadId) ? { workerSeal: current.stoppedWorkers.get(threadId) === void 0 ? {} : { terminal: current.stoppedWorkers.get(threadId) } } : {}
1375
+ };
1376
+ }),
1377
+ facts: (threadId, section, after) => Effect.sync(() => {
1378
+ const current = MutableRef.get(state.ref);
1379
+ const facts = {
1380
+ admissions: new Array(),
1381
+ commands: commands(void 0),
1382
+ deliveries: []
1383
+ };
1384
+ let next = after ?? "";
1385
+ if (section === "admissions") {
1386
+ const stored = threadRows(current, threadId, after === void 0 ? -1 : Number(after)).next().value;
1387
+ if (stored !== void 0) {
1388
+ facts.admissions.push(toAdmission(stored.row, stored.finalization?.finalizedAtMillis));
1389
+ next = String(stored.row.queueSequence);
1390
+ }
1391
+ } else {
1392
+ const entries = current.byThread.get(threadId)?.commands[section] ?? [];
1393
+ const at = after === void 0 ? 0 : commandPosition(entries, after);
1394
+ const choice = entries[at + Number(after !== void 0 && entries[at]?.key === after)];
1395
+ if (choice !== void 0) {
1396
+ next = choice.key;
1397
+ const stored = current.submissions.get(choice.submissionId);
1398
+ if (section === "aborts" && stored?.abortIntent !== void 0) facts.commands.aborts.push(cleanCommand(stored.abortIntent));
1399
+ else if (section === "approvals" && choice.toolCallId !== void 0) {
1400
+ const value = stored?.approvalDecisions.get(choice.toolCallId);
1401
+ if (value !== void 0) facts.commands.approvals.push(cleanCommand(value));
1402
+ } else if (section === "resolutions" && choice.resolutionKey !== void 0) {
1403
+ const value = stored?.unknownResolutions.get(choice.resolutionKey);
1404
+ if (value !== void 0) facts.commands.resolutions.push(cleanCommand(value.intent));
1405
+ }
1406
+ }
1407
+ }
1408
+ return {
1409
+ facts,
1410
+ after: next
1411
+ };
1412
+ }),
1413
+ submissions: (threadId) => Stream.paginate(-1, (after) => Effect.sync(() => {
1414
+ const stored = threadRows(MutableRef.get(state.ref), threadId, after).next().value;
1415
+ return [stored === void 0 ? [] : [toSnapshot(stored.row)], stored === void 0 ? Option.none() : Option.some(stored.row.queueSequence)];
1416
+ })),
1417
+ startImport: Effect.fnUntraced(function* (threadId) {
1418
+ const current = yield* Ref.get(state);
1419
+ if (current.lanes.has(threadId) || current.byThread.has(threadId) || current.stoppedWorkers.has(threadId)) return yield* ThreadImportRejected.make({
1420
+ threadId,
1421
+ reason: "target-not-empty",
1422
+ message: "Destination ledger contains Thread data"
1423
+ });
1424
+ const staged = {
1425
+ submissions: /* @__PURE__ */ new Map(),
1426
+ admissionIndex: /* @__PURE__ */ new Map(),
1427
+ lanes: /* @__PURE__ */ new Map(),
1428
+ childReservations: /* @__PURE__ */ new Map(),
1429
+ freshAdmissionSequence: current.freshAdmissionSequence,
1430
+ stoppedWorkers: /* @__PURE__ */ new Map(),
1431
+ latestByThread: /* @__PURE__ */ new Map(),
1432
+ activeByThread: /* @__PURE__ */ new Map(),
1433
+ byThread: /* @__PURE__ */ new Map(),
1434
+ receipts: /* @__PURE__ */ new Map(),
1435
+ joinsByHost: /* @__PURE__ */ new Map(),
1436
+ childrenByParent: /* @__PURE__ */ new Map()
1437
+ };
1438
+ const commandBytes = /* @__PURE__ */ new Map();
1439
+ const settledTimes = /* @__PURE__ */ new Map();
1440
+ const retainCommandBytes = Effect.fnUntraced(function* (schema, value, id) {
1441
+ const wire = yield* Schema.encodeEffect(Schema.toCodecJson(schema))(value).pipe(Effect.mapError(() => invalidThreadArchive("Invalid accepted command wire", threadId)));
1442
+ const bytes = (commandBytes.get(id) ?? 0) + utf8ByteLength(canonicalJson(wire));
1443
+ if (bytes > MAX_RUN_EVIDENCE_BYTES) return yield* invalidThreadArchive("Accepted commands exceed Run byte bounds", threadId);
1444
+ commandBytes.set(id, bytes);
1445
+ });
1446
+ return {
1447
+ reader: {
1448
+ admission: (id) => Effect.sync(() => {
1449
+ const stored = staged.submissions.get(id);
1450
+ return stored === void 0 ? void 0 : toAdmission(stored.row, settledTimes.get(id));
1451
+ }),
1452
+ runOwner: (runId) => Effect.sync(() => {
1453
+ if (!runId.startsWith("run:")) return void 0;
1454
+ const id = Schema.decodeSync(SubmissionId)(runId.slice(4));
1455
+ const stored = staged.submissions.get(id);
1456
+ return stored === void 0 ? void 0 : toAdmission(stored.row, settledTimes.get(id));
1457
+ }),
1458
+ commands: (id) => readCommands(staged.submissions.get(id))
1459
+ },
1460
+ stage: Effect.fnUntraced(function* (page) {
1461
+ for (const a of page.archive.admissions) {
1462
+ const key = admissionKey(threadId, a.principal, a.idempotencyKey);
1463
+ if (current.submissions.has(a.submissionId) || staged.submissions.has(a.submissionId) || current.receipts.has(a.receiptId) || staged.receipts.has(a.receiptId) || current.admissionIndex.has(key) || staged.admissionIndex.has(key)) return yield* ThreadImportRejected.make({
1464
+ threadId,
1465
+ reason: "destination-conflict",
1466
+ message: "Admission, receipt, or immutable admission key already exists"
1467
+ });
1468
+ if (threadRows(staged, threadId, a.queueSequence - 1).next().value?.row.queueSequence === a.queueSequence) return yield* invalidThreadArchive("Duplicate admission queue position", threadId);
1469
+ if (a.settledAt !== void 0) settledTimes.set(a.submissionId, DateTime.toEpochMillis(a.settledAt));
1470
+ withSubmission(staged, fromRebuilt(RebuiltSubmission.make({
1471
+ admission: a,
1472
+ state: "admitted",
1473
+ unknownToolCallIds: [],
1474
+ approvals: [],
1475
+ resolutions: []
1476
+ })));
1477
+ }
1478
+ for (const fact of page.archive.commands.aborts) {
1479
+ const stored = staged.submissions.get(fact.submissionId);
1480
+ if (stored === void 0 || stored.abortIntent !== void 0) return yield* invalidThreadArchive("Missing admission or duplicate abort", threadId);
1481
+ const intent = AbortIntent.make(fact);
1482
+ yield* retainCommandBytes(AbortIntent, intent, fact.submissionId);
1483
+ withSubmission(staged, {
1484
+ ...stored,
1485
+ abortIntent: intent
1486
+ });
1487
+ }
1488
+ for (const fact of page.archive.commands.approvals) {
1489
+ const stored = staged.submissions.get(fact.submissionId);
1490
+ if (stored === void 0 || stored.approvalDecisions.has(fact.toolCallId)) return yield* invalidThreadArchive("Missing admission or duplicate approval", threadId);
1491
+ if (stored.approvalDecisions.size >= MAX_RUN_EVIDENCE_RECORDS) return yield* invalidThreadArchive("Accepted approvals exceed Run record bounds", threadId);
1492
+ const intent = ApprovalDecisionIntent.make(fact);
1493
+ yield* retainCommandBytes(ApprovalDecisionIntent, intent, fact.submissionId);
1494
+ const approvals = new Map(stored.approvalDecisions).set(fact.toolCallId, intent);
1495
+ withSubmission(staged, {
1496
+ ...stored,
1497
+ approvalDecisions: approvals
1498
+ });
1499
+ }
1500
+ for (const fact of page.archive.commands.resolutions) {
1501
+ const stored = staged.submissions.get(fact.submissionId);
1502
+ const key = JSON.stringify([fact.toolCallId, unknownResolutionKind(fact.resolution)]);
1503
+ if (stored === void 0 || stored.unknownResolutions.has(key)) return yield* invalidThreadArchive("Missing admission or duplicate resolution", threadId);
1504
+ if (stored.unknownResolutions.size >= MAX_RUN_EVIDENCE_RECORDS) return yield* invalidThreadArchive("Accepted resolutions exceed Run record bounds", threadId);
1505
+ const intent = UnknownResolutionIntent.make(fact);
1506
+ yield* retainCommandBytes(UnknownResolutionIntent, intent, fact.submissionId);
1507
+ const resolutions = new Map(stored.unknownResolutions).set(key, { intent });
1508
+ withSubmission(staged, {
1509
+ ...stored,
1510
+ unknownResolutions: resolutions
1511
+ });
1512
+ }
1513
+ }),
1514
+ prepareCommit: Effect.fnUntraced(function* (workerSeal) {
1515
+ let lastQueue = 0;
1516
+ let terminalEvidence = workerSeal?.terminal === void 0;
1517
+ for (const stored of threadRows(staged, threadId)) {
1518
+ const a = toAdmission(stored.row, settledTimes.get(stored.row.submissionId));
1519
+ const rebuilt = yield* rebuildClosedSubmission(a);
1520
+ if (rebuilt.settlement !== void 0 && workerTerminalFromRecord(toSnapshot(stored.row), rebuilt.settlement) === workerSeal?.terminal) terminalEvidence = true;
1521
+ withSubmission(staged, fromRebuilt(rebuilt));
1522
+ lastQueue = Math.max(lastQueue, a.queueSequence);
1523
+ }
1524
+ if (!terminalEvidence) return yield* invalidThreadArchive("Worker seal terminal has no exact completed input evidence", threadId);
1525
+ if (!Number.isSafeInteger(lastQueue + 1)) return yield* ThreadImportRejected.make({
1526
+ threadId,
1527
+ reason: "unsupported-capacity",
1528
+ message: "Admission queue sequence is exhausted"
1529
+ });
1530
+ return () => {
1531
+ for (const stored of staged.submissions.values()) withSubmission(current, stored);
1532
+ current.lanes.set(threadId, { nextQueueSequence: lastQueue + 1 });
1533
+ if (workerSeal !== void 0) current.stoppedWorkers.set(threadId, workerSeal.terminal);
1534
+ };
1535
+ })
1536
+ };
1537
+ })
1538
+ });
1539
+ return Context.make(SubmissionLedger, ledger).pipe(Context.add(SettlementPublisher, { publish }));
1157
1540
  });
1158
1541
  /**
1159
1542
  * In-memory reference SubmissionLedger Layer (durability `non-durable`). All state lives in one
1160
- * `Ref` owned by the Layer's Scope; no daemon fibers are spawned and no wall clock is consulted.
1543
+ * `Ref` owned by the Layer's Scope. Fresh identities use the supplied `Crypto` service;
1544
+ * imported identities remain unchanged. No daemon fibers are spawned and no wall clock is consulted.
1161
1545
  */
1162
- const memorySubmissionLedgerLayer = (options = {}) => Layer.effect(SubmissionLedger, makeSubmissionLedger(options));
1546
+ const memorySubmissionLedgerLayer = (options = {}) => Layer.effectContext(makeSubmissionLedger(options));
1163
1547
  const MemorySubmissionLedgerLive = memorySubmissionLedgerLayer();
1164
1548
  //#endregion
1165
1549
  export { MemorySubmissionLedgerLive, memorySubmissionLedgerLayer, MemorySubmissionLedger_exports as t };