@effect-agent/storage-cloudflare 0.1.0-beta.23 → 0.1.0-beta.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AbortCommand, AbortIntent, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionRequest, AdmissionResolution, AdmissionResult, AppendConflict, AppendResult, ApprovalConflict, ApprovalDecision, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, CanonicalBatch, CanonicalRecord, CanonicalRecordEnvelope, CanonicalSequence, CheckpointRejected, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildReservationStatus, ChildSettledNotification, ChildSettledOutcome, Claim, ClaimJoiningRequest, ClaimRequest, ConversationCheckpoint, ConversationExport, ConversationExportRequest, ConversationMaterialization, ConversationNotMaterialized, ConversationObservation, ConversationRead, ConversationStore, ConversationStoreError, ConversationTail, ConversationTailRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, Digest, EMPTY_TAIL_DIGEST, FenceRejected, FencedAppendRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, LoadCheckpointRequest, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, ObservationOffset, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, ParentLinkage, PersistedJson, ProducerEpoch, QueueSequence, RecordEnvelope, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, SaveCheckpointRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementOutcome, SettlementReservation, SettlementReservationSnapshot, SubmissionLedger, SubmissionLookup, SubmissionLookupById, SubmissionLookupByKey, SubmissionSnapshot, SubmissionState, SuspendRequest, SuspensionReason, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, digestCanonicalBatch, settlementFailureFromRecord, submissionAbortRecordId } from "@effect-agent/session";
2
- import { Clock, Context, Crypto, DateTime, Duration, Effect, Layer, Option, Ref, Schema, Stream } from "effect";
2
+ import { Clock, Context, Crypto, DateTime, Duration, Effect, Layer, Option, Predicate, Ref, Schema, Stream } from "effect";
3
3
  import { SqliteClient, SqliteMigrator } from "@effect/sql-sqlite-do";
4
4
  import * as SqlClient from "effect/unstable/sql/SqlClient";
5
5
  import { BrowserCrypto } from "@effect/platform-browser";
@@ -394,6 +394,7 @@ const MAX_RECORDS_PER_CONVERSATION = 65536;
394
394
  const MAX_IDENTIFIER_LENGTH$1 = 1024;
395
395
  /** Durable Object SQL storage allows at most 100 bound parameters per statement. */
396
396
  const MAX_BOUND_PARAMETERS = 100;
397
+ const isSqlError = Schema.is(SqlError);
397
398
  const storedTextBytes = (value) => new TextEncoder().encode(value).byteLength;
398
399
  const chunked = (values, size) => {
399
400
  const chunks = [];
@@ -587,7 +588,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
587
588
  * nested transactions, so new journal operations must not wrap this helper inside another
588
589
  * transaction.
589
590
  */
590
- const withWriteTransaction = (operation) => (effect) => sql.withTransaction(effect).pipe(Effect.mapError((error) => error instanceof SqlError ? storageError(operation)(error) : error), Effect.withSpan("DoJournal.withWriteTransaction", { attributes: { operation } }));
591
+ const withWriteTransaction = (operation) => (effect) => sql.withTransaction(effect).pipe(Effect.mapError((error) => isSqlError(error) ? storageError(operation)(error) : error), Effect.withSpan("DoJournal.withWriteTransaction", { attributes: { operation } }));
591
592
  const materialize = Effect.fn("DoJournal.materialize")(function* (conversationId, createdAt, emptyTailDigest, producerEpoch) {
592
593
  if (conversationId.length > MAX_IDENTIFIER_LENGTH$1) return yield* DoStorageError.make({
593
594
  operation: "materialize conversation",
@@ -1119,6 +1120,9 @@ const OffsetText = Schema.String.check(Schema.isMaxLength(4 * 1024));
1119
1120
  const DO_OFFSET_PREFIX = "effect-agent-do@1:";
1120
1121
  const ZERO_CANONICAL_SEQUENCE = Schema.decodeSync(CanonicalSequence)(0);
1121
1122
  const isDigest = Schema.is(Digest);
1123
+ const isDoFenceRejected = Schema.is(DoFenceRejected);
1124
+ const isDoAppendConflict = Schema.is(DoAppendConflict);
1125
+ const isDoCheckpointConflict = Schema.is(DoCheckpointConflict);
1122
1126
  const storeError = (operation, error) => ConversationStoreError.make({
1123
1127
  cause: error,
1124
1128
  operation,
@@ -1347,8 +1351,8 @@ const makeServices$1 = Effect.fn("DoConversationStore.makeServices")(function* (
1347
1351
  }).pipe(Effect.mapError((error) => schemaStoreError("encode canonical append", error)));
1348
1352
  yield* hitFailpoint("append:before");
1349
1353
  const result = yield* journal.append(rawRequest).pipe(Effect.mapError((error) => {
1350
- if (error instanceof DoFenceRejected) return mapFence(validated.conversationId, error);
1351
- if (error instanceof DoAppendConflict) return error.actualTailSequence !== void 0 && isDigest(error.actualTailDigest) ? AppendConflict.make({
1354
+ if (isDoFenceRejected(error)) return mapFence(validated.conversationId, error);
1355
+ if (isDoAppendConflict(error)) return error.actualTailSequence !== void 0 && isDigest(error.actualTailDigest) ? AppendConflict.make({
1352
1356
  conversationId: validated.conversationId,
1353
1357
  batchId: validated.batch.batchId,
1354
1358
  reason: error.reason,
@@ -1449,7 +1453,7 @@ const makeServices$1 = Effect.fn("DoConversationStore.makeServices")(function* (
1449
1453
  checkpointJson
1450
1454
  });
1451
1455
  yield* hitFailpoint("save-checkpoint:before");
1452
- yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => error instanceof DoCheckpointConflict ? CheckpointRejected.make({
1456
+ yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => isDoCheckpointConflict(error) ? CheckpointRejected.make({
1453
1457
  conversationId: validated.checkpoint.conversationId,
1454
1458
  reason: "digest-mismatch"
1455
1459
  }) : storeError("save checkpoint", error)));
@@ -1511,10 +1515,7 @@ const storageFailpointLayer = (options) => options.failpoint === void 0 ? DoStor
1511
1515
  * port; point both at the SAME `ctx.storage` so claims fence the same producer epochs
1512
1516
  * (ADR-0011 D7's "same file" rule, transposed to one object's private database).
1513
1517
  */
1514
- const layer = (options) => {
1515
- const sqlLayer = SqliteClient.layer({ storage: options.storage });
1516
- return conversationStoreLayer.pipe(Layer.provide(Layer.mergeAll(storageConfigLayer(options), storageFailpointLayer(options), sqlLayer, BrowserCrypto.layer)));
1517
- };
1518
+ const layer = (options) => Layer.unwrap(Effect.map(DoStorageConfig, (config) => conversationStoreLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(DoStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ storage: options.storage }), BrowserCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
1518
1519
  /** Create an adapter-owned resumable observation offset for a known canonical sequence. */
1519
1520
  const observationOffsetAt = makeOffset;
1520
1521
  //#endregion
@@ -1528,6 +1529,11 @@ const BoundedIdentifier = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
1528
1529
  const BoundedTimestamp = Schema.NonEmptyString.check(Schema.isMaxLength(128));
1529
1530
  const SCAN_PAGE_SIZE = 256;
1530
1531
  const EPOCH_ZERO = Schema.decodeSync(ProducerEpoch)(0);
1532
+ const RESUME_IMMEDIATELY = "resume-immediately";
1533
+ const SUSPENDED = "suspended";
1534
+ const NOT_WAITING = "not-waiting";
1535
+ const STILL_WAITING = "still-waiting";
1536
+ const WOKEN = "woken";
1531
1537
  const MAX_IDENTIFIER_LENGTH = 1024;
1532
1538
  var SubmissionRow = class extends Schema.Class("SubmissionRow")({
1533
1539
  submission_id: BoundedIdentifier,
@@ -1687,6 +1693,9 @@ const decodeUnknownResolutionIntent = Schema.decodeUnknownEffect(UnknownResoluti
1687
1693
  const decodeParentLinkage = Schema.decodeUnknownEffect(ParentLinkage);
1688
1694
  const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(ChildBudgetReservationSnapshot);
1689
1695
  const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
1696
+ const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
1697
+ const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
1698
+ const isDoStorageError = Schema.is(DoStorageError);
1690
1699
  /** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
1691
1700
  const internalFailure = (operation) => (error) => LedgerError.make({
1692
1701
  operation,
@@ -1720,7 +1729,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
1720
1729
  * ownership-token and epoch checks are atomic with their writes (DUR-006). Transaction
1721
1730
  * failures surface as LedgerError carrying the typed `DoStorageError` as cause.
1722
1731
  */
1723
- const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => error instanceof DoStorageError ? internalFailure(operation)(error) : error));
1732
+ const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => isDoStorageError(error) ? internalFailure(operation)(error) : error));
1724
1733
  const mintUuid = (operation) => crypto.randomUUIDv7.pipe(Effect.mapError((error) => internalFailure(operation)(error)));
1725
1734
  const currentInstant = Effect.map(Clock.currentTimeMillis, (millis) => ({
1726
1735
  millis,
@@ -2607,7 +2616,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2607
2616
  if (validated.reason._tag === "ApprovalPending") {
2608
2617
  const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
2609
2618
  const decided = new Set(decisions.map((row) => row.tool_call_id));
2610
- if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return "resume-immediately";
2619
+ if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return RESUME_IMMEDIATELY;
2611
2620
  } else {
2612
2621
  const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
2613
2622
  const markerChildren = new Set(markers.map((row) => row.child_submission_id));
@@ -2616,7 +2625,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2616
2625
  allSettled = false;
2617
2626
  break;
2618
2627
  }
2619
- if (allSettled) return "resume-immediately";
2628
+ if (allSettled) return RESUME_IMMEDIATELY;
2620
2629
  }
2621
2630
  const now = yield* currentInstant;
2622
2631
  yield* sql`
@@ -2631,7 +2640,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2631
2640
  DELETE FROM effect_agent_submission_ownership
2632
2641
  WHERE submission_id = ${validated.submissionId}
2633
2642
  `.pipe(Effect.mapError(sqlFailure(operation)));
2634
- return "suspended";
2643
+ return SUSPENDED;
2635
2644
  }));
2636
2645
  yield* hitFailpoint("ledger:suspend:after", operation);
2637
2646
  return outcome;
@@ -2759,12 +2768,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2759
2768
  });
2760
2769
  }
2761
2770
  const existing = (yield* readUnknownResolutions(operation, validated.submissionId)).find((row) => row.tool_call_id === validated.toolCallId);
2762
- if (existing !== void 0 && existing.resolution_json !== resolutionJson) return yield* UnknownResolutionConflict.make({
2771
+ const existingIntent = existing === void 0 ? void 0 : yield* unknownResolutionIntentFromRow(operation, existing);
2772
+ if (existingIntent !== void 0 && !equivalentUnknownResolution(existingIntent.resolution, validated.resolution)) return yield* UnknownResolutionConflict.make({
2763
2773
  submissionId: validated.submissionId,
2764
2774
  toolCallId: validated.toolCallId
2765
2775
  });
2766
2776
  let resolved;
2767
- if (existing !== void 0) resolved = yield* unknownResolutionIntentFromRow(operation, existing);
2777
+ if (existingIntent !== void 0) resolved = existingIntent;
2768
2778
  else {
2769
2779
  const now = yield* currentInstant;
2770
2780
  yield* sql`
@@ -2839,13 +2849,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2839
2849
  )
2840
2850
  ON CONFLICT (parent_submission_id, child_submission_id) DO NOTHING
2841
2851
  `.pipe(Effect.mapError(sqlFailure(operation)));
2842
- if (parent.state !== "suspended" || parent.suspended_reason_json === null) return "not-waiting";
2852
+ if (parent.state !== "suspended" || parent.suspended_reason_json === null) return NOT_WAITING;
2843
2853
  const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(parent.suspended_reason_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", parent.submission_id, error.message)));
2844
- if (reason._tag !== "WaitingForChild") return "not-waiting";
2845
- if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return "not-waiting";
2854
+ if (reason._tag !== "WaitingForChild") return NOT_WAITING;
2855
+ if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return NOT_WAITING;
2846
2856
  const markers = yield* readChildSettlementMarkers(operation, validated.parentSubmissionId);
2847
2857
  const markerChildren = new Set(markers.map((row) => row.child_submission_id));
2848
- for (const entry of reason.children) if (!(yield* childProvablySettled(operation, markerChildren, entry.childSubmissionId))) return "still-waiting";
2858
+ for (const entry of reason.children) if (!(yield* childProvablySettled(operation, markerChildren, entry.childSubmissionId))) return STILL_WAITING;
2849
2859
  yield* sql`
2850
2860
  UPDATE effect_agent_submissions
2851
2861
  SET
@@ -2854,7 +2864,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2854
2864
  suspended_at = NULL
2855
2865
  WHERE submission_id = ${validated.parentSubmissionId}
2856
2866
  `.pipe(Effect.mapError(sqlFailure(operation)));
2857
- return "woken";
2867
+ return WOKEN;
2858
2868
  }));
2859
2869
  yield* hitFailpoint("ledger:child-settled:after", operation);
2860
2870
  return outcome;
@@ -2867,13 +2877,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2867
2877
  const reserved = yield* inWriteTransaction(operation, Effect.gen(function* () {
2868
2878
  const existing = yield* readChildReservation(operation, validated.reservationId);
2869
2879
  if (Option.isSome(existing)) {
2870
- if (!(existing.value.parent_submission_id === validated.parentSubmissionId && existing.value.parent_tool_call_id === validated.parentToolCallId && existing.value.allocation_digest === validated.allocationDigest && existing.value.allocation_json === allocationJson)) return yield* ChildReservationConflict.make({
2880
+ const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
2881
+ if (!(existing.value.parent_submission_id === validated.parentSubmissionId && existing.value.parent_tool_call_id === validated.parentToolCallId && existing.value.allocation_digest === validated.allocationDigest && equivalentPersistedJson(existingSnapshot.allocation, validated.allocation))) return yield* ChildReservationConflict.make({
2871
2882
  reservationId: validated.reservationId,
2872
2883
  status: existing.value.status,
2873
2884
  message: "A reservation with this identity exists with a different parent Tool Call or allocation."
2874
2885
  });
2875
2886
  return ReservedChildBudget.make({
2876
- reservation: yield* childReservationSnapshotFromRow(operation, existing.value),
2887
+ reservation: existingSnapshot,
2877
2888
  replayed: true
2878
2889
  });
2879
2890
  }
@@ -2964,7 +2975,8 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
2964
2975
  message: `Unknown child reservation ${validated.reservationId}.`
2965
2976
  });
2966
2977
  if (existing.value.status !== "reserved") {
2967
- if (existing.value.accounting_json === accountingJson) return yield* childReservationSnapshotFromRow(operation, existing.value);
2978
+ const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
2979
+ if (existingSnapshot.accounting !== void 0 && equivalentPersistedJson(existingSnapshot.accounting, validated.accounting)) return existingSnapshot;
2968
2980
  return yield* ChildReservationConflict.make({
2969
2981
  reservationId: validated.reservationId,
2970
2982
  status: existing.value.status,
@@ -3201,7 +3213,7 @@ const submissionLedgerLayer = Layer.effectContext(makeServices());
3201
3213
  * A composition-root convenience Layer for the durable Submission Ledger. Point it at the
3202
3214
  * same `ctx.storage` as the ConversationStore so claims fence the same producer epochs.
3203
3215
  */
3204
- const ledgerLayer = (options) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(storageConfigLayer(options), storageFailpointLayer(options), SqliteClient.layer({ storage: options.storage }), BrowserCrypto.layer)));
3216
+ const ledgerLayer = (options) => Layer.unwrap(Effect.map(DoStorageConfig, (config) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(DoStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ storage: options.storage }), BrowserCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
3205
3217
  //#endregion
3206
3218
  //#region src/port-protocol.ts
3207
3219
  /**
@@ -3357,20 +3369,32 @@ var PortTransportError = class extends Schema.TaggedError()("PortTransportError"
3357
3369
  retryable: Schema.optionalKey(Schema.Boolean),
3358
3370
  cause: Schema.optionalKey(Schema.Defect())
3359
3371
  }) {};
3372
+ const safeTransportDiagnostic = (cause) => {
3373
+ try {
3374
+ const message = cause instanceof Error ? cause.message : cause;
3375
+ return boundPortDiagnostic(typeof message === "string" ? message : String(message));
3376
+ } catch {
3377
+ return "[unavailable transport diagnostic]";
3378
+ }
3379
+ };
3380
+ const transportRetryableSignal = (cause) => {
3381
+ if (!Predicate.isObjectKeyword(cause)) return void 0;
3382
+ try {
3383
+ const signal = Reflect.get(cause, "retryable");
3384
+ return typeof signal === "boolean" ? signal : void 0;
3385
+ } catch {
3386
+ return;
3387
+ }
3388
+ };
3360
3389
  /**
3361
3390
  * Build a `PortTransportError` from an arbitrary thrown transport cause, preserving the
3362
3391
  * platform stub's own `retryable` signal when present.
3363
3392
  */
3364
3393
  const portTransportFailure = (target, cause) => {
3365
- const message = cause instanceof Error ? cause.message : String(cause);
3366
- let retryable;
3367
- if (typeof cause === "object" && cause !== null && "retryable" in cause) {
3368
- const signal = cause.retryable;
3369
- if (typeof signal === "boolean") retryable = signal;
3370
- }
3394
+ const retryable = transportRetryableSignal(cause);
3371
3395
  return PortTransportError.make({
3372
3396
  target,
3373
- message: boundPortDiagnostic(message),
3397
+ message: safeTransportDiagnostic(cause),
3374
3398
  ...retryable === void 0 ? {} : { retryable },
3375
3399
  cause
3376
3400
  });
@@ -3409,13 +3433,13 @@ const routableSubmissionTarget = (localConversationId) => Effect.fn("DoPortRouti
3409
3433
  conversationId
3410
3434
  })), Effect.orElseSucceed(() => LOCAL));
3411
3435
  });
3412
- const isResultTag = (tag) => (result) => result._tag === tag;
3413
- const isAdmitConflict = (failure) => failure._tag === "AdmissionConflict";
3414
- const isAbortConflict = (failure) => failure._tag === "SettlementConflict" || failure._tag === "JoinedToHost";
3415
- const noExtraFailure = (_failure) => false;
3416
- const isFenceRejected = (failure) => failure._tag === "FenceRejected";
3417
- const isAppendFailure = (failure) => failure._tag === "ConversationNotMaterialized" || failure._tag === "AppendConflict" || failure._tag === "FenceRejected";
3418
- const isNotMaterialized = (failure) => failure._tag === "ConversationNotMaterialized";
3436
+ const NoAdditionalPortFailure = Schema.Never;
3437
+ const AbortPortFailure = Schema.Union([SettlementConflict, JoinedToHost]);
3438
+ const AppendPortFailure = Schema.Union([
3439
+ ConversationNotMaterialized,
3440
+ AppendConflict,
3441
+ FenceRejected
3442
+ ]);
3419
3443
  /**
3420
3444
  * The fail-fast refusal for any foreign operation OUTSIDE the closed route-capable subset
3421
3445
  * (plan §1.3): honesty over accidental distribution.
@@ -3448,27 +3472,31 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3448
3472
  * any failure outside the operation's declared surface — including protocol anomalies — is
3449
3473
  * folded into a `LedgerError` naming the anomaly instead of being erased or re-thrown raw.
3450
3474
  */
3451
- const foreignLedgerCall = (operation, target, call, resultTag, isExpectedFailure) => transportCall(target, call).pipe(Effect.mapError(routeFailure(operation, target)), Effect.flatMap((response) => {
3452
- if (response._tag === "PortFailed") {
3453
- const failure = response.failure;
3454
- if (isExpectedFailure(failure)) return Effect.fail(failure);
3455
- if (failure._tag === "LedgerError") return Effect.fail(failure);
3456
- return Effect.fail(LedgerError.make({
3475
+ const foreignLedgerCall = (operation, target, call, resultSchema, failureSchema) => {
3476
+ const isExpectedResult = Schema.is(resultSchema);
3477
+ const isExpectedFailure = Schema.is(failureSchema);
3478
+ return transportCall(target, call).pipe(Effect.mapError(routeFailure(operation, target)), Effect.flatMap((response) => {
3479
+ if (response._tag === "PortFailed") {
3480
+ const failure = response.failure;
3481
+ if (isExpectedFailure(failure)) return Effect.fail(failure);
3482
+ if (failure._tag === "LedgerError") return Effect.fail(failure);
3483
+ return Effect.fail(LedgerError.make({
3484
+ operation,
3485
+ message: boundPortDiagnostic(`The Conversation Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
3486
+ cause: failure
3487
+ }));
3488
+ }
3489
+ const result = response.result;
3490
+ if (!isExpectedResult(result)) return Effect.fail(LedgerError.make({
3457
3491
  operation,
3458
- message: boundPortDiagnostic(`The Conversation Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
3459
- cause: failure
3492
+ message: `The Conversation Object owning ${target} answered ${operation} with the mismatched result ${result._tag}.`
3460
3493
  }));
3461
- }
3462
- const result = response.result;
3463
- if (!isResultTag(resultTag)(result)) return Effect.fail(LedgerError.make({
3494
+ return Effect.succeed(result);
3495
+ }), Effect.withSpan("DoPortRouting.foreignLedgerCall", { attributes: {
3464
3496
  operation,
3465
- message: `The Conversation Object owning ${target} answered ${operation} with the mismatched result ${result._tag}; expected ${resultTag}.`
3466
- }));
3467
- return Effect.succeed(result);
3468
- }), Effect.withSpan("DoPortRouting.foreignLedgerCall", { attributes: {
3469
- operation,
3470
- target
3471
- } }));
3497
+ target
3498
+ } }));
3499
+ };
3472
3500
  /**
3473
3501
  * Routed `resolveAdmission` — where the S2 tri-state becomes real (plan §1.3): when the
3474
3502
  * owning Object cannot be reached, or its answer cannot be understood, the routed adapter
@@ -3488,7 +3516,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3488
3516
  PortTransportError: (error) => Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Conversation Object owning ${target} is unreachable: ${error.message}`) })),
3489
3517
  PortProtocolError: (error) => Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The answer of the Conversation Object owning ${target} could not be understood: ${error.message}`) }))
3490
3518
  }), Effect.withSpan("DoPortRouting.resolveForeignAdmission", { attributes: { target } }));
3491
- const foreignLookupById = (operation, target, submissionId) => foreignLedgerCall(operation, target, LedgerLookupCall.make({ request: SubmissionLookupById.make({ submissionId }) }), "LedgerLookupResult", noExtraFailure).pipe(Effect.map((result) => result.submission === void 0 ? Option.none() : Option.some(result.submission)));
3519
+ const foreignLookupById = (operation, target, submissionId) => foreignLedgerCall(operation, target, LedgerLookupCall.make({ request: SubmissionLookupById.make({ submissionId }) }), LedgerLookupResult, NoAdditionalPortFailure).pipe(Effect.map((result) => result.submission === void 0 ? Option.none() : Option.some(result.submission)));
3492
3520
  /**
3493
3521
  * Enrich a LOCAL parent's recovery snapshot with the lane state of attached children whose
3494
3522
  * rows live in other Durable Objects (plan §1.3): markers first (the local facet already
@@ -3529,12 +3557,12 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3529
3557
  });
3530
3558
  const routed = SubmissionLedger.of({
3531
3559
  capabilities: local.capabilities,
3532
- admit: (request) => request.conversationId === options.localConversationId ? local.admit(request) : foreignLedgerCall("ledger admit", request.conversationId, LedgerAdmitCall.make({ request }), "LedgerAdmitResult", isAdmitConflict).pipe(Effect.map((reply) => reply.result)),
3533
- markReady: (request) => submissionTarget("ledger mark ready", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markReady(request) : foreignLedgerCall("ledger mark ready", target.conversationId, LedgerMarkReadyCall.make({ request }), "LedgerMarkReadyResult", noExtraFailure).pipe(Effect.asVoid))),
3534
- lookup: (request) => request._tag === "SubmissionLookupById" ? submissionTarget("ledger lookup", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.lookup(request) : foreignLookupById("ledger lookup", target.conversationId, request.submissionId))) : request.conversationId === options.localConversationId ? local.lookup(request) : foreignLedgerCall("ledger lookup", request.conversationId, LedgerLookupCall.make({ request }), "LedgerLookupResult", noExtraFailure).pipe(Effect.map((result) => result.submission === void 0 ? Option.none() : Option.some(result.submission))),
3560
+ admit: (request) => request.conversationId === options.localConversationId ? local.admit(request) : foreignLedgerCall("ledger admit", request.conversationId, LedgerAdmitCall.make({ request }), LedgerAdmitResult, AdmissionConflict).pipe(Effect.map((reply) => reply.result)),
3561
+ markReady: (request) => submissionTarget("ledger mark ready", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markReady(request) : foreignLedgerCall("ledger mark ready", target.conversationId, LedgerMarkReadyCall.make({ request }), LedgerMarkReadyResult, NoAdditionalPortFailure).pipe(Effect.asVoid))),
3562
+ lookup: (request) => request._tag === "SubmissionLookupById" ? submissionTarget("ledger lookup", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.lookup(request) : foreignLookupById("ledger lookup", target.conversationId, request.submissionId))) : request.conversationId === options.localConversationId ? local.lookup(request) : foreignLedgerCall("ledger lookup", request.conversationId, LedgerLookupCall.make({ request }), LedgerLookupResult, NoAdditionalPortFailure).pipe(Effect.map((result) => result.submission === void 0 ? Option.none() : Option.some(result.submission))),
3535
3563
  resolveAdmission: (request) => request.conversationId === options.localConversationId ? local.resolveAdmission(request) : resolveForeignAdmission(request.conversationId, request),
3536
- requestAbort: (request) => submissionTarget("ledger request abort", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.requestAbort(request) : foreignLedgerCall("ledger request abort", target.conversationId, LedgerRequestAbortCall.make({ request }), "LedgerRequestAbortResult", isAbortConflict).pipe(Effect.map((reply) => reply.intent)))),
3537
- recordChildSettled: (request) => submissionTarget("ledger record child settled", request.parentSubmissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.recordChildSettled(request) : foreignLedgerCall("ledger record child settled", target.conversationId, LedgerRecordChildSettledCall.make({ request }), "LedgerRecordChildSettledResult", noExtraFailure).pipe(Effect.map((reply) => reply.outcome)))),
3564
+ requestAbort: (request) => submissionTarget("ledger request abort", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.requestAbort(request) : foreignLedgerCall("ledger request abort", target.conversationId, LedgerRequestAbortCall.make({ request }), LedgerRequestAbortResult, AbortPortFailure).pipe(Effect.map((reply) => reply.intent)))),
3565
+ recordChildSettled: (request) => submissionTarget("ledger record child settled", request.parentSubmissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.recordChildSettled(request) : foreignLedgerCall("ledger record child settled", target.conversationId, LedgerRecordChildSettledCall.make({ request }), LedgerRecordChildSettledResult, NoAdditionalPortFailure).pipe(Effect.map((reply) => reply.outcome)))),
3538
3566
  claim: (request) => request.conversationId === options.localConversationId ? local.claim(request) : Effect.fail(crossConversationLedgerError("ledger claim", request.conversationId)),
3539
3567
  claimJoining: (request) => request.conversationId === options.localConversationId ? local.claimJoining(request) : Effect.fail(crossConversationLedgerError("ledger claim joining", request.conversationId)),
3540
3568
  renewOwnership: (request) => submissionTarget("ledger renew ownership", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.renewOwnership(request) : Effect.fail(crossConversationLedgerError("ledger renew ownership", target.conversationId)))),
@@ -3567,33 +3595,37 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
3567
3595
  cause: error
3568
3596
  });
3569
3597
  /** The store twin of `foreignLedgerCall` with `ConversationStoreError` as the base error. */
3570
- const foreignStoreCall = (operation, target, call, resultTag, isExpectedFailure) => transportCall(target, call).pipe(Effect.mapError(routeFailure(operation, target)), Effect.flatMap((response) => {
3571
- if (response._tag === "PortFailed") {
3572
- const failure = response.failure;
3573
- if (isExpectedFailure(failure)) return Effect.fail(failure);
3574
- if (failure._tag === "ConversationStoreError") return Effect.fail(failure);
3575
- return Effect.fail(ConversationStoreError.make({
3598
+ const foreignStoreCall = (operation, target, call, resultSchema, failureSchema) => {
3599
+ const isExpectedResult = Schema.is(resultSchema);
3600
+ const isExpectedFailure = Schema.is(failureSchema);
3601
+ return transportCall(target, call).pipe(Effect.mapError(routeFailure(operation, target)), Effect.flatMap((response) => {
3602
+ if (response._tag === "PortFailed") {
3603
+ const failure = response.failure;
3604
+ if (isExpectedFailure(failure)) return Effect.fail(failure);
3605
+ if (failure._tag === "ConversationStoreError") return Effect.fail(failure);
3606
+ return Effect.fail(ConversationStoreError.make({
3607
+ operation,
3608
+ message: boundPortDiagnostic(`The Conversation Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
3609
+ cause: failure
3610
+ }));
3611
+ }
3612
+ const result = response.result;
3613
+ if (!isExpectedResult(result)) return Effect.fail(ConversationStoreError.make({
3576
3614
  operation,
3577
- message: boundPortDiagnostic(`The Conversation Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
3578
- cause: failure
3615
+ message: `The Conversation Object owning ${target} answered ${operation} with the mismatched result ${result._tag}.`
3579
3616
  }));
3580
- }
3581
- const result = response.result;
3582
- if (!isResultTag(resultTag)(result)) return Effect.fail(ConversationStoreError.make({
3617
+ return Effect.succeed(result);
3618
+ }), Effect.withSpan("DoPortRouting.foreignStoreCall", { attributes: {
3583
3619
  operation,
3584
- message: `The Conversation Object owning ${target} answered ${operation} with the mismatched result ${result._tag}; expected ${resultTag}.`
3585
- }));
3586
- return Effect.succeed(result);
3587
- }), Effect.withSpan("DoPortRouting.foreignStoreCall", { attributes: {
3588
- operation,
3589
- target
3590
- } }));
3620
+ target
3621
+ } }));
3622
+ };
3591
3623
  const routed = ConversationStore.of({
3592
- materialize: (request) => request.conversationId === options.localConversationId ? local.materialize(request) : foreignStoreCall("conversation materialize", request.conversationId, StoreMaterializeCall.make({ request }), "StoreMaterializeResult", isFenceRejected).pipe(Effect.asVoid),
3593
- append: (request) => request.conversationId === options.localConversationId ? local.append(request) : foreignStoreCall("conversation append", request.conversationId, StoreAppendCall.make({ request }), "StoreAppendResult", isAppendFailure).pipe(Effect.map((reply) => reply.result)),
3594
- read: (request) => request.conversationId === options.localConversationId ? local.read(request) : Stream.unwrap(foreignStoreCall("conversation read", request.conversationId, StoreReadPageCall.make({ request }), "StoreReadPageResult", isNotMaterialized).pipe(Effect.map((reply) => Stream.fromIterable(reply.records)))),
3595
- inspectTail: (request) => request.conversationId === options.localConversationId ? local.inspectTail(request) : foreignStoreCall("conversation inspect tail", request.conversationId, StoreInspectTailCall.make({ request }), "StoreInspectTailResult", isNotMaterialized).pipe(Effect.map((reply) => reply.tail)),
3596
- export: (request) => request.conversationId === options.localConversationId ? local.export(request) : foreignStoreCall("conversation export", request.conversationId, StoreExportCall.make({ request }), "StoreExportResult", isNotMaterialized).pipe(Effect.map((reply) => reply.export)),
3624
+ materialize: (request) => request.conversationId === options.localConversationId ? local.materialize(request) : foreignStoreCall("conversation materialize", request.conversationId, StoreMaterializeCall.make({ request }), StoreMaterializeResult, FenceRejected).pipe(Effect.asVoid),
3625
+ append: (request) => request.conversationId === options.localConversationId ? local.append(request) : foreignStoreCall("conversation append", request.conversationId, StoreAppendCall.make({ request }), StoreAppendResult, AppendPortFailure).pipe(Effect.map((reply) => reply.result)),
3626
+ read: (request) => request.conversationId === options.localConversationId ? local.read(request) : Stream.unwrap(foreignStoreCall("conversation read", request.conversationId, StoreReadPageCall.make({ request }), StoreReadPageResult, ConversationNotMaterialized).pipe(Effect.map((reply) => Stream.fromIterable(reply.records)))),
3627
+ inspectTail: (request) => request.conversationId === options.localConversationId ? local.inspectTail(request) : foreignStoreCall("conversation inspect tail", request.conversationId, StoreInspectTailCall.make({ request }), StoreInspectTailResult, ConversationNotMaterialized).pipe(Effect.map((reply) => reply.tail)),
3628
+ export: (request) => request.conversationId === options.localConversationId ? local.export(request) : foreignStoreCall("conversation export", request.conversationId, StoreExportCall.make({ request }), StoreExportResult, ConversationNotMaterialized).pipe(Effect.map((reply) => reply.export)),
3597
3629
  observe: (request) => request.conversationId === options.localConversationId ? local.observe(request) : Stream.unwrap(Effect.fail(crossConversationStoreError("conversation observe", request.conversationId))),
3598
3630
  saveCheckpoint: (request) => request.checkpoint.conversationId === options.localConversationId ? local.saveCheckpoint(request) : Effect.fail(crossConversationStoreError("conversation save checkpoint", request.checkpoint.conversationId)),
3599
3631
  loadCheckpoint: (request) => request.conversationId === options.localConversationId ? local.loadCheckpoint(request) : Effect.fail(crossConversationStoreError("conversation load checkpoint", request.conversationId))