effect-agent 0.1.0-beta.113 → 0.1.0-beta.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/Agent-1DMSO_Fl.mjs.map +1 -1
  2. package/dist/{Agent-DriBcXBN.d.mts → Agent-D9cXqfF3.d.mts} +9 -2
  3. package/dist/{AgentRuntime-DLwPoEVH.mjs → AgentRuntime-Cp1hAb-W.mjs} +8 -4
  4. package/dist/AgentRuntime-Cp1hAb-W.mjs.map +1 -0
  5. package/dist/{AgentRuntime-CWkHuzyt.d.mts → AgentRuntime-DyZv8uu2.d.mts} +6 -6
  6. package/dist/{DurableAgentRuntime-BIrgxwRl.mjs → DurableAgentRuntime-tNrkJZNo.mjs} +254 -162
  7. package/dist/DurableAgentRuntime-tNrkJZNo.mjs.map +1 -0
  8. package/dist/{DurableFailpoint-mx3-nsxU.d.mts → DurableFailpoint-Cnr3wx1L.d.mts} +3 -3
  9. package/dist/{EventSource-ARs56eBq.d.mts → EventSource-DPRdlvhV.d.mts} +3 -3
  10. package/dist/{Messaging-DaZDlBqh.d.mts → Messaging-JYYN3oYS.d.mts} +3 -3
  11. package/dist/{MessagingHost-e1Sbzfao.d.mts → MessagingHost-CqzpxbGz.d.mts} +3 -3
  12. package/dist/{MessagingHost-D89X7ma8.mjs → MessagingHost-DfsbMPvq.mjs} +2 -2
  13. package/dist/{MessagingHost-D89X7ma8.mjs.map → MessagingHost-DfsbMPvq.mjs.map} +1 -1
  14. package/dist/{Output-CAFJTAbP.d.mts → Output-V-6YL1_Z.d.mts} +2 -2
  15. package/dist/{PreparedInputAdmission-_WYg-D0f.d.mts → PreparedInputAdmission-DqQUjA9k.d.mts} +4 -4
  16. package/dist/{Records-BtxMy8xD.d.mts → Records-Byeb70tt.d.mts} +89 -31
  17. package/dist/{RunOptions-BoESJrxw.d.mts → RunOptions-CSBsSj0X.d.mts} +3 -3
  18. package/dist/{Schedule-DGPyXBzK.d.mts → Schedule-krSnBcbU.d.mts} +2 -2
  19. package/dist/{Subagent-D9eYdGTl.mjs → Subagent-Di8EEjWZ.mjs} +67 -105
  20. package/dist/Subagent-Di8EEjWZ.mjs.map +1 -0
  21. package/dist/{Subagent-F72tuaxy.d.mts → Subagent-gw9KD6eb.d.mts} +310 -35
  22. package/dist/{SubagentHost-CmiNTOBZ.d.mts → SubagentHost-P9zB_TfV.d.mts} +24 -18
  23. package/dist/{Subscription-B9VszlF9.d.mts → Subscription-Xnwd42Sm.d.mts} +6 -3
  24. package/dist/{SubscriptionInput-BJWZqCWP.d.mts → SubscriptionInput-CqtJ58vl.d.mts} +3 -3
  25. package/dist/{Worker-T_3RbaKc.mjs → Worker-CfwzMZEK.mjs} +66 -5
  26. package/dist/Worker-CfwzMZEK.mjs.map +1 -0
  27. package/dist/{Worker-Du9dhVnp.d.mts → Worker-fwWkBg12.d.mts} +199 -6
  28. package/dist/{agent-registration-B5CI-Ygk.mjs → agent-registration-CKNsrpwo.mjs} +7 -7
  29. package/dist/agent-registration-CKNsrpwo.mjs.map +1 -0
  30. package/dist/capabilities/Commands.d.mts +1 -1
  31. package/dist/capabilities/Messaging.d.mts +1 -1
  32. package/dist/capabilities/Messaging.mjs +2 -2
  33. package/dist/capabilities/RunHooks.d.mts +1 -1
  34. package/dist/capabilities/Subagent.d.mts +3 -3
  35. package/dist/capabilities/Subagent.mjs +3 -3
  36. package/dist/core/Agent.d.mts +1 -1
  37. package/dist/core/Worker.d.mts +2 -2
  38. package/dist/core/Worker.mjs +2 -2
  39. package/dist/durable/ActivityStore.d.mts +1 -1
  40. package/dist/durable/Admin.d.mts +1 -1
  41. package/dist/durable/AgentRegistration.d.mts +1 -1
  42. package/dist/durable/AgentRegistration.mjs +1 -1
  43. package/dist/durable/Certification.d.mts +2 -2
  44. package/dist/durable/CommittedActivity.d.mts +1 -1
  45. package/dist/durable/Digest.d.mts +1 -1
  46. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  47. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  48. package/dist/durable/DurableFailpoint.d.mts +1 -1
  49. package/dist/durable/DurableFailpoint.mjs +4 -0
  50. package/dist/durable/DurableFailpoint.mjs.map +1 -1
  51. package/dist/durable/DurableFailpointTestControl.d.mts +1 -1
  52. package/dist/durable/EventSource.d.mts +1 -1
  53. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  54. package/dist/durable/MessageDelivery.d.mts +20 -3
  55. package/dist/durable/MessageDelivery.mjs +12 -4
  56. package/dist/durable/MessageDelivery.mjs.map +1 -1
  57. package/dist/durable/MessageDeliveryStoreConformance.d.mts +41 -39
  58. package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
  59. package/dist/durable/PersistentHistory.d.mts +1 -1
  60. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  61. package/dist/durable/Records.d.mts +2 -2
  62. package/dist/durable/Records.mjs +12 -5
  63. package/dist/durable/Records.mjs.map +1 -1
  64. package/dist/durable/Recovery.d.mts +1 -1
  65. package/dist/durable/RunJournal.d.mts +1 -1
  66. package/dist/durable/Schedule.d.mts +1 -1
  67. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  68. package/dist/durable/ScheduleTransition.d.mts +1 -1
  69. package/dist/durable/Scheduling.d.mts +2 -2
  70. package/dist/durable/SqlMessageDeliveryStore.mjs +7 -1
  71. package/dist/durable/SqlMessageDeliveryStore.mjs.map +1 -1
  72. package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
  73. package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
  74. package/dist/durable/SqlThreadNativeReads.d.mts +5 -1
  75. package/dist/durable/SqlThreadNativeReads.mjs +6 -0
  76. package/dist/durable/SqlThreadNativeReads.mjs.map +1 -1
  77. package/dist/durable/SubmissionLedger.d.mts +2 -2
  78. package/dist/durable/SubmissionLedger.mjs +34 -4
  79. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  80. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  81. package/dist/durable/SubmissionLedgerConformance.mjs +899 -706
  82. package/dist/durable/SubmissionLedgerConformance.mjs.map +1 -1
  83. package/dist/durable/SubmissionStatus.d.mts +1 -1
  84. package/dist/durable/Subscription.d.mts +1 -1
  85. package/dist/durable/Subscription.mjs +1 -1
  86. package/dist/durable/SubscriptionInput.d.mts +1 -1
  87. package/dist/durable/SubscriptionInput.mjs +1 -1
  88. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  89. package/dist/durable/SubscriptionTools.d.mts +3 -3
  90. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  91. package/dist/durable/Subscriptions.d.mts +5 -5
  92. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  93. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  94. package/dist/durable/ThreadInvariants.d.mts +1 -1
  95. package/dist/durable/ThreadProjection.d.mts +1 -1
  96. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  97. package/dist/durable/ThreadStore.d.mts +1 -1
  98. package/dist/durable/ThreadStore.mjs +5 -0
  99. package/dist/durable/ThreadStore.mjs.map +1 -1
  100. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  101. package/dist/durable/WorkerHost.d.mts +3 -3
  102. package/dist/durable/WorkerHost.mjs +1 -1
  103. package/dist/engine/AgentRuntime.d.mts +1 -1
  104. package/dist/engine/AgentRuntime.mjs +1 -1
  105. package/dist/engine/MessagingHost.d.mts +1 -1
  106. package/dist/engine/MessagingHost.mjs +1 -1
  107. package/dist/engine/Output.d.mts +1 -1
  108. package/dist/engine/RunOptions.d.mts +1 -1
  109. package/dist/engine/SubagentHost.d.mts +2 -2
  110. package/dist/engine/SubagentHost.mjs +5 -1
  111. package/dist/engine/SubagentHost.mjs.map +1 -1
  112. package/dist/index.d.mts +16 -16
  113. package/dist/index.mjs +5 -5
  114. package/package.json +1 -1
  115. package/src/capabilities/Subagent.ts +2 -1
  116. package/src/capabilities/internal/subagent-background.ts +86 -57
  117. package/src/capabilities/internal/subagent-reporting.ts +3 -128
  118. package/src/core/Agent.ts +8 -1
  119. package/src/core/Worker.ts +58 -2
  120. package/src/durable/DurableAgentRuntime.ts +2 -0
  121. package/src/durable/DurableFailpoint.ts +4 -0
  122. package/src/durable/MessageDelivery.ts +17 -2
  123. package/src/durable/Records.ts +12 -2
  124. package/src/durable/SqlMessageDeliveryStore.ts +15 -1
  125. package/src/durable/SqlThreadNativeReads.ts +14 -0
  126. package/src/durable/SubmissionLedger.ts +48 -1
  127. package/src/durable/SubmissionLedgerConformance.ts +260 -42
  128. package/src/durable/ThreadStore.ts +5 -0
  129. package/src/durable/internal/agent-registration.ts +13 -48
  130. package/src/durable/internal/worker-host.ts +490 -341
  131. package/src/engine/SubagentHost.ts +37 -13
  132. package/src/engine/internal/agent-runtime.ts +17 -2
  133. package/dist/AgentRuntime-DLwPoEVH.mjs.map +0 -1
  134. package/dist/DurableAgentRuntime-BIrgxwRl.mjs.map +0 -1
  135. package/dist/Subagent-D9eYdGTl.mjs.map +0 -1
  136. package/dist/Worker-T_3RbaKc.mjs.map +0 -1
  137. package/dist/agent-registration-B5CI-Ygk.mjs.map +0 -1
@@ -2,11 +2,12 @@ import { AgentId, DelegationId, SubmissionId, ThreadId, ToolCallId } from "../co
2
2
  import { AgentPolicy } from "../core/AgentPolicy.mjs";
3
3
  import { IdempotencyKey, JoinedToHost, Principal } from "../core/Receipt.mjs";
4
4
  import { SubagentDelegationCaps, SubagentGrant, SubagentReservationAmounts } from "../core/SubagentContract.mjs";
5
- import { p as WorkerUpdate, r as WorkerCompletion } from "../Worker-T_3RbaKc.mjs";
6
- import { d as MessageAdmission, l as InputMessage } from "../MessagingHost-D89X7ma8.mjs";
5
+ import { b as WorkerUpdate, o as WorkerCompletion } from "../Worker-CfwzMZEK.mjs";
6
+ import { d as MessageAdmission, l as InputMessage } from "../MessagingHost-DfsbMPvq.mjs";
7
7
  import { CanonicalSequence, DefinitionDigests, DeploymentId, Digest, PersistedJson, ProducerId, RecordEnvelope, SubmissionSettled, SubmissionSettledRecord, WorkerAdmission } from "./Records.mjs";
8
- import { AbortCommand, AbortIntentRequest, AdmissionConflict, AdmissionPolicyError, AdmissionRequest, ApprovalConflict, ApprovalDecisionCommand, ApprovalPendingSuspension, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildBudgetReservationRequest, ChildReservationConflict, ChildReservationId, ChildSettledNotification, ClaimJoiningRequest, ClaimRequest, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipToken, ParentLinkage, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ResolutionCompletedWithResult, ResolutionNeverHappened, ResolutionSafeToRetry, RevertJoiningRequest, SettlementConflict, SettlementFinalization, SettlementReservation, SubmissionLedger, SubmissionLookupById, SubmissionLookupByKey, SuspendRequest, UnknownResolutionCommand, UnknownResolutionConflict, WaitingChild, WaitingForChildSuspension, submissionInputRecordId, submissionSettlementId, submissionSettlementRecordId } from "./SubmissionLedger.mjs";
8
+ import { AbortCommand, AbortIntentRequest, AdmissionConflict, AdmissionPolicyError, AdmissionRequest, ApprovalConflict, ApprovalDecisionCommand, ApprovalPendingSuspension, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildBudgetReservationRequest, ChildReservationConflict, ChildReservationId, ChildSettledNotification, ClaimJoiningRequest, ClaimRequest, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipToken, ParentLinkage, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ResolutionCompletedWithResult, ResolutionNeverHappened, ResolutionSafeToRetry, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SubmissionLedger, SubmissionLookupById, SubmissionLookupByKey, SuspendRequest, UnknownResolutionCommand, UnknownResolutionConflict, WaitingChild, WaitingForChildSuspension, submissionInputRecordId, submissionSettlementId, submissionSettlementRecordId } from "./SubmissionLedger.mjs";
9
9
  import { digestJson } from "./Digest.mjs";
10
+ import { S as runIdForSubmission } from "../RunJournal-BXrq0VIK.mjs";
10
11
  import { Clock, DateTime, Duration, Effect, Option, Result, Schema, Stream } from "effect";
11
12
  import { TestClock } from "effect/testing";
12
13
  //#region src/durable/SubmissionLedgerConformance.ts
@@ -220,54 +221,55 @@ const admissionIdempotency = conformanceCase("replays identical admissions and r
220
221
  const second = yield* ledger.admit(yield* admissionRequest(threadId, "admission-key-2", { city: "Faro" }));
221
222
  yield* ensure(second.submissionId !== first.submissionId && second.queueSequence !== first.queueSequence, "A different key on the same lane must mint fresh identities and a fresh queue sequence");
222
223
  }));
224
+ const workerMetadata = (base) => WorkerAdmission.make({
225
+ messageId: base.idempotencyKey,
226
+ sourceSubmissionId: Schema.decodeSync(SubmissionId)("source-input"),
227
+ deliveryPrincipal: base.principal,
228
+ parameters: { prompt: "original projection parameters" },
229
+ createdAtMillis: 1,
230
+ origin: {
231
+ worker: {
232
+ schemaVersion: 1,
233
+ threadId: base.threadId,
234
+ targetAgentId: CONFORMANCE_AGENT,
235
+ delegationId: Schema.decodeSync(DelegationId)("worker-research")
236
+ },
237
+ source: {
238
+ _tag: "programmatic",
239
+ threadId: decodeThreadId("worker-source"),
240
+ agentId: CONFORMANCE_AGENT
241
+ },
242
+ targetDigests: CONFORMANCE_DIGESTS,
243
+ policy: AgentPolicy.resolve(),
244
+ budget: {
245
+ caps: SubagentDelegationCaps.make({
246
+ maxConcurrentChildren: 1,
247
+ maxTotalChildInvocations: 2
248
+ }),
249
+ allocation: SubagentReservationAmounts.make({
250
+ turns: 12,
251
+ toolCalls: 24,
252
+ durationMillis: 3e5,
253
+ inputTokens: 0,
254
+ outputTokens: 0,
255
+ costMicrousd: 0,
256
+ resultBytes: 1e3
257
+ })
258
+ },
259
+ grant: SubagentGrant.make({
260
+ maxDepth: 1,
261
+ allowedToolNames: []
262
+ }),
263
+ depth: 1,
264
+ firstMessageId: base.idempotencyKey,
265
+ createdAtMillis: 1,
266
+ expiresAtMillis: 1e6
267
+ }
268
+ });
223
269
  const workerAdmissionIdentity = conformanceCase("retains immutable worker admission metadata and rejects same-key changes or omission", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
224
270
  const ledger = yield* SubmissionLedger;
225
271
  const base = yield* admissionRequest(decodeThreadId("ledger-conformance-worker"), "worker-message", { text: "first" });
226
- const metadata = WorkerAdmission.make({
227
- messageId: base.idempotencyKey,
228
- sourceSubmissionId: Schema.decodeSync(SubmissionId)("source-input"),
229
- deliveryPrincipal: base.principal,
230
- parameters: { prompt: "original projection parameters" },
231
- createdAtMillis: 1,
232
- origin: {
233
- worker: {
234
- schemaVersion: 1,
235
- threadId: base.threadId,
236
- targetAgentId: CONFORMANCE_AGENT,
237
- delegationId: Schema.decodeSync(DelegationId)("worker-research")
238
- },
239
- source: {
240
- _tag: "programmatic",
241
- threadId: decodeThreadId("worker-source"),
242
- agentId: CONFORMANCE_AGENT
243
- },
244
- targetDigests: CONFORMANCE_DIGESTS,
245
- policy: AgentPolicy.resolve(),
246
- budget: {
247
- caps: SubagentDelegationCaps.make({
248
- maxConcurrentChildren: 1,
249
- maxTotalChildInvocations: 2
250
- }),
251
- allocation: SubagentReservationAmounts.make({
252
- turns: 12,
253
- toolCalls: 24,
254
- durationMillis: 3e5,
255
- inputTokens: 0,
256
- outputTokens: 0,
257
- costMicrousd: 0,
258
- resultBytes: 1e3
259
- })
260
- },
261
- grant: SubagentGrant.make({
262
- maxDepth: 1,
263
- allowedToolNames: []
264
- }),
265
- depth: 1,
266
- firstMessageId: base.idempotencyKey,
267
- createdAtMillis: 1,
268
- expiresAtMillis: 1e6
269
- }
270
- });
272
+ const metadata = workerMetadata(base);
271
273
  const admitted = yield* ledger.admit(AdmissionRequest.make({
272
274
  ...base,
273
275
  workerAdmission: metadata
@@ -1297,6 +1299,664 @@ const unknownAbortClaim = conformanceCase("a durable abort makes an unknown head
1297
1299
  yield* ensure((yield* expectSome("follower claim", yield* claimLane(threadId, PRODUCER_B))).submissionId === follower.submissionId, "Settlement must release the follower");
1298
1300
  }
1299
1301
  }));
1302
+ const unknownResolutionLifecycle = conformanceCase("unknown resolutions reopen the lane only when no open call remains", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1303
+ const threadId = decodeThreadId("ledger-conformance-unknown");
1304
+ const ledger = yield* SubmissionLedger;
1305
+ const admitted = yield* admitReady(threadId, "unknown-key-1", { work: "unknown" });
1306
+ const claim = yield* expectSome("the claim before the unknown marking", yield* claimLane(threadId, PRODUCER_A));
1307
+ const call1 = decodeToolCallId("call-unknown-1");
1308
+ const call2 = decodeToolCallId("call-unknown-2");
1309
+ const call3 = decodeToolCallId("call-unknown-3");
1310
+ const marking = MarkUnknownRequest.make({
1311
+ submissionId: admitted.submissionId,
1312
+ toolCallIds: [call1, call2],
1313
+ reason: "the worker died during two supplier calls"
1314
+ });
1315
+ yield* ledger.markUnknown(marking);
1316
+ yield* ledger.markUnknown(marking);
1317
+ yield* ensure((yield* expectSome("lookup after marking unknown", yield* lookupById(admitted.submissionId))).state === "unknown", "Marking unknown must be idempotent and block the lane");
1318
+ yield* advancePastLease(claim.leaseExpiresAt);
1319
+ yield* ensure(Option.isNone(yield* claimLane(threadId, PRODUCER_B)), "An unknown lane must consume no worker permit");
1320
+ const first = yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1321
+ submissionId: admitted.submissionId,
1322
+ toolCallId: call1,
1323
+ author: "conformance-operator",
1324
+ reason: "supplier store shows no booking",
1325
+ resolution: ResolutionNeverHappened.make()
1326
+ }));
1327
+ yield* ensure((yield* expectSome("lookup after a partial resolution", yield* lookupById(admitted.submissionId))).state === "unknown" && Option.isNone(yield* claimLane(threadId, PRODUCER_B)), "A partial resolution must keep the lane blocked while calls remain open");
1328
+ yield* TestClock.adjust("1 second");
1329
+ const divergent = yield* expectFailure("re-resolving the same call divergently", ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1330
+ submissionId: admitted.submissionId,
1331
+ toolCallId: call1,
1332
+ author: "conformance-operator",
1333
+ reason: "changed my mind",
1334
+ resolution: ResolutionSafeToRetry.make()
1335
+ })));
1336
+ yield* ensure(isUnknownResolutionConflict(divergent) && divergent.toolCallId === call1, "A divergent re-resolution must conflict");
1337
+ const replayed = yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1338
+ submissionId: admitted.submissionId,
1339
+ toolCallId: call1,
1340
+ author: "conformance-operator-second",
1341
+ reason: "a different reason text",
1342
+ resolution: ResolutionNeverHappened.make()
1343
+ }));
1344
+ yield* ensure(sameInstant(replayed.resolvedAt, first.resolvedAt) && replayed.reason === first.reason, "Repeating the same resolution must replay the recorded intent unchanged");
1345
+ yield* ledger.markUnknown(MarkUnknownRequest.make({
1346
+ submissionId: admitted.submissionId,
1347
+ toolCallIds: [call3],
1348
+ reason: "a second wave of uncertainty"
1349
+ }));
1350
+ yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1351
+ submissionId: admitted.submissionId,
1352
+ toolCallId: call2,
1353
+ author: "conformance-operator",
1354
+ reason: "supplier confirmed the booking",
1355
+ resolution: ResolutionCompletedWithResult.make({
1356
+ result: { bookingRef: "booking-1" },
1357
+ isFailure: false
1358
+ })
1359
+ }));
1360
+ yield* ensure((yield* expectSome("lookup while the extended set stays open", yield* lookupById(admitted.submissionId))).state === "unknown" && Option.isNone(yield* claimLane(threadId, PRODUCER_B)), "The lane must stay blocked while any marked call lacks a resolution");
1361
+ yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1362
+ submissionId: admitted.submissionId,
1363
+ toolCallId: call3,
1364
+ author: "conformance-operator",
1365
+ reason: "idempotency key covers a repeat",
1366
+ resolution: ResolutionSafeToRetry.make()
1367
+ }));
1368
+ yield* ensure((yield* expectSome("lookup after the covering resolution", yield* lookupById(admitted.submissionId))).state === "input-applied", "Covering every marked call must reopen the lane as input-applied");
1369
+ yield* ensure((yield* recoverySnapshot(admitted.submissionId)).unknownResolutions.length === 3, "The recovery snapshot must expose every recorded resolution intent");
1370
+ const reclaim = yield* expectSome("the claim after the lane reopened", yield* claimLane(threadId, PRODUCER_B));
1371
+ yield* ensure(reclaim.submissionId === admitted.submissionId && reclaim.producerEpoch > claim.producerEpoch, "The reopened lane must grant a fresh fenced Attempt");
1372
+ yield* settleClaimed(admitted, reclaim.ownershipToken);
1373
+ const late = yield* expectFailure("resolving an unknown call for a settled Submission", ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1374
+ submissionId: admitted.submissionId,
1375
+ toolCallId: decodeToolCallId("call-unknown-late"),
1376
+ author: "conformance-operator",
1377
+ reason: "too late",
1378
+ resolution: ResolutionNeverHappened.make()
1379
+ })));
1380
+ yield* ensure(isSettlementConflict(late), "A resolution must never land on a settled Submission");
1381
+ }));
1382
+ const suspendResumesImmediatelyWhenDecided = conformanceCase("suspend with an already-present decision resumes immediately", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1383
+ const threadId = decodeThreadId("ledger-conformance-suspend");
1384
+ const ledger = yield* SubmissionLedger;
1385
+ const admitted = yield* admitReady(threadId, "suspend-key-1", { work: "suspend" });
1386
+ const claim = yield* expectSome("the claim before suspension", yield* claimLane(threadId, PRODUCER_A));
1387
+ const callA = decodeToolCallId("call-suspend-a");
1388
+ const callB = decodeToolCallId("call-suspend-b");
1389
+ const foreign = yield* expectFailure("suspending without owning the lane", ledger.suspend(SuspendRequest.make({
1390
+ submissionId: admitted.submissionId,
1391
+ ownershipToken: BOGUS_TOKEN,
1392
+ reason: ApprovalPendingSuspension.make({ toolCallIds: [callA] })
1393
+ })));
1394
+ yield* ensure(isOwnershipLost(foreign), "suspend must be fenced by the owning token");
1395
+ yield* ledger.recordApprovalDecision(ApprovalDecisionCommand.make({
1396
+ submissionId: admitted.submissionId,
1397
+ toolCallId: callA,
1398
+ decision: "approved",
1399
+ resolver: "conformance-approver",
1400
+ reason: "decided before the suspend committed"
1401
+ }));
1402
+ yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1403
+ submissionId: admitted.submissionId,
1404
+ ownershipToken: claim.ownershipToken,
1405
+ reason: ApprovalPendingSuspension.make({ toolCallIds: [callA] })
1406
+ }))) === "resume-immediately", "A fully-decided suspension reason must resume immediately");
1407
+ yield* ensure((yield* expectSome("lookup after the immediate resume", yield* lookupById(admitted.submissionId))).state === "running", "An immediate resume must not transition the Submission to suspended");
1408
+ yield* ledger.renewOwnership(RenewOwnershipRequest.make({
1409
+ submissionId: admitted.submissionId,
1410
+ ownershipToken: claim.ownershipToken
1411
+ }));
1412
+ yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1413
+ submissionId: admitted.submissionId,
1414
+ ownershipToken: claim.ownershipToken,
1415
+ reason: ApprovalPendingSuspension.make({ toolCallIds: [callA, callB] })
1416
+ }))) === "suspended", "An undecided call in the reason must suspend durably");
1417
+ yield* ensure((yield* expectSome("lookup after the suspension", yield* lookupById(admitted.submissionId))).state === "suspended", "The suspension must be durable");
1418
+ const suspendedSnapshot = yield* recoverySnapshot(admitted.submissionId);
1419
+ yield* ensure(suspendedSnapshot.suspension !== void 0 && suspendedSnapshot.suspension.reason._tag === "ApprovalPending" && suspendedSnapshot.suspension.reason.toolCallIds.length === 2 && suspendedSnapshot.approvalDecisions.length === 1, "The recovery snapshot must expose the suspension reason and prior decisions");
1420
+ const ended = yield* expectFailure("renewing after the suspension ended the ownership period", ledger.renewOwnership(RenewOwnershipRequest.make({
1421
+ submissionId: admitted.submissionId,
1422
+ ownershipToken: claim.ownershipToken
1423
+ })));
1424
+ yield* ensure(isOwnershipLost(ended), "Suspension must end the ownership period without settling");
1425
+ yield* ledger.recordApprovalDecision(ApprovalDecisionCommand.make({
1426
+ submissionId: admitted.submissionId,
1427
+ toolCallId: callB,
1428
+ decision: "denied",
1429
+ resolver: "conformance-approver",
1430
+ reason: "the covering decision"
1431
+ }));
1432
+ yield* ensure((yield* expectSome("lookup after the covering decision", yield* lookupById(admitted.submissionId))).state === "input-applied", "The covering decision must wake the suspended lane");
1433
+ yield* ensure((yield* recoverySnapshot(admitted.submissionId)).suspension === void 0, "Waking must clear the durable suspension");
1434
+ const reclaim = yield* expectSome("the claim after waking", yield* claimLane(threadId, PRODUCER_B));
1435
+ yield* settleClaimed(admitted, reclaim.ownershipToken);
1436
+ const terminal = yield* expectFailure("suspending a settled Submission", ledger.suspend(SuspendRequest.make({
1437
+ submissionId: admitted.submissionId,
1438
+ ownershipToken: reclaim.ownershipToken,
1439
+ reason: ApprovalPendingSuspension.make({ toolCallIds: [callA] })
1440
+ })));
1441
+ yield* ensure(isSettlementConflict(terminal) && terminal.existingOutcome === "completed", "Suspension must never land on a settled Submission");
1442
+ }));
1443
+ const joinedSettlementLinkageAuthority = conformanceCase("a joined Submission's settlement reservation is authorized by host linkage", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1444
+ const threadId = decodeThreadId("ledger-conformance-joined-settlement");
1445
+ const ledger = yield* SubmissionLedger;
1446
+ const host = yield* admitReady(threadId, "joined-settle-host", { work: "host" });
1447
+ const queued = yield* admitReady(threadId, "joined-settle-2", { queued: 2 });
1448
+ const hostClaim = yield* expectSome("the host claim", yield* claimLane(threadId, PRODUCER_A));
1449
+ const claims = yield* ledger.claimJoining(ClaimJoiningRequest.make({
1450
+ threadId,
1451
+ hostSubmissionId: host.submissionId,
1452
+ ownershipToken: hostClaim.ownershipToken,
1453
+ maxCount: 1
1454
+ }));
1455
+ yield* ensure(claims.length === 1 && claims[0]?.submissionId === queued.submissionId, "The queued Submission must join the host's contiguous prefix");
1456
+ const joiningReservation = yield* settlementReservation({
1457
+ submissionId: queued.submissionId,
1458
+ ownershipToken: BOGUS_TOKEN,
1459
+ receiptId: queued.receiptId,
1460
+ outcome: "completed"
1461
+ });
1462
+ const fenced = yield* expectFailure("reserving a joining Submission's settlement without lane ownership", ledger.reserveSettlement(joiningReservation));
1463
+ yield* ensure(isOwnershipLost(fenced), "A joining Submission's settlement reservation must stay ownership-fenced");
1464
+ yield* ledger.markJoined(MarkJoinedRequest.make({
1465
+ submissionId: queued.submissionId,
1466
+ ownershipToken: hostClaim.ownershipToken,
1467
+ recordId: submissionInputRecordId(queued.submissionId),
1468
+ sequence: decodeSequence(7)
1469
+ }));
1470
+ const joinedReservation = yield* settlementReservation({
1471
+ submissionId: queued.submissionId,
1472
+ ownershipToken: BOGUS_TOKEN,
1473
+ receiptId: queued.receiptId,
1474
+ outcome: "completed"
1475
+ });
1476
+ yield* ledger.reserveSettlement(joinedReservation);
1477
+ yield* ensure((yield* ledger.finalizeSettlement(SettlementFinalization.make({
1478
+ submissionId: queued.submissionId,
1479
+ settlementId: submissionSettlementId(queued.submissionId)
1480
+ }))).outcome === "completed", "The joined settlement must finalize with the reserved outcome");
1481
+ const settled = yield* expectSome("lookup after the joined settlement", yield* lookupById(queued.submissionId));
1482
+ yield* ensure(settled.state === "settled" && settled.settledOutcome === "completed", "The joined Submission must settle terminally");
1483
+ }));
1484
+ const childReservationIdempotency = conformanceCase("replays an identical child reservation and rejects a divergent allocation digest", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1485
+ const threadId = decodeThreadId("ledger-conformance-child-reserve");
1486
+ const ledger = yield* SubmissionLedger;
1487
+ const parent = yield* admitReady(threadId, "child-reserve-parent", { work: "parent" });
1488
+ const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1489
+ const allocation = {
1490
+ turns: 4,
1491
+ toolCalls: 8
1492
+ };
1493
+ const allocationDigest = yield* digestJson(allocation);
1494
+ const requestFields = {
1495
+ reservationId: decodeChildReservationId("child-reservation:run-reserve:call-1"),
1496
+ parentSubmissionId: parent.submissionId,
1497
+ parentToolCallId: decodeToolCallId("call-child-reserve"),
1498
+ ownershipToken: claim.ownershipToken,
1499
+ allocation,
1500
+ allocationDigest
1501
+ };
1502
+ const request = ChildBudgetReservationRequest.make(requestFields);
1503
+ const first = yield* ledger.reserveChildBudget(request);
1504
+ yield* ensure(!first.replayed && first.reservation.status === "reserved" && first.reservation.allocationDigest === allocationDigest && first.reservation.childSubmissionId === void 0 && persistedJsonEquivalent(first.reservation.allocation, allocation), "The first reservation must create a reserved row carrying the exact allocation");
1505
+ yield* TestClock.adjust("1 second");
1506
+ const replayed = yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1507
+ ...requestFields,
1508
+ allocation: {
1509
+ toolCalls: 8,
1510
+ turns: 4
1511
+ }
1512
+ }));
1513
+ yield* ensure(replayed.replayed && replayed.reservation.status === "reserved" && persistedJsonEquivalent(replayed.reservation.allocation, allocation) && sameInstant(replayed.reservation.reservedAt, first.reservation.reservedAt), "An identical reservation must replay the stored row unchanged");
1514
+ const divergentAllocation = {
1515
+ turns: 64,
1516
+ toolCalls: 8
1517
+ };
1518
+ const divergent = yield* expectFailure("a divergent allocation for the same reservation id", ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1519
+ ...requestFields,
1520
+ allocation: divergentAllocation,
1521
+ allocationDigest: yield* digestJson(divergentAllocation)
1522
+ })));
1523
+ yield* ensure(isChildReservationConflict(divergent) && divergent.status === "reserved", "A divergent allocation must conflict with the recorded reservation");
1524
+ const secondId = yield* expectFailure("a second reservation id for the same parent Tool Call", ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1525
+ ...requestFields,
1526
+ reservationId: decodeChildReservationId("child-reservation:run-reserve:call-1-other")
1527
+ })));
1528
+ yield* ensure(isChildReservationConflict(secondId), "One parent Tool Call must never own two reservations");
1529
+ const snapshot = yield* recoverySnapshot(parent.submissionId);
1530
+ yield* ensure(snapshot.childReservations.length === 1 && snapshot.childReservations[0].reservationId === request.reservationId && snapshot.childReservations[0].status === "reserved" && snapshot.childAttachments.length === 0, "The parent recovery snapshot must expose the reservation before any attachment");
1531
+ }));
1532
+ const childReservationFencing = conformanceCase("a stale parent token cannot transition a child reservation", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1533
+ const threadId = decodeThreadId("ledger-conformance-child-fence");
1534
+ const childLane = decodeThreadId("ledger-conformance-child-fence-child");
1535
+ const ledger = yield* SubmissionLedger;
1536
+ const parent = yield* admitReady(threadId, "child-fence-parent", { work: "parent" });
1537
+ const child = yield* admitReady(childLane, "child-fence-child", { work: "child" });
1538
+ const firstClaim = yield* expectSome("the first parent claim", yield* claimLane(threadId, PRODUCER_A));
1539
+ const allocation = { turns: 2 };
1540
+ const allocationDigest = yield* digestJson(allocation);
1541
+ const requestFields = {
1542
+ reservationId: decodeChildReservationId("child-reservation:run-fence:call-1"),
1543
+ parentSubmissionId: parent.submissionId,
1544
+ parentToolCallId: decodeToolCallId("call-child-fence"),
1545
+ ownershipToken: BOGUS_TOKEN,
1546
+ allocation,
1547
+ allocationDigest
1548
+ };
1549
+ const request = ChildBudgetReservationRequest.make(requestFields);
1550
+ const foreign = yield* expectFailure("creating a reservation without owning the parent lane", ledger.reserveChildBudget(request));
1551
+ yield* ensure(isOwnershipLost(foreign), "Reservation creation must be fenced by the parent's live ownership token");
1552
+ yield* ensure((yield* recoverySnapshot(parent.submissionId)).childReservations.length === 0, "A fenced reservation attempt must leave no row behind");
1553
+ yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1554
+ ...requestFields,
1555
+ ownershipToken: firstClaim.ownershipToken
1556
+ }));
1557
+ yield* ledger.releaseOwnership(ReleaseOwnershipRequest.make({
1558
+ submissionId: parent.submissionId,
1559
+ ownershipToken: firstClaim.ownershipToken
1560
+ }));
1561
+ const reclaim = yield* expectSome("the replacement parent claim", yield* claimLane(threadId, PRODUCER_B));
1562
+ yield* ensure(reclaim.producerEpoch > firstClaim.producerEpoch, "The replacement claim must fence the stale parent Attempt");
1563
+ const staleAttach = yield* expectFailure("attaching a child with the superseded parent token", ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1564
+ reservationId: request.reservationId,
1565
+ ownershipToken: firstClaim.ownershipToken,
1566
+ childSubmissionId: child.submissionId
1567
+ })));
1568
+ yield* ensure(isOwnershipLost(staleAttach), "A superseded parent token must not attach a child");
1569
+ const staleCreate = yield* expectFailure("creating a second-call reservation with the superseded parent token", ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1570
+ ...requestFields,
1571
+ reservationId: decodeChildReservationId("child-reservation:run-fence:call-2"),
1572
+ parentToolCallId: decodeToolCallId("call-child-fence-second"),
1573
+ ownershipToken: firstClaim.ownershipToken
1574
+ })));
1575
+ yield* ensure(isOwnershipLost(staleCreate), "A superseded parent token must not create new reservation state");
1576
+ const staleReplay = yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1577
+ ...requestFields,
1578
+ ownershipToken: firstClaim.ownershipToken
1579
+ }));
1580
+ yield* ensure(staleReplay.replayed && staleReplay.reservation.status === "reserved", "An identical reservation replay must return the recorded row even from a stale caller");
1581
+ yield* ensure((yield* ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1582
+ reservationId: request.reservationId,
1583
+ ownershipToken: reclaim.ownershipToken,
1584
+ childSubmissionId: child.submissionId
1585
+ }))).childSubmissionId === child.submissionId, "The live replacement token must attach the child");
1586
+ }));
1587
+ const attachChildIdempotency = conformanceCase("attachChildToReservation is idempotent and rejects a divergent child", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1588
+ const threadId = decodeThreadId("ledger-conformance-child-attach");
1589
+ const childLane = decodeThreadId("ledger-conformance-child-attach-child");
1590
+ const ledger = yield* SubmissionLedger;
1591
+ const parent = yield* admitReady(threadId, "child-attach-parent", { work: "parent" });
1592
+ const child = yield* admitReady(childLane, "child-attach-child", { queued: 1 });
1593
+ const otherChild = yield* admitReady(childLane, "child-attach-other", { queued: 2 });
1594
+ const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1595
+ const allocation = { turns: 3 };
1596
+ const reservationId = decodeChildReservationId("child-reservation:run-attach:call-1");
1597
+ yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1598
+ reservationId,
1599
+ parentSubmissionId: parent.submissionId,
1600
+ parentToolCallId: decodeToolCallId("call-child-attach"),
1601
+ ownershipToken: claim.ownershipToken,
1602
+ allocation,
1603
+ allocationDigest: yield* digestJson(allocation)
1604
+ }));
1605
+ const unknownReservation = yield* expectFailure("attaching to a reservation that was never created", ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1606
+ reservationId: decodeChildReservationId("child-reservation:run-attach:missing"),
1607
+ ownershipToken: claim.ownershipToken,
1608
+ childSubmissionId: child.submissionId
1609
+ })));
1610
+ yield* ensure(isLedgerError(unknownReservation), "Attaching to an unknown reservation must fail as a ledger error");
1611
+ const attached = yield* ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1612
+ reservationId,
1613
+ ownershipToken: claim.ownershipToken,
1614
+ childSubmissionId: child.submissionId
1615
+ }));
1616
+ yield* ensure(attached.childSubmissionId === child.submissionId && attached.status === "reserved", "Attaching must record the child on the reservation row");
1617
+ yield* ensure((yield* ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1618
+ reservationId,
1619
+ ownershipToken: claim.ownershipToken,
1620
+ childSubmissionId: child.submissionId
1621
+ }))).childSubmissionId === child.submissionId, "Repeating the identical attachment must be a no-op");
1622
+ const divergent = yield* expectFailure("attaching a different child to the same reservation", ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1623
+ reservationId,
1624
+ ownershipToken: claim.ownershipToken,
1625
+ childSubmissionId: otherChild.submissionId
1626
+ })));
1627
+ yield* ensure(isChildReservationConflict(divergent), "A divergent child attachment must conflict with the recorded child");
1628
+ const snapshot = yield* recoverySnapshot(parent.submissionId);
1629
+ yield* ensure(snapshot.childAttachments.length === 1 && snapshot.childAttachments[0].childSubmissionId === child.submissionId && snapshot.childAttachments[0].toolCallId === decodeToolCallId("call-child-attach") && snapshot.childAttachments[0].childState === "ready" && snapshot.childAttachments[0].childOutcome === void 0, "The parent recovery snapshot must expose the attachment with the child's lane state");
1630
+ }));
1631
+ const beginReleaseFreezesAccountingOnce = conformanceCase("beginRelease freezes the accounting decision exactly once", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1632
+ const threadId = decodeThreadId("ledger-conformance-child-freeze");
1633
+ const ledger = yield* SubmissionLedger;
1634
+ const parent = yield* admitReady(threadId, "child-freeze-parent", { work: "parent" });
1635
+ const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1636
+ const allocation = { turns: 4 };
1637
+ const reservationId = decodeChildReservationId("child-reservation:run-freeze:call-1");
1638
+ yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1639
+ reservationId,
1640
+ parentSubmissionId: parent.submissionId,
1641
+ parentToolCallId: decodeToolCallId("call-child-freeze"),
1642
+ ownershipToken: claim.ownershipToken,
1643
+ allocation,
1644
+ allocationDigest: yield* digestJson(allocation)
1645
+ }));
1646
+ const accounting = {
1647
+ consumed: { turns: 1 },
1648
+ released: { turns: 3 }
1649
+ };
1650
+ const frozen = yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1651
+ reservationId,
1652
+ accounting
1653
+ }));
1654
+ yield* ensure(frozen.status === "releasePending" && frozen.accounting !== void 0 && persistedJsonEquivalent(frozen.accounting, accounting) && frozen.releaseBeganAt !== void 0, "The first beginRelease must freeze the accounting and move to releasePending");
1655
+ yield* TestClock.adjust("1 second");
1656
+ const replayed = yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1657
+ reservationId,
1658
+ accounting: {
1659
+ released: { turns: 3 },
1660
+ consumed: { turns: 1 }
1661
+ }
1662
+ }));
1663
+ yield* ensure(replayed.status === "releasePending" && replayed.releaseBeganAt !== void 0 && frozen.releaseBeganAt !== void 0 && sameInstant(replayed.releaseBeganAt, frozen.releaseBeganAt), "Replaying the identical accounting must be a no-op with the frozen decision retained");
1664
+ const divergent = yield* expectFailure("freezing a different accounting decision", ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1665
+ reservationId,
1666
+ accounting: {
1667
+ consumed: { turns: 4 },
1668
+ released: { turns: 0 }
1669
+ }
1670
+ })));
1671
+ yield* ensure(isChildReservationConflict(divergent) && divergent.status === "releasePending", "A divergent accounting freeze must conflict with the frozen decision");
1672
+ yield* ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId }));
1673
+ const afterRelease = yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1674
+ reservationId,
1675
+ accounting
1676
+ }));
1677
+ yield* ensure(afterRelease.status === "released" && afterRelease.accounting !== void 0 && persistedJsonEquivalent(afterRelease.accounting, accounting), "Replaying the identical accounting after release must return the released row");
1678
+ const divergentAfterRelease = yield* expectFailure("freezing a different accounting decision after release", ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1679
+ reservationId,
1680
+ accounting: {
1681
+ consumed: { turns: 2 },
1682
+ released: { turns: 2 }
1683
+ }
1684
+ })));
1685
+ yield* ensure(isChildReservationConflict(divergentAfterRelease) && divergentAfterRelease.status === "released", "The frozen decision must stay immutable after release");
1686
+ }));
1687
+ const releaseAppliedExactlyOnce = conformanceCase("release returns unused allocation exactly once", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1688
+ const threadId = decodeThreadId("ledger-conformance-child-release");
1689
+ const ledger = yield* SubmissionLedger;
1690
+ const parent = yield* admitReady(threadId, "child-release-parent", { work: "parent" });
1691
+ const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1692
+ const allocation = { turns: 4 };
1693
+ const reservationId = decodeChildReservationId("child-reservation:run-release:call-1");
1694
+ yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1695
+ reservationId,
1696
+ parentSubmissionId: parent.submissionId,
1697
+ parentToolCallId: decodeToolCallId("call-child-release"),
1698
+ ownershipToken: claim.ownershipToken,
1699
+ allocation,
1700
+ allocationDigest: yield* digestJson(allocation)
1701
+ }));
1702
+ const early = yield* expectFailure("releasing before the accounting decision is frozen", ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId })));
1703
+ yield* ensure(isChildReservationConflict(early) && early.status === "reserved", "Release must never skip the releasePending freeze");
1704
+ yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1705
+ reservationId,
1706
+ accounting: {
1707
+ consumed: {},
1708
+ released: { turns: 4 }
1709
+ }
1710
+ }));
1711
+ const released = yield* ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId }));
1712
+ yield* ensure(released.status === "released" && released.releasedAt !== void 0, "Release must transition releasePending to released");
1713
+ yield* TestClock.adjust("1 second");
1714
+ const replayed = yield* ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId }));
1715
+ yield* ensure(replayed.status === "released" && replayed.releasedAt !== void 0 && released.releasedAt !== void 0 && sameInstant(replayed.releasedAt, released.releasedAt), "Replaying the release must return the stored row unchanged — never applied twice");
1716
+ const snapshot = yield* recoverySnapshot(parent.submissionId);
1717
+ yield* ensure(snapshot.childReservations.length === 1 && snapshot.childReservations[0].status === "released", "The recovery snapshot must expose the released reservation");
1718
+ }));
1719
+ const recordChildSettledWake = conformanceCase("recordChildSettled accepts canonical terminalizing prefixes and wakes only when all are covered", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1720
+ const parentLane = decodeThreadId("ledger-conformance-child-wake");
1721
+ const childLaneA = decodeThreadId("ledger-conformance-child-wake-a");
1722
+ const childLaneB = decodeThreadId("ledger-conformance-child-wake-b");
1723
+ const ledger = yield* SubmissionLedger;
1724
+ const parent = yield* admitReady(parentLane, "child-wake-parent", { work: "parent" });
1725
+ const childA = yield* admitReady(childLaneA, "child-wake-a", { child: "a" });
1726
+ const childB = yield* admitReady(childLaneB, "child-wake-b", { child: "b" });
1727
+ const parentClaim = yield* expectSome("the parent claim", yield* claimLane(parentLane, PRODUCER_A));
1728
+ yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1729
+ submissionId: parent.submissionId,
1730
+ ownershipToken: parentClaim.ownershipToken,
1731
+ reason: WaitingForChildSuspension.make({ children: [WaitingChild.make({
1732
+ toolCallId: decodeToolCallId("call-wake-a"),
1733
+ childSubmissionId: childA.submissionId
1734
+ }), WaitingChild.make({
1735
+ toolCallId: decodeToolCallId("call-wake-b"),
1736
+ childSubmissionId: childB.submissionId
1737
+ })] })
1738
+ }))) === "suspended", "Unsettled children must suspend the parent durably");
1739
+ const ended = yield* expectFailure("renewing after the waitingForChild suspension ended the ownership period", ledger.renewOwnership(RenewOwnershipRequest.make({
1740
+ submissionId: parent.submissionId,
1741
+ ownershipToken: parentClaim.ownershipToken
1742
+ })));
1743
+ yield* ensure(isOwnershipLost(ended), "waitingForChild must end the ownership period without settling (SUB-030)");
1744
+ yield* ensure(Option.isNone(yield* claimLane(parentLane, PRODUCER_B)), "A waitingForChild head must produce no claim and consume no worker permit");
1745
+ const childClaimA = yield* expectSome("the first child claim", yield* claimLane(childLaneA, PRODUCER_A));
1746
+ const childReservationA = yield* settlementReservation({
1747
+ submissionId: childA.submissionId,
1748
+ ownershipToken: childClaimA.ownershipToken,
1749
+ receiptId: childA.receiptId,
1750
+ outcome: "completed"
1751
+ });
1752
+ yield* ledger.reserveSettlement(childReservationA);
1753
+ yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1754
+ parentSubmissionId: parent.submissionId,
1755
+ childSubmissionId: childA.submissionId
1756
+ }))) === "still-waiting", "A settlement notification must not wake the parent while a listed child is unsettled");
1757
+ yield* ledger.finalizeSettlement(SettlementFinalization.make({
1758
+ submissionId: childA.submissionId,
1759
+ settlementId: childReservationA.settlementId
1760
+ }));
1761
+ yield* ensure((yield* expectSome("lookup while one child is outstanding", yield* lookupById(parent.submissionId))).state === "suspended" && Option.isNone(yield* claimLane(parentLane, PRODUCER_B)), "The parent lane must stay suspended until every listed child settled");
1762
+ const childClaimB = yield* expectSome("the second child claim", yield* claimLane(childLaneB, PRODUCER_A));
1763
+ const childReservationB = yield* settlementReservation({
1764
+ submissionId: childB.submissionId,
1765
+ ownershipToken: childClaimB.ownershipToken,
1766
+ receiptId: childB.receiptId,
1767
+ outcome: "completed"
1768
+ });
1769
+ yield* ledger.reserveSettlement(childReservationB);
1770
+ yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1771
+ parentSubmissionId: parent.submissionId,
1772
+ childSubmissionId: childB.submissionId
1773
+ }))) === "woken", "The covering canonical settlement prefix must wake the parent before child finalization");
1774
+ yield* ledger.finalizeSettlement(SettlementFinalization.make({
1775
+ submissionId: childB.submissionId,
1776
+ settlementId: childReservationB.settlementId
1777
+ }));
1778
+ yield* ensure((yield* expectSome("lookup after the covering settlement", yield* lookupById(parent.submissionId))).state === "input-applied", "The woken parent must transition suspended(WaitingForChild) to input-applied");
1779
+ yield* ensure((yield* recoverySnapshot(parent.submissionId)).suspension === void 0, "Waking must clear the durable suspension");
1780
+ yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1781
+ parentSubmissionId: parent.submissionId,
1782
+ childSubmissionId: childA.submissionId
1783
+ }))) === "not-waiting", "Replaying a notification after the wake must be an idempotent no-op");
1784
+ const reclaim = yield* expectSome("the parent claim after waking", yield* claimLane(parentLane, PRODUCER_B));
1785
+ yield* ensure(reclaim.submissionId === parent.submissionId && reclaim.producerEpoch > parentClaim.producerEpoch, "The woken lane must grant a fresh fenced Attempt");
1786
+ yield* settleClaimed(parent, reclaim.ownershipToken);
1787
+ yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1788
+ parentSubmissionId: parent.submissionId,
1789
+ childSubmissionId: childB.submissionId
1790
+ }))) === "not-waiting", "A notification for a settled parent must answer not-waiting");
1791
+ }));
1792
+ const suspendResumesImmediatelyForSettledChildren = conformanceCase("suspend returns resume-immediately when children already settled", ({ ensure, expectSome }) => Effect.gen(function* () {
1793
+ const parentLane = decodeThreadId("ledger-conformance-child-raced");
1794
+ const childLaneA = decodeThreadId("ledger-conformance-child-raced-a");
1795
+ const childLaneB = decodeThreadId("ledger-conformance-child-raced-b");
1796
+ const ledger = yield* SubmissionLedger;
1797
+ const settledChild = yield* admitReady(childLaneA, "child-raced-a", { child: "a" });
1798
+ const settledClaim = yield* expectSome("the settled child's claim", yield* claimLane(childLaneA, PRODUCER_A));
1799
+ yield* settleClaimed(settledChild, settledClaim.ownershipToken);
1800
+ const pendingChild = yield* admitReady(childLaneB, "child-raced-b", { child: "b" });
1801
+ const parent = yield* admitReady(parentLane, "child-raced-parent", { work: "parent" });
1802
+ const parentClaim = yield* expectSome("the parent claim", yield* claimLane(parentLane, PRODUCER_A));
1803
+ yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1804
+ submissionId: parent.submissionId,
1805
+ ownershipToken: parentClaim.ownershipToken,
1806
+ reason: WaitingForChildSuspension.make({ children: [WaitingChild.make({
1807
+ toolCallId: decodeToolCallId("call-raced-a"),
1808
+ childSubmissionId: settledChild.submissionId
1809
+ })] })
1810
+ }))) === "resume-immediately", "A suspend listing only settled children must resume immediately");
1811
+ yield* ensure((yield* expectSome("lookup after the immediate resume", yield* lookupById(parent.submissionId))).state === "running", "An immediate resume must not transition the parent to suspended");
1812
+ yield* ledger.renewOwnership(RenewOwnershipRequest.make({
1813
+ submissionId: parent.submissionId,
1814
+ ownershipToken: parentClaim.ownershipToken
1815
+ }));
1816
+ yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1817
+ submissionId: parent.submissionId,
1818
+ ownershipToken: parentClaim.ownershipToken,
1819
+ reason: WaitingForChildSuspension.make({ children: [WaitingChild.make({
1820
+ toolCallId: decodeToolCallId("call-raced-a"),
1821
+ childSubmissionId: settledChild.submissionId
1822
+ }), WaitingChild.make({
1823
+ toolCallId: decodeToolCallId("call-raced-b"),
1824
+ childSubmissionId: pendingChild.submissionId
1825
+ })] })
1826
+ }))) === "suspended", "One unsettled listed child must suspend the parent durably");
1827
+ const snapshot = yield* recoverySnapshot(parent.submissionId);
1828
+ yield* ensure(snapshot.suspension !== void 0 && snapshot.suspension.reason._tag === "WaitingForChild" && snapshot.suspension.reason.children.length === 2, "The recovery snapshot must expose the WaitingForChild reason with its children");
1829
+ }));
1830
+ const admissionParentLinkage = conformanceCase("admit records and replays parent linkage", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1831
+ const parentLane = decodeThreadId("ledger-conformance-linkage");
1832
+ const childLane = decodeThreadId("ledger-conformance-linkage-child");
1833
+ const ledger = yield* SubmissionLedger;
1834
+ const parent = yield* admitReady(parentLane, "linkage-parent", { work: "parent" });
1835
+ const linkage = ParentLinkage.make({
1836
+ parentSubmissionId: parent.submissionId,
1837
+ parentToolCallId: decodeToolCallId("call-linkage")
1838
+ });
1839
+ const request = yield* admissionRequest(childLane, "linkage-child-key", { task: "research" }, linkage);
1840
+ const admitted = yield* ledger.admit(request);
1841
+ yield* ensure(!admitted.replayed, "The first linked admission must create the child");
1842
+ const byId = yield* expectSome("lookup of the linked child", yield* lookupById(admitted.submissionId));
1843
+ yield* ensure(byId.parentLinkage !== void 0 && byId.parentLinkage.parentSubmissionId === parent.submissionId && byId.parentLinkage.parentToolCallId === linkage.parentToolCallId, "The child snapshot must expose its immutable parent linkage");
1844
+ const childSnapshot = yield* recoverySnapshot(admitted.submissionId);
1845
+ yield* ensure(childSnapshot.parentLinkage !== void 0 && childSnapshot.parentLinkage.parentSubmissionId === parent.submissionId, "The child recovery snapshot must expose its parent linkage");
1846
+ const replayed = yield* ledger.admit(request);
1847
+ yield* ensure(replayed.replayed && replayed.submissionId === admitted.submissionId && replayed.receiptId === admitted.receiptId, "An identical linked admission must replay the original identities (SUB-016)");
1848
+ const divergentLinkage = yield* expectFailure("replaying the admission with a different parent Tool Call", ledger.admit(yield* admissionRequest(childLane, "linkage-child-key", { task: "research" }, ParentLinkage.make({
1849
+ parentSubmissionId: parent.submissionId,
1850
+ parentToolCallId: decodeToolCallId("call-linkage-other")
1851
+ }))));
1852
+ yield* ensure(isAdmissionConflict(divergentLinkage), "A divergent parent linkage must conflict even when the input digest matches");
1853
+ const droppedLinkage = yield* expectFailure("replaying the admission without its parent linkage", ledger.admit(yield* admissionRequest(childLane, "linkage-child-key", { task: "research" })));
1854
+ yield* ensure(isAdmissionConflict(droppedLinkage), "Dropping the recorded linkage on replay must conflict");
1855
+ const plain = yield* ledger.admit(yield* admissionRequest(childLane, "linkage-plain-key", { task: "plain" }));
1856
+ const addedLinkage = yield* expectFailure("replaying an unlinked admission with a parent linkage", ledger.admit(yield* admissionRequest(childLane, "linkage-plain-key", { task: "plain" }, linkage)));
1857
+ yield* ensure(isAdmissionConflict(addedLinkage), "Adding a linkage to an unlinked admission on replay must conflict");
1858
+ yield* ensure((yield* expectSome("lookup of the unlinked Submission", yield* lookupById(plain.submissionId))).parentLinkage === void 0, "An unlinked Submission must expose no parent linkage");
1859
+ }));
1860
+ const resolveAdmissionAuthority = conformanceCase("resolveAdmission distinguishes notAdmitted from admitted authoritatively", ({ ensure, expectSome }) => Effect.gen(function* () {
1861
+ const threadId = decodeThreadId("ledger-conformance-resolve");
1862
+ const ledger = yield* SubmissionLedger;
1863
+ const key = SubmissionLookupByKey.make({
1864
+ threadId,
1865
+ principal: CONFORMANCE_PRINCIPAL,
1866
+ idempotencyKey: decodeIdempotencyKey("resolve-key-1")
1867
+ });
1868
+ yield* ensure((yield* ledger.resolveAdmission(key))._tag === "NotAdmitted", "The authoritative store must prove absence before admission (SUB-031)");
1869
+ const admitted = yield* ledger.admit(yield* admissionRequest(threadId, "resolve-key-1", { work: "resolve" }));
1870
+ const after = yield* ledger.resolveAdmission(key);
1871
+ yield* ensure(after._tag === "Admitted" && after.submission.submissionId === admitted.submissionId && after.submission.receiptId === admitted.receiptId && after.submission.state === "admitted", "An admitted key must resolve to the full authoritative snapshot");
1872
+ yield* ensure((yield* ledger.resolveAdmission(SubmissionLookupByKey.make({
1873
+ threadId,
1874
+ principal: OTHER_PRINCIPAL,
1875
+ idempotencyKey: decodeIdempotencyKey("resolve-key-1")
1876
+ })))._tag === "NotAdmitted", "Admission resolution must stay scoped to the requesting principal");
1877
+ yield* ledger.markReady(MarkReadyRequest.make({ submissionId: admitted.submissionId }));
1878
+ const claim = yield* expectSome("the claim before terminal resolution", yield* claimLane(threadId, PRODUCER_A));
1879
+ yield* settleClaimed(admitted, claim.ownershipToken);
1880
+ const settled = yield* ledger.resolveAdmission(key);
1881
+ yield* ensure(settled._tag === "Admitted" && settled.submission.state === "settled" && settled.submission.settledOutcome === "completed", "A settled Submission still resolves as admitted — terminality never becomes absence");
1882
+ }));
1883
+ const queuedAbortSettlementAuthority = conformanceCase("reserveSettlement authorizes an aborted, unowned queued settlement by its durable intent", ({ ensure, expectFailure }) => Effect.gen(function* () {
1884
+ const threadId = decodeThreadId("ledger-conformance-queued-abort");
1885
+ const ledger = yield* SubmissionLedger;
1886
+ const head = yield* admitReady(threadId, "queued-abort-head", { work: "head" });
1887
+ const second = yield* admitReady(threadId, "queued-abort-2", { queued: 2 });
1888
+ const third = yield* admitReady(threadId, "queued-abort-3", { queued: 3 });
1889
+ yield* ledger.requestAbort(AbortCommand.make({
1890
+ submissionId: second.submissionId,
1891
+ author: "operator",
1892
+ reason: "cancelled while queued"
1893
+ }));
1894
+ const unaborted = yield* expectFailure("reserving an aborted settlement for a queued row without an abort intent", settlementReservation({
1895
+ submissionId: third.submissionId,
1896
+ ownershipToken: BOGUS_TOKEN,
1897
+ receiptId: third.receiptId,
1898
+ outcome: "aborted"
1899
+ }).pipe(Effect.flatMap((reservation) => ledger.reserveSettlement(reservation))));
1900
+ yield* ensure(isOwnershipLost(unaborted), "A queued reservation without a durable abort intent must stay fenced (OwnershipLost)");
1901
+ const wrongOutcome = yield* expectFailure("reserving a completed settlement for the aborted queued row", settlementReservation({
1902
+ submissionId: second.submissionId,
1903
+ ownershipToken: BOGUS_TOKEN,
1904
+ receiptId: second.receiptId,
1905
+ outcome: "completed",
1906
+ result: { fabricated: true }
1907
+ }).pipe(Effect.flatMap((reservation) => ledger.reserveSettlement(reservation))));
1908
+ yield* ensure(isOwnershipLost(wrongOutcome), "An abort intent must never authorize a non-aborted settlement outcome");
1909
+ const reservation = yield* settlementReservation({
1910
+ submissionId: second.submissionId,
1911
+ ownershipToken: BOGUS_TOKEN,
1912
+ receiptId: second.receiptId,
1913
+ outcome: "aborted"
1914
+ });
1915
+ const reserved = yield* ledger.reserveSettlement(reservation);
1916
+ yield* ensure(reserved.replayed === false && reserved.outcome === "aborted", "The abort intent must authorize the aborted reservation without lane ownership");
1917
+ yield* ensure((yield* ledger.reserveSettlement(reservation)).replayed === true, "Replaying the identical aborted reservation must short-circuit idempotently");
1918
+ yield* ledger.finalizeSettlement(SettlementFinalization.make({
1919
+ submissionId: second.submissionId,
1920
+ settlementId: submissionSettlementId(second.submissionId)
1921
+ }));
1922
+ const settled = yield* recoverySnapshot(second.submissionId);
1923
+ yield* ensure(settled.submission.state === "settled" && settled.submission.settledOutcome === "aborted", "The aborted queued Submission must settle while the head is still unsettled");
1924
+ yield* ensure((yield* recoverySnapshot(head.submissionId)).submission.state === "ready", "Settling the aborted queued row must not disturb the unclaimed head");
1925
+ }));
1926
+ const abortedSettledRowIsNotAJoiningGap = conformanceCase("claimJoining treats an aborted-settled row as a non-gap", ({ ensure, expectSome }) => Effect.gen(function* () {
1927
+ const threadId = decodeThreadId("ledger-conformance-aborted-non-gap");
1928
+ const ledger = yield* SubmissionLedger;
1929
+ const host = yield* admitReady(threadId, "aborted-gap-host", { work: "host" });
1930
+ const second = yield* admitReady(threadId, "aborted-gap-2", { queued: 2 });
1931
+ const third = yield* admitReady(threadId, "aborted-gap-3", { queued: 3 });
1932
+ yield* ledger.requestAbort(AbortCommand.make({
1933
+ submissionId: second.submissionId,
1934
+ author: "operator",
1935
+ reason: "cancelled while queued"
1936
+ }));
1937
+ const reservation = yield* settlementReservation({
1938
+ submissionId: second.submissionId,
1939
+ ownershipToken: BOGUS_TOKEN,
1940
+ receiptId: second.receiptId,
1941
+ outcome: "aborted"
1942
+ });
1943
+ yield* ledger.reserveSettlement(reservation);
1944
+ yield* ledger.finalizeSettlement(SettlementFinalization.make({
1945
+ submissionId: second.submissionId,
1946
+ settlementId: submissionSettlementId(second.submissionId)
1947
+ }));
1948
+ const hostClaim = yield* expectSome("the host claim", yield* claimLane(threadId, PRODUCER_A));
1949
+ yield* ensure(hostClaim.submissionId === host.submissionId, "The host must head the lane (the aborted-settled row is out of the queue)");
1950
+ const claims = yield* ledger.claimJoining(ClaimJoiningRequest.make({
1951
+ threadId,
1952
+ hostSubmissionId: host.submissionId,
1953
+ ownershipToken: hostClaim.ownershipToken,
1954
+ maxCount: 8
1955
+ }));
1956
+ yield* ensure(claims.length === 1 && claims[0].submissionId === third.submissionId, "The joining prefix must skip the aborted-settled row and claim the later ready work");
1957
+ const settled = yield* recoverySnapshot(second.submissionId);
1958
+ yield* ensure(settled.submission.state === "settled" && settled.submission.settledOutcome === "aborted", "Walking the prefix must never disturb the aborted-settled row");
1959
+ }));
1300
1960
  /**
1301
1961
  * The shared, adapter-parameterized SubmissionLedger contract suite (STORE-010). Every durable
1302
1962
  * ledger adapter test suite must execute each case against its own ledger provisioning, inside
@@ -1305,6 +1965,183 @@ const unknownAbortClaim = conformanceCase("a durable abort makes an unknown head
1305
1965
  const submissionLedgerConformanceCases = [
1306
1966
  admissionIdempotency,
1307
1967
  workerAdmissionIdentity,
1968
+ conformanceCase("seals terminal assignments atomically while waiting and unapplied corrections remain steerable", ({ ensure, expectSome, expectFailure }) => Effect.gen(function* () {
1969
+ const ledger = yield* SubmissionLedger;
1970
+ if (ledger.inspectWorker === void 0) return;
1971
+ for (const scenario of [
1972
+ {
1973
+ name: "completed",
1974
+ outcome: "completed",
1975
+ disposition: "completed",
1976
+ terminal: "completed"
1977
+ },
1978
+ {
1979
+ name: "waiting",
1980
+ outcome: "completed",
1981
+ disposition: "waiting",
1982
+ terminal: void 0
1983
+ },
1984
+ {
1985
+ name: "failure",
1986
+ outcome: "failed",
1987
+ queued: true,
1988
+ terminal: "failed"
1989
+ },
1990
+ {
1991
+ name: "failure-joining",
1992
+ outcome: "failed",
1993
+ queued: true,
1994
+ joining: true,
1995
+ terminal: "failed"
1996
+ },
1997
+ {
1998
+ name: "exhaustion",
1999
+ outcome: "completed",
2000
+ exhausted: true,
2001
+ terminal: "failed"
2002
+ },
2003
+ {
2004
+ name: "active-abort",
2005
+ outcome: "aborted",
2006
+ terminal: "cancelled"
2007
+ },
2008
+ {
2009
+ name: "queued-abort",
2010
+ outcome: "aborted",
2011
+ noRun: true,
2012
+ terminal: void 0
2013
+ },
2014
+ {
2015
+ name: "reusable",
2016
+ outcome: "completed",
2017
+ disposition: "completed",
2018
+ reusable: true,
2019
+ terminal: void 0
2020
+ },
2021
+ {
2022
+ name: "late-correction",
2023
+ outcome: "completed",
2024
+ disposition: "completed",
2025
+ queued: true,
2026
+ terminal: void 0
2027
+ },
2028
+ {
2029
+ name: "applied-correction",
2030
+ outcome: "completed",
2031
+ disposition: "completed",
2032
+ queued: true,
2033
+ joined: true,
2034
+ terminal: "completed"
2035
+ }
2036
+ ]) {
2037
+ const threadId = decodeThreadId(`assignment-${scenario.name}`);
2038
+ const base = yield* admissionRequest(threadId, "first", { text: "first" });
2039
+ const metadata = workerMetadata(base);
2040
+ const workerAdmission = WorkerAdmission.make({
2041
+ ...metadata,
2042
+ origin: {
2043
+ ...metadata.origin,
2044
+ ..."reusable" in scenario ? {} : { lifecycle: "assignment" }
2045
+ }
2046
+ });
2047
+ const request = AdmissionRequest.make({
2048
+ ...base,
2049
+ workerAdmission
2050
+ });
2051
+ const first = yield* ledger.admit(request);
2052
+ yield* ledger.markReady(MarkReadyRequest.make({ submissionId: first.submissionId }));
2053
+ const claim = yield* expectSome("assignment claim", yield* claimLane(threadId, PRODUCER_A));
2054
+ const runId = runIdForSubmission(first.submissionId);
2055
+ const payload = yield* Schema.decodeEffect(SubmissionSettledRecord)(SubmissionSettled.make({
2056
+ submissionId: first.submissionId,
2057
+ settlementId: submissionSettlementId(first.submissionId),
2058
+ receiptId: first.receiptId,
2059
+ outcome: scenario.outcome,
2060
+ ..."noRun" in scenario ? {} : { runId },
2061
+ ...scenario.outcome === "aborted" ? {} : { result: scenario.outcome === "failed" ? {
2062
+ errorTag: "TestFailure",
2063
+ message: "Failed task"
2064
+ } : { answer: "done" } },
2065
+ ..."disposition" in scenario ? { runDisposition: scenario.disposition } : {},
2066
+ ..."exhausted" in scenario ? {
2067
+ finishReason: "budget-exhausted",
2068
+ exhausted: "turns"
2069
+ } : {}
2070
+ })).pipe(Effect.orDie);
2071
+ const record = RecordEnvelope.make({
2072
+ recordId: submissionSettlementRecordId(first.submissionId),
2073
+ family: "thread",
2074
+ schemaVersion: 1,
2075
+ createdAt: CONFORMANCE_CREATED_AT,
2076
+ deploymentId: CONFORMANCE_DEPLOYMENT,
2077
+ payload
2078
+ });
2079
+ let queued;
2080
+ if ("queued" in scenario) {
2081
+ const next = yield* admissionRequest(threadId, "correction", { text: "correction" });
2082
+ queued = yield* ledger.admit(AdmissionRequest.make({
2083
+ ...next,
2084
+ workerAdmission: {
2085
+ ...workerAdmission,
2086
+ messageId: next.idempotencyKey
2087
+ }
2088
+ }));
2089
+ yield* ledger.markReady(MarkReadyRequest.make({ submissionId: queued.submissionId }));
2090
+ if ("joined" in scenario || "joining" in scenario) yield* ledger.claimJoining(ClaimJoiningRequest.make({
2091
+ threadId,
2092
+ hostSubmissionId: first.submissionId,
2093
+ ownershipToken: claim.ownershipToken,
2094
+ maxCount: 1
2095
+ }));
2096
+ if ("joined" in scenario) yield* ledger.markJoined(MarkJoinedRequest.make({
2097
+ submissionId: queued.submissionId,
2098
+ ownershipToken: claim.ownershipToken,
2099
+ recordId: submissionInputRecordId(queued.submissionId),
2100
+ sequence: Schema.decodeSync(CanonicalSequence)(2)
2101
+ }));
2102
+ }
2103
+ yield* ledger.reserveSettlement(SettlementReservation.make({
2104
+ submissionId: first.submissionId,
2105
+ ownershipToken: claim.ownershipToken,
2106
+ settlementId: payload.settlementId,
2107
+ outcome: scenario.outcome,
2108
+ record,
2109
+ recordDigest: yield* digestJson(yield* Schema.encodeEffect(RecordEnvelope)(record).pipe(Effect.orDie))
2110
+ }));
2111
+ const finalization = SettlementFinalization.make({
2112
+ submissionId: first.submissionId,
2113
+ settlementId: payload.settlementId
2114
+ });
2115
+ const settled = yield* ledger.finalizeSettlement(finalization);
2116
+ yield* ensure(Schema.toEquivalence(Settlement)(settled, yield* ledger.finalizeSettlement(finalization)), "Finalization replay must preserve its exact outcome");
2117
+ const control = yield* ledger.inspectWorker(threadId);
2118
+ yield* ensure(control.terminal === scenario.terminal && control.stopped === (scenario.terminal !== void 0), `Wrong assignment state for ${scenario.name}`);
2119
+ yield* ensure((yield* ledger.admit(request)).receiptId === first.receiptId, "A seal must preserve same-command receipt replay");
2120
+ const next = yield* admissionRequest(threadId, "later", { text: "later" });
2121
+ const later = AdmissionRequest.make({
2122
+ ...next,
2123
+ workerAdmission: {
2124
+ ...workerAdmission,
2125
+ messageId: next.idempotencyKey
2126
+ }
2127
+ });
2128
+ if (scenario.terminal !== void 0) {
2129
+ const refusal = yield* expectFailure("terminal admission", ledger.admit(later));
2130
+ yield* ensure(Schema.is(AdmissionPolicyError)(refusal) && refusal.code === "worker-stopped", "New instructions cannot reopen a terminal assignment");
2131
+ } else yield* ledger.admit(later);
2132
+ if (queued !== void 0) {
2133
+ if ("joining" in scenario) yield* ledger.revertJoining(RevertJoiningRequest.make({ submissionId: queued.submissionId }));
2134
+ yield* ensure((yield* recoverySnapshot(queued.submissionId)).abortIntent !== void 0 === (scenario.outcome === "failed"), "Failure aborts queued inputs; completion never cancels an accepted correction or joined member");
2135
+ }
2136
+ if (scenario.terminal !== void 0 && ledger.stopWorker !== void 0) {
2137
+ yield* ledger.stopWorker({
2138
+ threadId,
2139
+ author: CONFORMANCE_PRINCIPAL
2140
+ });
2141
+ yield* ensure((yield* ledger.inspectWorker(threadId)).terminal === scenario.terminal, "Explicit stop cannot replace an assignment's first terminal outcome");
2142
+ }
2143
+ }
2144
+ })),
1308
2145
  messageAdmissionIdentity,
1309
2146
  workerCompletionIdentity,
1310
2147
  workerUpdateIdentity,
@@ -1328,665 +2165,21 @@ const submissionLedgerConformanceCases = [
1328
2165
  markJoinedIdempotency,
1329
2166
  claimNeverGrantsBlockedHead,
1330
2167
  approvalDecisionIdempotency,
1331
- conformanceCase("unknown resolutions reopen the lane only when no open call remains", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1332
- const threadId = decodeThreadId("ledger-conformance-unknown");
1333
- const ledger = yield* SubmissionLedger;
1334
- const admitted = yield* admitReady(threadId, "unknown-key-1", { work: "unknown" });
1335
- const claim = yield* expectSome("the claim before the unknown marking", yield* claimLane(threadId, PRODUCER_A));
1336
- const call1 = decodeToolCallId("call-unknown-1");
1337
- const call2 = decodeToolCallId("call-unknown-2");
1338
- const call3 = decodeToolCallId("call-unknown-3");
1339
- const marking = MarkUnknownRequest.make({
1340
- submissionId: admitted.submissionId,
1341
- toolCallIds: [call1, call2],
1342
- reason: "the worker died during two supplier calls"
1343
- });
1344
- yield* ledger.markUnknown(marking);
1345
- yield* ledger.markUnknown(marking);
1346
- yield* ensure((yield* expectSome("lookup after marking unknown", yield* lookupById(admitted.submissionId))).state === "unknown", "Marking unknown must be idempotent and block the lane");
1347
- yield* advancePastLease(claim.leaseExpiresAt);
1348
- yield* ensure(Option.isNone(yield* claimLane(threadId, PRODUCER_B)), "An unknown lane must consume no worker permit");
1349
- const first = yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1350
- submissionId: admitted.submissionId,
1351
- toolCallId: call1,
1352
- author: "conformance-operator",
1353
- reason: "supplier store shows no booking",
1354
- resolution: ResolutionNeverHappened.make()
1355
- }));
1356
- yield* ensure((yield* expectSome("lookup after a partial resolution", yield* lookupById(admitted.submissionId))).state === "unknown" && Option.isNone(yield* claimLane(threadId, PRODUCER_B)), "A partial resolution must keep the lane blocked while calls remain open");
1357
- yield* TestClock.adjust("1 second");
1358
- const divergent = yield* expectFailure("re-resolving the same call divergently", ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1359
- submissionId: admitted.submissionId,
1360
- toolCallId: call1,
1361
- author: "conformance-operator",
1362
- reason: "changed my mind",
1363
- resolution: ResolutionSafeToRetry.make()
1364
- })));
1365
- yield* ensure(isUnknownResolutionConflict(divergent) && divergent.toolCallId === call1, "A divergent re-resolution must conflict");
1366
- const replayed = yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1367
- submissionId: admitted.submissionId,
1368
- toolCallId: call1,
1369
- author: "conformance-operator-second",
1370
- reason: "a different reason text",
1371
- resolution: ResolutionNeverHappened.make()
1372
- }));
1373
- yield* ensure(sameInstant(replayed.resolvedAt, first.resolvedAt) && replayed.reason === first.reason, "Repeating the same resolution must replay the recorded intent unchanged");
1374
- yield* ledger.markUnknown(MarkUnknownRequest.make({
1375
- submissionId: admitted.submissionId,
1376
- toolCallIds: [call3],
1377
- reason: "a second wave of uncertainty"
1378
- }));
1379
- yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1380
- submissionId: admitted.submissionId,
1381
- toolCallId: call2,
1382
- author: "conformance-operator",
1383
- reason: "supplier confirmed the booking",
1384
- resolution: ResolutionCompletedWithResult.make({
1385
- result: { bookingRef: "booking-1" },
1386
- isFailure: false
1387
- })
1388
- }));
1389
- yield* ensure((yield* expectSome("lookup while the extended set stays open", yield* lookupById(admitted.submissionId))).state === "unknown" && Option.isNone(yield* claimLane(threadId, PRODUCER_B)), "The lane must stay blocked while any marked call lacks a resolution");
1390
- yield* ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1391
- submissionId: admitted.submissionId,
1392
- toolCallId: call3,
1393
- author: "conformance-operator",
1394
- reason: "idempotency key covers a repeat",
1395
- resolution: ResolutionSafeToRetry.make()
1396
- }));
1397
- yield* ensure((yield* expectSome("lookup after the covering resolution", yield* lookupById(admitted.submissionId))).state === "input-applied", "Covering every marked call must reopen the lane as input-applied");
1398
- yield* ensure((yield* recoverySnapshot(admitted.submissionId)).unknownResolutions.length === 3, "The recovery snapshot must expose every recorded resolution intent");
1399
- const reclaim = yield* expectSome("the claim after the lane reopened", yield* claimLane(threadId, PRODUCER_B));
1400
- yield* ensure(reclaim.submissionId === admitted.submissionId && reclaim.producerEpoch > claim.producerEpoch, "The reopened lane must grant a fresh fenced Attempt");
1401
- yield* settleClaimed(admitted, reclaim.ownershipToken);
1402
- const late = yield* expectFailure("resolving an unknown call for a settled Submission", ledger.recordUnknownResolution(UnknownResolutionCommand.make({
1403
- submissionId: admitted.submissionId,
1404
- toolCallId: decodeToolCallId("call-unknown-late"),
1405
- author: "conformance-operator",
1406
- reason: "too late",
1407
- resolution: ResolutionNeverHappened.make()
1408
- })));
1409
- yield* ensure(isSettlementConflict(late), "A resolution must never land on a settled Submission");
1410
- })),
2168
+ unknownResolutionLifecycle,
1411
2169
  unknownAbortClaim,
1412
- conformanceCase("suspend with an already-present decision resumes immediately", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1413
- const threadId = decodeThreadId("ledger-conformance-suspend");
1414
- const ledger = yield* SubmissionLedger;
1415
- const admitted = yield* admitReady(threadId, "suspend-key-1", { work: "suspend" });
1416
- const claim = yield* expectSome("the claim before suspension", yield* claimLane(threadId, PRODUCER_A));
1417
- const callA = decodeToolCallId("call-suspend-a");
1418
- const callB = decodeToolCallId("call-suspend-b");
1419
- const foreign = yield* expectFailure("suspending without owning the lane", ledger.suspend(SuspendRequest.make({
1420
- submissionId: admitted.submissionId,
1421
- ownershipToken: BOGUS_TOKEN,
1422
- reason: ApprovalPendingSuspension.make({ toolCallIds: [callA] })
1423
- })));
1424
- yield* ensure(isOwnershipLost(foreign), "suspend must be fenced by the owning token");
1425
- yield* ledger.recordApprovalDecision(ApprovalDecisionCommand.make({
1426
- submissionId: admitted.submissionId,
1427
- toolCallId: callA,
1428
- decision: "approved",
1429
- resolver: "conformance-approver",
1430
- reason: "decided before the suspend committed"
1431
- }));
1432
- yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1433
- submissionId: admitted.submissionId,
1434
- ownershipToken: claim.ownershipToken,
1435
- reason: ApprovalPendingSuspension.make({ toolCallIds: [callA] })
1436
- }))) === "resume-immediately", "A fully-decided suspension reason must resume immediately");
1437
- yield* ensure((yield* expectSome("lookup after the immediate resume", yield* lookupById(admitted.submissionId))).state === "running", "An immediate resume must not transition the Submission to suspended");
1438
- yield* ledger.renewOwnership(RenewOwnershipRequest.make({
1439
- submissionId: admitted.submissionId,
1440
- ownershipToken: claim.ownershipToken
1441
- }));
1442
- yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1443
- submissionId: admitted.submissionId,
1444
- ownershipToken: claim.ownershipToken,
1445
- reason: ApprovalPendingSuspension.make({ toolCallIds: [callA, callB] })
1446
- }))) === "suspended", "An undecided call in the reason must suspend durably");
1447
- yield* ensure((yield* expectSome("lookup after the suspension", yield* lookupById(admitted.submissionId))).state === "suspended", "The suspension must be durable");
1448
- const suspendedSnapshot = yield* recoverySnapshot(admitted.submissionId);
1449
- yield* ensure(suspendedSnapshot.suspension !== void 0 && suspendedSnapshot.suspension.reason._tag === "ApprovalPending" && suspendedSnapshot.suspension.reason.toolCallIds.length === 2 && suspendedSnapshot.approvalDecisions.length === 1, "The recovery snapshot must expose the suspension reason and prior decisions");
1450
- const ended = yield* expectFailure("renewing after the suspension ended the ownership period", ledger.renewOwnership(RenewOwnershipRequest.make({
1451
- submissionId: admitted.submissionId,
1452
- ownershipToken: claim.ownershipToken
1453
- })));
1454
- yield* ensure(isOwnershipLost(ended), "Suspension must end the ownership period without settling");
1455
- yield* ledger.recordApprovalDecision(ApprovalDecisionCommand.make({
1456
- submissionId: admitted.submissionId,
1457
- toolCallId: callB,
1458
- decision: "denied",
1459
- resolver: "conformance-approver",
1460
- reason: "the covering decision"
1461
- }));
1462
- yield* ensure((yield* expectSome("lookup after the covering decision", yield* lookupById(admitted.submissionId))).state === "input-applied", "The covering decision must wake the suspended lane");
1463
- yield* ensure((yield* recoverySnapshot(admitted.submissionId)).suspension === void 0, "Waking must clear the durable suspension");
1464
- const reclaim = yield* expectSome("the claim after waking", yield* claimLane(threadId, PRODUCER_B));
1465
- yield* settleClaimed(admitted, reclaim.ownershipToken);
1466
- const terminal = yield* expectFailure("suspending a settled Submission", ledger.suspend(SuspendRequest.make({
1467
- submissionId: admitted.submissionId,
1468
- ownershipToken: reclaim.ownershipToken,
1469
- reason: ApprovalPendingSuspension.make({ toolCallIds: [callA] })
1470
- })));
1471
- yield* ensure(isSettlementConflict(terminal) && terminal.existingOutcome === "completed", "Suspension must never land on a settled Submission");
1472
- })),
1473
- conformanceCase("a joined Submission's settlement reservation is authorized by host linkage", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1474
- const threadId = decodeThreadId("ledger-conformance-joined-settlement");
1475
- const ledger = yield* SubmissionLedger;
1476
- const host = yield* admitReady(threadId, "joined-settle-host", { work: "host" });
1477
- const queued = yield* admitReady(threadId, "joined-settle-2", { queued: 2 });
1478
- const hostClaim = yield* expectSome("the host claim", yield* claimLane(threadId, PRODUCER_A));
1479
- const claims = yield* ledger.claimJoining(ClaimJoiningRequest.make({
1480
- threadId,
1481
- hostSubmissionId: host.submissionId,
1482
- ownershipToken: hostClaim.ownershipToken,
1483
- maxCount: 1
1484
- }));
1485
- yield* ensure(claims.length === 1 && claims[0]?.submissionId === queued.submissionId, "The queued Submission must join the host's contiguous prefix");
1486
- const joiningReservation = yield* settlementReservation({
1487
- submissionId: queued.submissionId,
1488
- ownershipToken: BOGUS_TOKEN,
1489
- receiptId: queued.receiptId,
1490
- outcome: "completed"
1491
- });
1492
- const fenced = yield* expectFailure("reserving a joining Submission's settlement without lane ownership", ledger.reserveSettlement(joiningReservation));
1493
- yield* ensure(isOwnershipLost(fenced), "A joining Submission's settlement reservation must stay ownership-fenced");
1494
- yield* ledger.markJoined(MarkJoinedRequest.make({
1495
- submissionId: queued.submissionId,
1496
- ownershipToken: hostClaim.ownershipToken,
1497
- recordId: submissionInputRecordId(queued.submissionId),
1498
- sequence: decodeSequence(7)
1499
- }));
1500
- const joinedReservation = yield* settlementReservation({
1501
- submissionId: queued.submissionId,
1502
- ownershipToken: BOGUS_TOKEN,
1503
- receiptId: queued.receiptId,
1504
- outcome: "completed"
1505
- });
1506
- yield* ledger.reserveSettlement(joinedReservation);
1507
- yield* ensure((yield* ledger.finalizeSettlement(SettlementFinalization.make({
1508
- submissionId: queued.submissionId,
1509
- settlementId: submissionSettlementId(queued.submissionId)
1510
- }))).outcome === "completed", "The joined settlement must finalize with the reserved outcome");
1511
- const settled = yield* expectSome("lookup after the joined settlement", yield* lookupById(queued.submissionId));
1512
- yield* ensure(settled.state === "settled" && settled.settledOutcome === "completed", "The joined Submission must settle terminally");
1513
- })),
1514
- conformanceCase("replays an identical child reservation and rejects a divergent allocation digest", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1515
- const threadId = decodeThreadId("ledger-conformance-child-reserve");
1516
- const ledger = yield* SubmissionLedger;
1517
- const parent = yield* admitReady(threadId, "child-reserve-parent", { work: "parent" });
1518
- const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1519
- const allocation = {
1520
- turns: 4,
1521
- toolCalls: 8
1522
- };
1523
- const allocationDigest = yield* digestJson(allocation);
1524
- const requestFields = {
1525
- reservationId: decodeChildReservationId("child-reservation:run-reserve:call-1"),
1526
- parentSubmissionId: parent.submissionId,
1527
- parentToolCallId: decodeToolCallId("call-child-reserve"),
1528
- ownershipToken: claim.ownershipToken,
1529
- allocation,
1530
- allocationDigest
1531
- };
1532
- const request = ChildBudgetReservationRequest.make(requestFields);
1533
- const first = yield* ledger.reserveChildBudget(request);
1534
- yield* ensure(!first.replayed && first.reservation.status === "reserved" && first.reservation.allocationDigest === allocationDigest && first.reservation.childSubmissionId === void 0 && persistedJsonEquivalent(first.reservation.allocation, allocation), "The first reservation must create a reserved row carrying the exact allocation");
1535
- yield* TestClock.adjust("1 second");
1536
- const replayed = yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1537
- ...requestFields,
1538
- allocation: {
1539
- toolCalls: 8,
1540
- turns: 4
1541
- }
1542
- }));
1543
- yield* ensure(replayed.replayed && replayed.reservation.status === "reserved" && persistedJsonEquivalent(replayed.reservation.allocation, allocation) && sameInstant(replayed.reservation.reservedAt, first.reservation.reservedAt), "An identical reservation must replay the stored row unchanged");
1544
- const divergentAllocation = {
1545
- turns: 64,
1546
- toolCalls: 8
1547
- };
1548
- const divergent = yield* expectFailure("a divergent allocation for the same reservation id", ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1549
- ...requestFields,
1550
- allocation: divergentAllocation,
1551
- allocationDigest: yield* digestJson(divergentAllocation)
1552
- })));
1553
- yield* ensure(isChildReservationConflict(divergent) && divergent.status === "reserved", "A divergent allocation must conflict with the recorded reservation");
1554
- const secondId = yield* expectFailure("a second reservation id for the same parent Tool Call", ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1555
- ...requestFields,
1556
- reservationId: decodeChildReservationId("child-reservation:run-reserve:call-1-other")
1557
- })));
1558
- yield* ensure(isChildReservationConflict(secondId), "One parent Tool Call must never own two reservations");
1559
- const snapshot = yield* recoverySnapshot(parent.submissionId);
1560
- yield* ensure(snapshot.childReservations.length === 1 && snapshot.childReservations[0].reservationId === request.reservationId && snapshot.childReservations[0].status === "reserved" && snapshot.childAttachments.length === 0, "The parent recovery snapshot must expose the reservation before any attachment");
1561
- })),
1562
- conformanceCase("a stale parent token cannot transition a child reservation", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1563
- const threadId = decodeThreadId("ledger-conformance-child-fence");
1564
- const childLane = decodeThreadId("ledger-conformance-child-fence-child");
1565
- const ledger = yield* SubmissionLedger;
1566
- const parent = yield* admitReady(threadId, "child-fence-parent", { work: "parent" });
1567
- const child = yield* admitReady(childLane, "child-fence-child", { work: "child" });
1568
- const firstClaim = yield* expectSome("the first parent claim", yield* claimLane(threadId, PRODUCER_A));
1569
- const allocation = { turns: 2 };
1570
- const allocationDigest = yield* digestJson(allocation);
1571
- const requestFields = {
1572
- reservationId: decodeChildReservationId("child-reservation:run-fence:call-1"),
1573
- parentSubmissionId: parent.submissionId,
1574
- parentToolCallId: decodeToolCallId("call-child-fence"),
1575
- ownershipToken: BOGUS_TOKEN,
1576
- allocation,
1577
- allocationDigest
1578
- };
1579
- const request = ChildBudgetReservationRequest.make(requestFields);
1580
- const foreign = yield* expectFailure("creating a reservation without owning the parent lane", ledger.reserveChildBudget(request));
1581
- yield* ensure(isOwnershipLost(foreign), "Reservation creation must be fenced by the parent's live ownership token");
1582
- yield* ensure((yield* recoverySnapshot(parent.submissionId)).childReservations.length === 0, "A fenced reservation attempt must leave no row behind");
1583
- yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1584
- ...requestFields,
1585
- ownershipToken: firstClaim.ownershipToken
1586
- }));
1587
- yield* ledger.releaseOwnership(ReleaseOwnershipRequest.make({
1588
- submissionId: parent.submissionId,
1589
- ownershipToken: firstClaim.ownershipToken
1590
- }));
1591
- const reclaim = yield* expectSome("the replacement parent claim", yield* claimLane(threadId, PRODUCER_B));
1592
- yield* ensure(reclaim.producerEpoch > firstClaim.producerEpoch, "The replacement claim must fence the stale parent Attempt");
1593
- const staleAttach = yield* expectFailure("attaching a child with the superseded parent token", ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1594
- reservationId: request.reservationId,
1595
- ownershipToken: firstClaim.ownershipToken,
1596
- childSubmissionId: child.submissionId
1597
- })));
1598
- yield* ensure(isOwnershipLost(staleAttach), "A superseded parent token must not attach a child");
1599
- const staleCreate = yield* expectFailure("creating a second-call reservation with the superseded parent token", ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1600
- ...requestFields,
1601
- reservationId: decodeChildReservationId("child-reservation:run-fence:call-2"),
1602
- parentToolCallId: decodeToolCallId("call-child-fence-second"),
1603
- ownershipToken: firstClaim.ownershipToken
1604
- })));
1605
- yield* ensure(isOwnershipLost(staleCreate), "A superseded parent token must not create new reservation state");
1606
- const staleReplay = yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1607
- ...requestFields,
1608
- ownershipToken: firstClaim.ownershipToken
1609
- }));
1610
- yield* ensure(staleReplay.replayed && staleReplay.reservation.status === "reserved", "An identical reservation replay must return the recorded row even from a stale caller");
1611
- yield* ensure((yield* ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1612
- reservationId: request.reservationId,
1613
- ownershipToken: reclaim.ownershipToken,
1614
- childSubmissionId: child.submissionId
1615
- }))).childSubmissionId === child.submissionId, "The live replacement token must attach the child");
1616
- })),
1617
- conformanceCase("attachChildToReservation is idempotent and rejects a divergent child", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1618
- const threadId = decodeThreadId("ledger-conformance-child-attach");
1619
- const childLane = decodeThreadId("ledger-conformance-child-attach-child");
1620
- const ledger = yield* SubmissionLedger;
1621
- const parent = yield* admitReady(threadId, "child-attach-parent", { work: "parent" });
1622
- const child = yield* admitReady(childLane, "child-attach-child", { queued: 1 });
1623
- const otherChild = yield* admitReady(childLane, "child-attach-other", { queued: 2 });
1624
- const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1625
- const allocation = { turns: 3 };
1626
- const reservationId = decodeChildReservationId("child-reservation:run-attach:call-1");
1627
- yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1628
- reservationId,
1629
- parentSubmissionId: parent.submissionId,
1630
- parentToolCallId: decodeToolCallId("call-child-attach"),
1631
- ownershipToken: claim.ownershipToken,
1632
- allocation,
1633
- allocationDigest: yield* digestJson(allocation)
1634
- }));
1635
- const unknownReservation = yield* expectFailure("attaching to a reservation that was never created", ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1636
- reservationId: decodeChildReservationId("child-reservation:run-attach:missing"),
1637
- ownershipToken: claim.ownershipToken,
1638
- childSubmissionId: child.submissionId
1639
- })));
1640
- yield* ensure(isLedgerError(unknownReservation), "Attaching to an unknown reservation must fail as a ledger error");
1641
- const attached = yield* ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1642
- reservationId,
1643
- ownershipToken: claim.ownershipToken,
1644
- childSubmissionId: child.submissionId
1645
- }));
1646
- yield* ensure(attached.childSubmissionId === child.submissionId && attached.status === "reserved", "Attaching must record the child on the reservation row");
1647
- yield* ensure((yield* ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1648
- reservationId,
1649
- ownershipToken: claim.ownershipToken,
1650
- childSubmissionId: child.submissionId
1651
- }))).childSubmissionId === child.submissionId, "Repeating the identical attachment must be a no-op");
1652
- const divergent = yield* expectFailure("attaching a different child to the same reservation", ledger.attachChildToReservation(AttachChildToReservationRequest.make({
1653
- reservationId,
1654
- ownershipToken: claim.ownershipToken,
1655
- childSubmissionId: otherChild.submissionId
1656
- })));
1657
- yield* ensure(isChildReservationConflict(divergent), "A divergent child attachment must conflict with the recorded child");
1658
- const snapshot = yield* recoverySnapshot(parent.submissionId);
1659
- yield* ensure(snapshot.childAttachments.length === 1 && snapshot.childAttachments[0].childSubmissionId === child.submissionId && snapshot.childAttachments[0].toolCallId === decodeToolCallId("call-child-attach") && snapshot.childAttachments[0].childState === "ready" && snapshot.childAttachments[0].childOutcome === void 0, "The parent recovery snapshot must expose the attachment with the child's lane state");
1660
- })),
1661
- conformanceCase("beginRelease freezes the accounting decision exactly once", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1662
- const threadId = decodeThreadId("ledger-conformance-child-freeze");
1663
- const ledger = yield* SubmissionLedger;
1664
- const parent = yield* admitReady(threadId, "child-freeze-parent", { work: "parent" });
1665
- const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1666
- const allocation = { turns: 4 };
1667
- const reservationId = decodeChildReservationId("child-reservation:run-freeze:call-1");
1668
- yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1669
- reservationId,
1670
- parentSubmissionId: parent.submissionId,
1671
- parentToolCallId: decodeToolCallId("call-child-freeze"),
1672
- ownershipToken: claim.ownershipToken,
1673
- allocation,
1674
- allocationDigest: yield* digestJson(allocation)
1675
- }));
1676
- const accounting = {
1677
- consumed: { turns: 1 },
1678
- released: { turns: 3 }
1679
- };
1680
- const frozen = yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1681
- reservationId,
1682
- accounting
1683
- }));
1684
- yield* ensure(frozen.status === "releasePending" && frozen.accounting !== void 0 && persistedJsonEquivalent(frozen.accounting, accounting) && frozen.releaseBeganAt !== void 0, "The first beginRelease must freeze the accounting and move to releasePending");
1685
- yield* TestClock.adjust("1 second");
1686
- const replayed = yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1687
- reservationId,
1688
- accounting: {
1689
- released: { turns: 3 },
1690
- consumed: { turns: 1 }
1691
- }
1692
- }));
1693
- yield* ensure(replayed.status === "releasePending" && replayed.releaseBeganAt !== void 0 && frozen.releaseBeganAt !== void 0 && sameInstant(replayed.releaseBeganAt, frozen.releaseBeganAt), "Replaying the identical accounting must be a no-op with the frozen decision retained");
1694
- const divergent = yield* expectFailure("freezing a different accounting decision", ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1695
- reservationId,
1696
- accounting: {
1697
- consumed: { turns: 4 },
1698
- released: { turns: 0 }
1699
- }
1700
- })));
1701
- yield* ensure(isChildReservationConflict(divergent) && divergent.status === "releasePending", "A divergent accounting freeze must conflict with the frozen decision");
1702
- yield* ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId }));
1703
- const afterRelease = yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1704
- reservationId,
1705
- accounting
1706
- }));
1707
- yield* ensure(afterRelease.status === "released" && afterRelease.accounting !== void 0 && persistedJsonEquivalent(afterRelease.accounting, accounting), "Replaying the identical accounting after release must return the released row");
1708
- const divergentAfterRelease = yield* expectFailure("freezing a different accounting decision after release", ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1709
- reservationId,
1710
- accounting: {
1711
- consumed: { turns: 2 },
1712
- released: { turns: 2 }
1713
- }
1714
- })));
1715
- yield* ensure(isChildReservationConflict(divergentAfterRelease) && divergentAfterRelease.status === "released", "The frozen decision must stay immutable after release");
1716
- })),
1717
- conformanceCase("release returns unused allocation exactly once", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1718
- const threadId = decodeThreadId("ledger-conformance-child-release");
1719
- const ledger = yield* SubmissionLedger;
1720
- const parent = yield* admitReady(threadId, "child-release-parent", { work: "parent" });
1721
- const claim = yield* expectSome("the parent claim", yield* claimLane(threadId, PRODUCER_A));
1722
- const allocation = { turns: 4 };
1723
- const reservationId = decodeChildReservationId("child-reservation:run-release:call-1");
1724
- yield* ledger.reserveChildBudget(ChildBudgetReservationRequest.make({
1725
- reservationId,
1726
- parentSubmissionId: parent.submissionId,
1727
- parentToolCallId: decodeToolCallId("call-child-release"),
1728
- ownershipToken: claim.ownershipToken,
1729
- allocation,
1730
- allocationDigest: yield* digestJson(allocation)
1731
- }));
1732
- const early = yield* expectFailure("releasing before the accounting decision is frozen", ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId })));
1733
- yield* ensure(isChildReservationConflict(early) && early.status === "reserved", "Release must never skip the releasePending freeze");
1734
- yield* ledger.beginChildBudgetRelease(BeginChildBudgetReleaseRequest.make({
1735
- reservationId,
1736
- accounting: {
1737
- consumed: {},
1738
- released: { turns: 4 }
1739
- }
1740
- }));
1741
- const released = yield* ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId }));
1742
- yield* ensure(released.status === "released" && released.releasedAt !== void 0, "Release must transition releasePending to released");
1743
- yield* TestClock.adjust("1 second");
1744
- const replayed = yield* ledger.releaseChildBudget(ReleaseChildBudgetRequest.make({ reservationId }));
1745
- yield* ensure(replayed.status === "released" && replayed.releasedAt !== void 0 && released.releasedAt !== void 0 && sameInstant(replayed.releasedAt, released.releasedAt), "Replaying the release must return the stored row unchanged — never applied twice");
1746
- const snapshot = yield* recoverySnapshot(parent.submissionId);
1747
- yield* ensure(snapshot.childReservations.length === 1 && snapshot.childReservations[0].status === "released", "The recovery snapshot must expose the released reservation");
1748
- })),
1749
- conformanceCase("recordChildSettled accepts canonical terminalizing prefixes and wakes only when all are covered", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1750
- const parentLane = decodeThreadId("ledger-conformance-child-wake");
1751
- const childLaneA = decodeThreadId("ledger-conformance-child-wake-a");
1752
- const childLaneB = decodeThreadId("ledger-conformance-child-wake-b");
1753
- const ledger = yield* SubmissionLedger;
1754
- const parent = yield* admitReady(parentLane, "child-wake-parent", { work: "parent" });
1755
- const childA = yield* admitReady(childLaneA, "child-wake-a", { child: "a" });
1756
- const childB = yield* admitReady(childLaneB, "child-wake-b", { child: "b" });
1757
- const parentClaim = yield* expectSome("the parent claim", yield* claimLane(parentLane, PRODUCER_A));
1758
- yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1759
- submissionId: parent.submissionId,
1760
- ownershipToken: parentClaim.ownershipToken,
1761
- reason: WaitingForChildSuspension.make({ children: [WaitingChild.make({
1762
- toolCallId: decodeToolCallId("call-wake-a"),
1763
- childSubmissionId: childA.submissionId
1764
- }), WaitingChild.make({
1765
- toolCallId: decodeToolCallId("call-wake-b"),
1766
- childSubmissionId: childB.submissionId
1767
- })] })
1768
- }))) === "suspended", "Unsettled children must suspend the parent durably");
1769
- const ended = yield* expectFailure("renewing after the waitingForChild suspension ended the ownership period", ledger.renewOwnership(RenewOwnershipRequest.make({
1770
- submissionId: parent.submissionId,
1771
- ownershipToken: parentClaim.ownershipToken
1772
- })));
1773
- yield* ensure(isOwnershipLost(ended), "waitingForChild must end the ownership period without settling (SUB-030)");
1774
- yield* ensure(Option.isNone(yield* claimLane(parentLane, PRODUCER_B)), "A waitingForChild head must produce no claim and consume no worker permit");
1775
- const childClaimA = yield* expectSome("the first child claim", yield* claimLane(childLaneA, PRODUCER_A));
1776
- const childReservationA = yield* settlementReservation({
1777
- submissionId: childA.submissionId,
1778
- ownershipToken: childClaimA.ownershipToken,
1779
- receiptId: childA.receiptId,
1780
- outcome: "completed"
1781
- });
1782
- yield* ledger.reserveSettlement(childReservationA);
1783
- yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1784
- parentSubmissionId: parent.submissionId,
1785
- childSubmissionId: childA.submissionId
1786
- }))) === "still-waiting", "A settlement notification must not wake the parent while a listed child is unsettled");
1787
- yield* ledger.finalizeSettlement(SettlementFinalization.make({
1788
- submissionId: childA.submissionId,
1789
- settlementId: childReservationA.settlementId
1790
- }));
1791
- yield* ensure((yield* expectSome("lookup while one child is outstanding", yield* lookupById(parent.submissionId))).state === "suspended" && Option.isNone(yield* claimLane(parentLane, PRODUCER_B)), "The parent lane must stay suspended until every listed child settled");
1792
- const childClaimB = yield* expectSome("the second child claim", yield* claimLane(childLaneB, PRODUCER_A));
1793
- const childReservationB = yield* settlementReservation({
1794
- submissionId: childB.submissionId,
1795
- ownershipToken: childClaimB.ownershipToken,
1796
- receiptId: childB.receiptId,
1797
- outcome: "completed"
1798
- });
1799
- yield* ledger.reserveSettlement(childReservationB);
1800
- yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1801
- parentSubmissionId: parent.submissionId,
1802
- childSubmissionId: childB.submissionId
1803
- }))) === "woken", "The covering canonical settlement prefix must wake the parent before child finalization");
1804
- yield* ledger.finalizeSettlement(SettlementFinalization.make({
1805
- submissionId: childB.submissionId,
1806
- settlementId: childReservationB.settlementId
1807
- }));
1808
- yield* ensure((yield* expectSome("lookup after the covering settlement", yield* lookupById(parent.submissionId))).state === "input-applied", "The woken parent must transition suspended(WaitingForChild) to input-applied");
1809
- yield* ensure((yield* recoverySnapshot(parent.submissionId)).suspension === void 0, "Waking must clear the durable suspension");
1810
- yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1811
- parentSubmissionId: parent.submissionId,
1812
- childSubmissionId: childA.submissionId
1813
- }))) === "not-waiting", "Replaying a notification after the wake must be an idempotent no-op");
1814
- const reclaim = yield* expectSome("the parent claim after waking", yield* claimLane(parentLane, PRODUCER_B));
1815
- yield* ensure(reclaim.submissionId === parent.submissionId && reclaim.producerEpoch > parentClaim.producerEpoch, "The woken lane must grant a fresh fenced Attempt");
1816
- yield* settleClaimed(parent, reclaim.ownershipToken);
1817
- yield* ensure((yield* ledger.recordChildSettled(ChildSettledNotification.make({
1818
- parentSubmissionId: parent.submissionId,
1819
- childSubmissionId: childB.submissionId
1820
- }))) === "not-waiting", "A notification for a settled parent must answer not-waiting");
1821
- })),
1822
- conformanceCase("suspend returns resume-immediately when children already settled", ({ ensure, expectSome }) => Effect.gen(function* () {
1823
- const parentLane = decodeThreadId("ledger-conformance-child-raced");
1824
- const childLaneA = decodeThreadId("ledger-conformance-child-raced-a");
1825
- const childLaneB = decodeThreadId("ledger-conformance-child-raced-b");
1826
- const ledger = yield* SubmissionLedger;
1827
- const settledChild = yield* admitReady(childLaneA, "child-raced-a", { child: "a" });
1828
- const settledClaim = yield* expectSome("the settled child's claim", yield* claimLane(childLaneA, PRODUCER_A));
1829
- yield* settleClaimed(settledChild, settledClaim.ownershipToken);
1830
- const pendingChild = yield* admitReady(childLaneB, "child-raced-b", { child: "b" });
1831
- const parent = yield* admitReady(parentLane, "child-raced-parent", { work: "parent" });
1832
- const parentClaim = yield* expectSome("the parent claim", yield* claimLane(parentLane, PRODUCER_A));
1833
- yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1834
- submissionId: parent.submissionId,
1835
- ownershipToken: parentClaim.ownershipToken,
1836
- reason: WaitingForChildSuspension.make({ children: [WaitingChild.make({
1837
- toolCallId: decodeToolCallId("call-raced-a"),
1838
- childSubmissionId: settledChild.submissionId
1839
- })] })
1840
- }))) === "resume-immediately", "A suspend listing only settled children must resume immediately");
1841
- yield* ensure((yield* expectSome("lookup after the immediate resume", yield* lookupById(parent.submissionId))).state === "running", "An immediate resume must not transition the parent to suspended");
1842
- yield* ledger.renewOwnership(RenewOwnershipRequest.make({
1843
- submissionId: parent.submissionId,
1844
- ownershipToken: parentClaim.ownershipToken
1845
- }));
1846
- yield* ensure((yield* ledger.suspend(SuspendRequest.make({
1847
- submissionId: parent.submissionId,
1848
- ownershipToken: parentClaim.ownershipToken,
1849
- reason: WaitingForChildSuspension.make({ children: [WaitingChild.make({
1850
- toolCallId: decodeToolCallId("call-raced-a"),
1851
- childSubmissionId: settledChild.submissionId
1852
- }), WaitingChild.make({
1853
- toolCallId: decodeToolCallId("call-raced-b"),
1854
- childSubmissionId: pendingChild.submissionId
1855
- })] })
1856
- }))) === "suspended", "One unsettled listed child must suspend the parent durably");
1857
- const snapshot = yield* recoverySnapshot(parent.submissionId);
1858
- yield* ensure(snapshot.suspension !== void 0 && snapshot.suspension.reason._tag === "WaitingForChild" && snapshot.suspension.reason.children.length === 2, "The recovery snapshot must expose the WaitingForChild reason with its children");
1859
- })),
1860
- conformanceCase("admit records and replays parent linkage", ({ ensure, expectFailure, expectSome }) => Effect.gen(function* () {
1861
- const parentLane = decodeThreadId("ledger-conformance-linkage");
1862
- const childLane = decodeThreadId("ledger-conformance-linkage-child");
1863
- const ledger = yield* SubmissionLedger;
1864
- const parent = yield* admitReady(parentLane, "linkage-parent", { work: "parent" });
1865
- const linkage = ParentLinkage.make({
1866
- parentSubmissionId: parent.submissionId,
1867
- parentToolCallId: decodeToolCallId("call-linkage")
1868
- });
1869
- const request = yield* admissionRequest(childLane, "linkage-child-key", { task: "research" }, linkage);
1870
- const admitted = yield* ledger.admit(request);
1871
- yield* ensure(!admitted.replayed, "The first linked admission must create the child");
1872
- const byId = yield* expectSome("lookup of the linked child", yield* lookupById(admitted.submissionId));
1873
- yield* ensure(byId.parentLinkage !== void 0 && byId.parentLinkage.parentSubmissionId === parent.submissionId && byId.parentLinkage.parentToolCallId === linkage.parentToolCallId, "The child snapshot must expose its immutable parent linkage");
1874
- const childSnapshot = yield* recoverySnapshot(admitted.submissionId);
1875
- yield* ensure(childSnapshot.parentLinkage !== void 0 && childSnapshot.parentLinkage.parentSubmissionId === parent.submissionId, "The child recovery snapshot must expose its parent linkage");
1876
- const replayed = yield* ledger.admit(request);
1877
- yield* ensure(replayed.replayed && replayed.submissionId === admitted.submissionId && replayed.receiptId === admitted.receiptId, "An identical linked admission must replay the original identities (SUB-016)");
1878
- const divergentLinkage = yield* expectFailure("replaying the admission with a different parent Tool Call", ledger.admit(yield* admissionRequest(childLane, "linkage-child-key", { task: "research" }, ParentLinkage.make({
1879
- parentSubmissionId: parent.submissionId,
1880
- parentToolCallId: decodeToolCallId("call-linkage-other")
1881
- }))));
1882
- yield* ensure(isAdmissionConflict(divergentLinkage), "A divergent parent linkage must conflict even when the input digest matches");
1883
- const droppedLinkage = yield* expectFailure("replaying the admission without its parent linkage", ledger.admit(yield* admissionRequest(childLane, "linkage-child-key", { task: "research" })));
1884
- yield* ensure(isAdmissionConflict(droppedLinkage), "Dropping the recorded linkage on replay must conflict");
1885
- const plain = yield* ledger.admit(yield* admissionRequest(childLane, "linkage-plain-key", { task: "plain" }));
1886
- const addedLinkage = yield* expectFailure("replaying an unlinked admission with a parent linkage", ledger.admit(yield* admissionRequest(childLane, "linkage-plain-key", { task: "plain" }, linkage)));
1887
- yield* ensure(isAdmissionConflict(addedLinkage), "Adding a linkage to an unlinked admission on replay must conflict");
1888
- yield* ensure((yield* expectSome("lookup of the unlinked Submission", yield* lookupById(plain.submissionId))).parentLinkage === void 0, "An unlinked Submission must expose no parent linkage");
1889
- })),
1890
- conformanceCase("resolveAdmission distinguishes notAdmitted from admitted authoritatively", ({ ensure, expectSome }) => Effect.gen(function* () {
1891
- const threadId = decodeThreadId("ledger-conformance-resolve");
1892
- const ledger = yield* SubmissionLedger;
1893
- const key = SubmissionLookupByKey.make({
1894
- threadId,
1895
- principal: CONFORMANCE_PRINCIPAL,
1896
- idempotencyKey: decodeIdempotencyKey("resolve-key-1")
1897
- });
1898
- yield* ensure((yield* ledger.resolveAdmission(key))._tag === "NotAdmitted", "The authoritative store must prove absence before admission (SUB-031)");
1899
- const admitted = yield* ledger.admit(yield* admissionRequest(threadId, "resolve-key-1", { work: "resolve" }));
1900
- const after = yield* ledger.resolveAdmission(key);
1901
- yield* ensure(after._tag === "Admitted" && after.submission.submissionId === admitted.submissionId && after.submission.receiptId === admitted.receiptId && after.submission.state === "admitted", "An admitted key must resolve to the full authoritative snapshot");
1902
- yield* ensure((yield* ledger.resolveAdmission(SubmissionLookupByKey.make({
1903
- threadId,
1904
- principal: OTHER_PRINCIPAL,
1905
- idempotencyKey: decodeIdempotencyKey("resolve-key-1")
1906
- })))._tag === "NotAdmitted", "Admission resolution must stay scoped to the requesting principal");
1907
- yield* ledger.markReady(MarkReadyRequest.make({ submissionId: admitted.submissionId }));
1908
- const claim = yield* expectSome("the claim before terminal resolution", yield* claimLane(threadId, PRODUCER_A));
1909
- yield* settleClaimed(admitted, claim.ownershipToken);
1910
- const settled = yield* ledger.resolveAdmission(key);
1911
- yield* ensure(settled._tag === "Admitted" && settled.submission.state === "settled" && settled.submission.settledOutcome === "completed", "A settled Submission still resolves as admitted — terminality never becomes absence");
1912
- })),
1913
- conformanceCase("reserveSettlement authorizes an aborted, unowned queued settlement by its durable intent", ({ ensure, expectFailure }) => Effect.gen(function* () {
1914
- const threadId = decodeThreadId("ledger-conformance-queued-abort");
1915
- const ledger = yield* SubmissionLedger;
1916
- const head = yield* admitReady(threadId, "queued-abort-head", { work: "head" });
1917
- const second = yield* admitReady(threadId, "queued-abort-2", { queued: 2 });
1918
- const third = yield* admitReady(threadId, "queued-abort-3", { queued: 3 });
1919
- yield* ledger.requestAbort(AbortCommand.make({
1920
- submissionId: second.submissionId,
1921
- author: "operator",
1922
- reason: "cancelled while queued"
1923
- }));
1924
- const unaborted = yield* expectFailure("reserving an aborted settlement for a queued row without an abort intent", settlementReservation({
1925
- submissionId: third.submissionId,
1926
- ownershipToken: BOGUS_TOKEN,
1927
- receiptId: third.receiptId,
1928
- outcome: "aborted"
1929
- }).pipe(Effect.flatMap((reservation) => ledger.reserveSettlement(reservation))));
1930
- yield* ensure(isOwnershipLost(unaborted), "A queued reservation without a durable abort intent must stay fenced (OwnershipLost)");
1931
- const wrongOutcome = yield* expectFailure("reserving a completed settlement for the aborted queued row", settlementReservation({
1932
- submissionId: second.submissionId,
1933
- ownershipToken: BOGUS_TOKEN,
1934
- receiptId: second.receiptId,
1935
- outcome: "completed",
1936
- result: { fabricated: true }
1937
- }).pipe(Effect.flatMap((reservation) => ledger.reserveSettlement(reservation))));
1938
- yield* ensure(isOwnershipLost(wrongOutcome), "An abort intent must never authorize a non-aborted settlement outcome");
1939
- const reservation = yield* settlementReservation({
1940
- submissionId: second.submissionId,
1941
- ownershipToken: BOGUS_TOKEN,
1942
- receiptId: second.receiptId,
1943
- outcome: "aborted"
1944
- });
1945
- const reserved = yield* ledger.reserveSettlement(reservation);
1946
- yield* ensure(reserved.replayed === false && reserved.outcome === "aborted", "The abort intent must authorize the aborted reservation without lane ownership");
1947
- yield* ensure((yield* ledger.reserveSettlement(reservation)).replayed === true, "Replaying the identical aborted reservation must short-circuit idempotently");
1948
- yield* ledger.finalizeSettlement(SettlementFinalization.make({
1949
- submissionId: second.submissionId,
1950
- settlementId: submissionSettlementId(second.submissionId)
1951
- }));
1952
- const settled = yield* recoverySnapshot(second.submissionId);
1953
- yield* ensure(settled.submission.state === "settled" && settled.submission.settledOutcome === "aborted", "The aborted queued Submission must settle while the head is still unsettled");
1954
- yield* ensure((yield* recoverySnapshot(head.submissionId)).submission.state === "ready", "Settling the aborted queued row must not disturb the unclaimed head");
1955
- })),
1956
- conformanceCase("claimJoining treats an aborted-settled row as a non-gap", ({ ensure, expectSome }) => Effect.gen(function* () {
1957
- const threadId = decodeThreadId("ledger-conformance-aborted-non-gap");
1958
- const ledger = yield* SubmissionLedger;
1959
- const host = yield* admitReady(threadId, "aborted-gap-host", { work: "host" });
1960
- const second = yield* admitReady(threadId, "aborted-gap-2", { queued: 2 });
1961
- const third = yield* admitReady(threadId, "aborted-gap-3", { queued: 3 });
1962
- yield* ledger.requestAbort(AbortCommand.make({
1963
- submissionId: second.submissionId,
1964
- author: "operator",
1965
- reason: "cancelled while queued"
1966
- }));
1967
- const reservation = yield* settlementReservation({
1968
- submissionId: second.submissionId,
1969
- ownershipToken: BOGUS_TOKEN,
1970
- receiptId: second.receiptId,
1971
- outcome: "aborted"
1972
- });
1973
- yield* ledger.reserveSettlement(reservation);
1974
- yield* ledger.finalizeSettlement(SettlementFinalization.make({
1975
- submissionId: second.submissionId,
1976
- settlementId: submissionSettlementId(second.submissionId)
1977
- }));
1978
- const hostClaim = yield* expectSome("the host claim", yield* claimLane(threadId, PRODUCER_A));
1979
- yield* ensure(hostClaim.submissionId === host.submissionId, "The host must head the lane (the aborted-settled row is out of the queue)");
1980
- const claims = yield* ledger.claimJoining(ClaimJoiningRequest.make({
1981
- threadId,
1982
- hostSubmissionId: host.submissionId,
1983
- ownershipToken: hostClaim.ownershipToken,
1984
- maxCount: 8
1985
- }));
1986
- yield* ensure(claims.length === 1 && claims[0].submissionId === third.submissionId, "The joining prefix must skip the aborted-settled row and claim the later ready work");
1987
- const settled = yield* recoverySnapshot(second.submissionId);
1988
- yield* ensure(settled.submission.state === "settled" && settled.submission.settledOutcome === "aborted", "Walking the prefix must never disturb the aborted-settled row");
1989
- }))
2170
+ suspendResumesImmediatelyWhenDecided,
2171
+ joinedSettlementLinkageAuthority,
2172
+ childReservationIdempotency,
2173
+ childReservationFencing,
2174
+ attachChildIdempotency,
2175
+ beginReleaseFreezesAccountingOnce,
2176
+ releaseAppliedExactlyOnce,
2177
+ recordChildSettledWake,
2178
+ suspendResumesImmediatelyForSettledChildren,
2179
+ admissionParentLinkage,
2180
+ resolveAdmissionAuthority,
2181
+ queuedAbortSettlementAuthority,
2182
+ abortedSettledRowIsNotAJoiningGap
1990
2183
  ];
1991
2184
  //#endregion
1992
2185
  export { SubmissionLedgerConformanceViolation, submissionLedgerConformanceCases };