@yielded/agent-storage-memory 0.1.0-beta.166 → 0.1.0-beta.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MemoryMessageDeliveryStore.d.mts +7 -3
- package/dist/MemoryMessageDeliveryStore.mjs +427 -64
- package/dist/MemoryMessageDeliveryStore.mjs.map +1 -1
- package/dist/MemoryScheduleStore.mjs +8 -8
- package/dist/MemoryScheduleStore.mjs.map +1 -1
- package/dist/MemorySemanticIndex.mjs +7 -9
- package/dist/MemorySemanticIndex.mjs.map +1 -1
- package/dist/MemorySubmissionLedger.d.mts +7 -4
- package/dist/MemorySubmissionLedger.mjs +761 -377
- package/dist/MemorySubmissionLedger.mjs.map +1 -1
- package/dist/MemorySubscriptionStore.mjs +23 -23
- package/dist/MemorySubscriptionStore.mjs.map +1 -1
- package/dist/MemoryThreadStore.d.mts +8 -4
- package/dist/MemoryThreadStore.mjs +1325 -277
- package/dist/MemoryThreadStore.mjs.map +1 -1
- package/dist/MemoryThreadStoreKernel-DYNDjlhO.d.mts +85 -0
- package/dist/WorkSelection-CmqTuws3.mjs +31 -0
- package/dist/WorkSelection-CmqTuws3.mjs.map +1 -0
- package/package.json +1 -1
- package/src/MemoryMessageDeliveryStore.ts +766 -230
- package/src/MemoryScheduleStore.ts +190 -200
- package/src/MemorySemanticIndex.ts +94 -95
- package/src/MemorySubmissionLedger.ts +1955 -1245
- package/src/MemorySubscriptionStore.ts +1122 -1117
- package/src/MemoryThreadStore.ts +2609 -628
- package/src/internal/MemoryThreadStoreKernel.ts +156 -0
- package/src/internal/WorkSelection.ts +29 -0
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import {
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
132
|
-
|
|
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.
|
|
139
|
-
*
|
|
140
|
-
*
|
|
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
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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 &&
|
|
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 (
|
|
234
|
-
const lane = current.lanes.get(request.threadId) ?? {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
const
|
|
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
|
|
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
|
|
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
|
|
422
|
+
const stored = {
|
|
262
423
|
row,
|
|
263
|
-
ownership: void 0,
|
|
264
424
|
inputApplied: void 0,
|
|
265
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
307
|
-
const markReady = Effect.
|
|
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.
|
|
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.
|
|
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
|
|
498
|
+
});
|
|
499
|
+
const selectHead = Effect.fn("MemorySubmissionLedger.selectHead")((unvalidated) => Effect.gen(function* () {
|
|
344
500
|
const request = yield* validate(ClaimRequest, "claim", unvalidated);
|
|
345
|
-
const
|
|
501
|
+
const identity = yield* mintIdentity("claim");
|
|
346
502
|
const decision = yield* Ref.modify(state, (current) => {
|
|
347
|
-
|
|
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
|
-
|
|
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 !== "
|
|
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:
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
466
|
-
const
|
|
467
|
-
|
|
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
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
})
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
522
|
-
if (
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
544
|
-
|
|
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)
|
|
548
|
-
|
|
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:
|
|
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:
|
|
675
|
+
settledOutcome: settlement.outcome
|
|
569
676
|
},
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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:
|
|
685
|
+
settlementId: settlement.settlementId,
|
|
579
686
|
receiptId: stored.row.receiptId,
|
|
580
|
-
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.
|
|
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.
|
|
712
|
+
const stopWorker = Effect.fnUntraced(function* (unvalidated) {
|
|
605
713
|
const request = yield* validate(WorkerStopCommand, "stopWorker", unvalidated);
|
|
606
714
|
const now = yield* Clock.currentTimeMillis;
|
|
607
|
-
|
|
608
|
-
const submissions =
|
|
715
|
+
const decision = yield* Ref.modify(state, (current) => {
|
|
716
|
+
const submissions = current.submissions;
|
|
609
717
|
let owned = 0;
|
|
610
|
-
|
|
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.
|
|
614
|
-
if (stored.abortIntent === void 0)
|
|
615
|
-
|
|
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 :
|
|
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.
|
|
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.
|
|
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 (!
|
|
674
|
-
const later =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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 (!
|
|
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.
|
|
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.
|
|
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 (
|
|
907
|
+
if (canonical !== void 0) return [failure(SettlementConflict.make({
|
|
764
908
|
submissionId: request.submissionId,
|
|
765
|
-
existingOutcome:
|
|
909
|
+
existingOutcome: canonical.settlement.outcome
|
|
766
910
|
})), current];
|
|
767
|
-
if (!
|
|
768
|
-
if (request.reason._tag === "ApprovalPending" ? request.reason.toolCallIds
|
|
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.
|
|
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
|
|
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.
|
|
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 (
|
|
989
|
+
if (canonical !== void 0) return [failure(SettlementConflict.make({
|
|
843
990
|
submissionId: request.submissionId,
|
|
844
|
-
existingOutcome:
|
|
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.
|
|
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
|
|
877
|
-
|
|
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
|
-
|
|
890
|
-
const
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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 (!
|
|
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.
|
|
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 (!
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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 = [
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
1280
|
+
});
|
|
1281
|
+
const ledger = SubmissionLedger.of({
|
|
1127
1282
|
capabilities,
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
|
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.
|
|
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 };
|