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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/DoMemoryStore.mjs +1 -1
  2. package/dist/DoMessageDeliveryStore.d.mts +1 -1
  3. package/dist/DoMessageDeliveryStore.mjs +3 -2
  4. package/dist/DoMessageDeliveryStore.mjs.map +1 -1
  5. package/dist/DoScheduleStore.mjs +16 -37
  6. package/dist/DoScheduleStore.mjs.map +1 -1
  7. package/dist/DoStorageConfig.d.mts +2 -10
  8. package/dist/DoStorageConfig.mjs +2 -11
  9. package/dist/DoStorageConfig.mjs.map +1 -1
  10. package/dist/{DoStorageError-CGuAfSwF.d.mts → DoStorageError-CxjMnzUA.d.mts} +7 -18
  11. package/dist/DoStorageError.d.mts +2 -2
  12. package/dist/DoStorageError.mjs +14 -30
  13. package/dist/DoStorageError.mjs.map +1 -1
  14. package/dist/DoStorageFailpoint.d.mts +1 -1
  15. package/dist/DoStorageFailpointTesting.d.mts +1 -1
  16. package/dist/{DoStorageVersion-iNSyi3cJ.d.mts → DoStorageVersion-DTFoLglB.d.mts} +3 -4
  17. package/dist/DoStorageVersion.d.mts +1 -1
  18. package/dist/DoStorageVersion.mjs +2 -2
  19. package/dist/DoStorageVersion.mjs.map +1 -1
  20. package/dist/DoSubmissionLedger.d.mts +7 -5
  21. package/dist/DoSubmissionLedger.mjs +378 -528
  22. package/dist/DoSubmissionLedger.mjs.map +1 -1
  23. package/dist/DoSubscriptionStore.mjs +5 -24
  24. package/dist/DoSubscriptionStore.mjs.map +1 -1
  25. package/dist/{DoThreadStore-D_83OjCH.d.mts → DoThreadStore-Bd4fTns3.d.mts} +17 -18
  26. package/dist/DoThreadStore-CwBvTLoq.mjs +468 -0
  27. package/dist/DoThreadStore-CwBvTLoq.mjs.map +1 -0
  28. package/dist/DoThreadStore.d.mts +2 -2
  29. package/dist/DoThreadStore.mjs +4 -529
  30. package/dist/MemoryProtocol.mjs +3 -3
  31. package/dist/MemoryProtocol.mjs.map +1 -1
  32. package/dist/PortProtocol.d.mts +11727 -3410
  33. package/dist/PortProtocol.mjs +103 -15
  34. package/dist/PortProtocol.mjs.map +1 -1
  35. package/dist/PortRouting.d.mts +14 -9
  36. package/dist/PortRouting.mjs +155 -29
  37. package/dist/PortRouting.mjs.map +1 -1
  38. package/dist/do-journal-Cawnsetj.mjs +761 -0
  39. package/dist/do-journal-Cawnsetj.mjs.map +1 -0
  40. package/dist/index.d.mts +3 -3
  41. package/dist/index.mjs +1 -1
  42. package/dist/migrations-D_EP3KYf.mjs +148 -0
  43. package/dist/migrations-D_EP3KYf.mjs.map +1 -0
  44. package/dist/{owned-state-DHm2aRXx.mjs → owned-state-BHzUi0eb.mjs} +50 -19
  45. package/dist/owned-state-BHzUi0eb.mjs.map +1 -0
  46. package/package.json +1 -1
  47. package/src/DoMessageDeliveryStore.ts +2 -1
  48. package/src/DoScheduleStore.ts +19 -59
  49. package/src/DoStorageConfig.ts +2 -11
  50. package/src/DoStorageError.ts +3 -26
  51. package/src/DoStorageVersion.ts +1 -1
  52. package/src/DoSubmissionLedger.ts +1806 -2030
  53. package/src/DoSubscriptionStore.ts +5 -47
  54. package/src/DoThreadStore.ts +276 -622
  55. package/src/MemoryProtocol.ts +3 -3
  56. package/src/PortProtocol.ts +198 -12
  57. package/src/PortRouting.ts +527 -108
  58. package/src/internal/canonical-append.ts +15 -0
  59. package/src/internal/do-journal.ts +548 -1440
  60. package/src/internal/migrations.ts +184 -287
  61. package/src/internal/owned-state.ts +81 -30
  62. package/src/internal/prompt-checkpoint.ts +158 -0
  63. package/src/internal/storage-span.ts +2 -5
  64. package/dist/DoThreadStore.mjs.map +0 -1
  65. package/dist/do-journal-DUhjZkOf.mjs +0 -1352
  66. package/dist/do-journal-DUhjZkOf.mjs.map +0 -1
  67. package/dist/migrations-DtKz_Gxr.mjs +0 -306
  68. package/dist/migrations-DtKz_Gxr.mjs.map +0 -1
  69. package/dist/owned-state-DHm2aRXx.mjs.map +0 -1
  70. package/src/internal/message-delivery-schema.ts +0 -24
  71. package/src/internal/recovery-checkpoint-schema.ts +0 -17
@@ -1,6 +1,11 @@
1
1
  import type { DurableRuntimeFailpoint } from "@yielded/agent/durable-failpoint";
2
2
  import { LifecyclePublicationError } from "@yielded/agent/lifecycle-publication";
3
3
  import { MessageDeliveryStore, MessageDeliveryError } from "@yielded/agent/message-delivery";
4
+ import {
5
+ SettlementPublisher,
6
+ type SettlementPublicationResult,
7
+ type SettlementPublicationFailure,
8
+ } from "@yielded/agent/settlement-publisher";
4
9
  import {
5
10
  AdmissionIndeterminate,
6
11
  AdmissionConflict,
@@ -8,6 +13,7 @@ import {
8
13
  ChildAttachmentSnapshot,
9
14
  JoinedToHost,
10
15
  LedgerError,
16
+ OwnershipLost,
11
17
  RecoverySnapshot,
12
18
  SettlementConflict,
13
19
  SubmissionLedger,
@@ -19,10 +25,13 @@ import {
19
25
  AppendConflict,
20
26
  ThreadMaterialization,
21
27
  ThreadNotMaterialized,
28
+ ThreadExportRejected,
22
29
  ThreadStore,
30
+ ThreadReader,
23
31
  ThreadStoreError,
24
- FenceRejected,
25
32
  } from "@yielded/agent/thread-store";
33
+ import { checkExportSource } from "@yielded/agent/thread-transfer";
34
+ import { WorkDiscoveryUnavailable } from "@yielded/agent/thread-work";
26
35
  import type { WakeScheduler } from "@yielded/agent/wake-scheduler";
27
36
  import {
28
37
  admitWorker,
@@ -32,6 +41,8 @@ import {
32
41
  import { Cause, Clock, Context, Effect, Layer, Option, Predicate, Schema, Stream } from "effect";
33
42
 
34
43
  import {
44
+ SettlementPublishCall,
45
+ SettlementPublishResult,
35
46
  WorkerAdmitCall,
36
47
  WorkerAdmitResult,
37
48
  MessageDeliveryListCall,
@@ -45,6 +56,8 @@ import {
45
56
  encodePortResponse,
46
57
  LedgerAdmitCall,
47
58
  LedgerAdmitResult,
59
+ LedgerFundingOwnerCall,
60
+ LedgerFundingOwnerResult,
48
61
  LedgerLookupCall,
49
62
  LedgerLookupResult,
50
63
  LedgerMarkReadyCall,
@@ -62,6 +75,14 @@ import {
62
75
  PortFailed,
63
76
  PortProtocolError,
64
77
  PortSucceeded,
78
+ StoreRangePageCall,
79
+ StoreRangePageResult,
80
+ StoreRangeSealCall,
81
+ StoreRangeSealResult,
82
+ StoreRangeArchiveCall,
83
+ StoreRangeArchiveResult,
84
+ StoreRangeVerifyCall,
85
+ StoreRangeVerifyResult,
65
86
  StoreAppendCall,
66
87
  StoreAppendResult,
67
88
  StoreCountPeerMessagesCall,
@@ -70,12 +91,24 @@ import {
70
91
  StoreExportResult,
71
92
  StoreInspectTailCall,
72
93
  StoreInspectTailResult,
94
+ StoreWorkerCapacityCall,
95
+ StoreWorkerCapacityResult,
96
+ StoreVerificationCall,
97
+ StoreVerificationResult,
73
98
  StoreReadIdentityCall,
74
99
  StoreReadIdentityResult,
100
+ StoreReadExecutionCall,
101
+ StoreReadExecutionResult,
75
102
  StoreMaterializeCall,
76
103
  StoreMaterializeResult,
77
104
  StoreReadPageCall,
78
105
  StoreReadPageResult,
106
+ StoreReadPromptCall,
107
+ StoreReadPromptResult,
108
+ StoreWorkPageCall,
109
+ StoreWorkPageResult,
110
+ StoreWorkRebuildCall,
111
+ StoreWorkRebuildResult,
79
112
  type PortFailure,
80
113
  type PortRequest,
81
114
  type PortRequestEnvelope,
@@ -89,6 +122,12 @@ type SubmissionId = SubmissionSnapshot["submissionId"];
89
122
  const ThreadIdSchema = ThreadMaterialization.fields.threadId;
90
123
  const decodeThreadId = Schema.decodeUnknownEffect(ThreadIdSchema);
91
124
 
125
+ /** Owner-local maintenance scope; permits local inventory without global Object enumeration. */
126
+ export const CurrentNativeSource = Context.Reference<boolean>(
127
+ "@effect-agent/storage-cloudflare/CurrentNativeSource",
128
+ { defaultValue: () => false },
129
+ );
130
+
92
131
  /**
93
132
  * The ledger row bound routable Submission identities must respect (mirrors the local
94
133
  * facet's `MAX_IDENTIFIER_LENGTH`; the minting side already refuses longer identities typed
@@ -200,7 +239,7 @@ const LOCAL: RouteTarget = { _tag: "local" };
200
239
  const routableSubmissionTarget = (
201
240
  ownsThread: RoutedPortOptions["ownsThread"],
202
241
  ): ((operation: string, submissionId: string) => Effect.Effect<RouteTarget, LedgerError>) =>
203
- Effect.fn("DoPortRouting.routableSubmissionTarget")(function* (
242
+ Effect.fnUntraced(function* (
204
243
  operation: string,
205
244
  submissionId: string,
206
245
  ): Effect.fn.Return<RouteTarget, LedgerError> {
@@ -239,7 +278,7 @@ export const makeLocalSubmissionLookup = (
239
278
  ) => Effect.Effect<Option.Option<SubmissionSnapshot>, LedgerError, SubmissionLedger>) => {
240
279
  const submissionTarget = routableSubmissionTarget(options.ownsThread);
241
280
 
242
- return Effect.fn("DoPortRouting.lookupLocalSubmission")(function* (submissionId: SubmissionId) {
281
+ return Effect.fnUntraced(function* (submissionId: SubmissionId) {
243
282
  const operation = "local Submission lookup";
244
283
  const target = yield* submissionTarget(operation, submissionId);
245
284
 
@@ -264,7 +303,7 @@ export const makeLocalSubmissionLookup = (
264
303
 
265
304
  const NoAdditionalPortFailure = Schema.Never;
266
305
  const AbortPortFailure = Schema.Union([SettlementConflict, JoinedToHost]);
267
- const AppendPortFailure = Schema.Union([ThreadNotMaterialized, AppendConflict, FenceRejected]);
306
+ const AppendPortFailure = Schema.Union([ThreadNotMaterialized, AppendConflict, OwnershipLost]);
268
307
 
269
308
  /**
270
309
  * The fail-fast refusal for any foreign operation OUTSIDE the closed route-capable subset
@@ -285,13 +324,13 @@ const crossThreadStoreError = (operation: string, target: string): ThreadStoreEr
285
324
  operation,
286
325
  message:
287
326
  `${operation} addressed to foreign Thread ${target} is not route-capable; the ` +
288
- "closed cross-Object subset is materialize, append, read (paged), inspectTail, and " +
327
+ "closed cross-Object subset is materialize, append, read and readPrompt (paged), inspectTail, and " +
289
328
  "export. Observation and checkpoints are lane-local by construction and must execute " +
290
329
  "inside the owning Thread's Durable Object.",
291
330
  });
292
331
 
293
332
  const makeTransportCall = (transport: ThreadPortTransport["Service"]) =>
294
- Effect.fn("DoPortRouting.transportCall")(function* (target: ThreadId, call: PortRequest) {
333
+ Effect.fnUntraced(function* (target: ThreadId, call: PortRequest) {
295
334
  const encoded = yield* encodePortRequest(call).pipe(
296
335
  Effect.mapError((error) =>
297
336
  PortProtocolError.make({
@@ -313,9 +352,7 @@ const makeTransportCall = (transport: ThreadPortTransport["Service"]) =>
313
352
 
314
353
  type TransportCall = ReturnType<typeof makeTransportCall>;
315
354
 
316
- const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServices")(function* (
317
- options: RoutedPortOptions,
318
- ) {
355
+ const makeRoutedLedgerServices = Effect.fnUntraced(function* (options: RoutedPortOptions) {
319
356
  const local = yield* SubmissionLedger;
320
357
  const transport = yield* ThreadPortTransport;
321
358
  const transportCall: TransportCall = makeTransportCall(transport);
@@ -480,7 +517,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
480
517
  * local nor marker-settled. A transport failure surfaces as `LedgerError` so the alarm
481
518
  * pass retries; the child's canonical Settlement remains the only authority (DUR-015).
482
519
  */
483
- const enrichChildAttachments = Effect.fn("DoPortRouting.enrichChildAttachments")(function* (
520
+ const enrichChildAttachments = Effect.fnUntraced(function* (
484
521
  snapshot: RecoverySnapshot,
485
522
  ): Effect.fn.Return<RecoverySnapshot, LedgerError> {
486
523
  const operation = "ledger load recovery snapshot";
@@ -582,6 +619,37 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
582
619
  ),
583
620
  ),
584
621
 
622
+ resolveFundingOwner: (request) =>
623
+ (options.ownsThread(request.threadId)
624
+ ? local.resolveFundingOwner === undefined
625
+ ? Effect.fail(
626
+ LedgerError.make({
627
+ operation: "resolve funding owner",
628
+ message: "Native funding-owner lookup unavailable.",
629
+ }),
630
+ )
631
+ : local.resolveFundingOwner(request)
632
+ : foreignLedgerCall(
633
+ "resolve funding owner",
634
+ request.threadId,
635
+ LedgerFundingOwnerCall.make({ request }),
636
+ LedgerFundingOwnerResult,
637
+ NoAdditionalPortFailure,
638
+ ).pipe(Effect.map((reply) => reply.funding))
639
+ ).pipe(
640
+ Effect.filterOrFail(
641
+ (funding) =>
642
+ funding.selected.submissionId === request.submissionId &&
643
+ funding.selected.threadId === request.threadId &&
644
+ funding.owner.threadId === request.threadId,
645
+ () =>
646
+ LedgerError.make({
647
+ operation: "resolve funding owner",
648
+ message: "Funding-owner response belongs to another input or Thread.",
649
+ }),
650
+ ),
651
+ ),
652
+
585
653
  resolveAdmission: (request) =>
586
654
  options.ownsThread(request.threadId)
587
655
  ? local.resolveAdmission(request)
@@ -652,9 +720,9 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
652
720
 
653
721
  // Every operation below is lane-local by construction (plan §1.3): a foreign address is
654
722
  // an out-of-contract call and fails fast typed instead of being quietly distributed.
655
- claim: (request) =>
723
+ selectHead: (request) =>
656
724
  options.ownsThread(request.threadId)
657
- ? local.claim(request)
725
+ ? local.selectHead(request)
658
726
  : Effect.fail(crossThreadLedgerError("ledger claim", request.threadId)),
659
727
 
660
728
  claimJoining: (request) =>
@@ -662,24 +730,6 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
662
730
  ? local.claimJoining(request)
663
731
  : Effect.fail(crossThreadLedgerError("ledger claim joining", request.threadId)),
664
732
 
665
- renewOwnership: (request) =>
666
- submissionTarget("ledger renew ownership", request.submissionId).pipe(
667
- Effect.flatMap((target) =>
668
- target._tag === "local"
669
- ? local.renewOwnership(request)
670
- : Effect.fail(crossThreadLedgerError("ledger renew ownership", target.threadId)),
671
- ),
672
- ),
673
-
674
- releaseOwnership: (request) =>
675
- submissionTarget("ledger release ownership", request.submissionId).pipe(
676
- Effect.flatMap((target) =>
677
- target._tag === "local"
678
- ? local.releaseOwnership(request)
679
- : Effect.fail(crossThreadLedgerError("ledger release ownership", target.threadId)),
680
- ),
681
- ),
682
-
683
733
  markInputApplied: (request) =>
684
734
  submissionTarget("ledger mark input applied", request.submissionId).pipe(
685
735
  Effect.flatMap((target) =>
@@ -689,15 +739,6 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
689
739
  ),
690
740
  ),
691
741
 
692
- reserveSettlement: (request) =>
693
- submissionTarget("ledger reserve settlement", request.submissionId).pipe(
694
- Effect.flatMap((target) =>
695
- target._tag === "local"
696
- ? local.reserveSettlement(request)
697
- : Effect.fail(crossThreadLedgerError("ledger reserve settlement", target.threadId)),
698
- ),
699
- ),
700
-
701
742
  finalizeSettlement: (request) =>
702
743
  submissionTarget("ledger finalize settlement", request.submissionId).pipe(
703
744
  Effect.flatMap((target) =>
@@ -806,12 +847,71 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
806
847
  return Context.make(SubmissionLedger, routed);
807
848
  });
808
849
 
809
- const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices")(function* (
810
- options: RoutedPortOptions,
811
- ) {
850
+ /** Route the data-only atomic publication to the same owner as its canonical Thread. */
851
+ export const routedSettlementPublisherLayer = (options: RoutedPortOptions) =>
852
+ Layer.effect(SettlementPublisher)(
853
+ Effect.gen(function* () {
854
+ const local = yield* SettlementPublisher;
855
+ const call = makeTransportCall(yield* ThreadPortTransport);
856
+
857
+ const isFailure = Schema.is(
858
+ Schema.Union([
859
+ LedgerError,
860
+ OwnershipLost,
861
+ SettlementConflict,
862
+ ThreadStoreError,
863
+ ThreadNotMaterialized,
864
+ AppendConflict,
865
+ ]),
866
+ );
867
+
868
+ return SettlementPublisher.of({
869
+ publish: (request) => {
870
+ if (options.ownsThread(request.append.threadId)) return local.publish(request);
871
+
872
+ return call(request.append.threadId, SettlementPublishCall.make({ request })).pipe(
873
+ Effect.mapError((cause) =>
874
+ LedgerError.make({
875
+ operation: "route settlement publication",
876
+ message: cause.message,
877
+ cause,
878
+ }),
879
+ ),
880
+ Effect.flatMap(
881
+ (
882
+ response,
883
+ ): Effect.Effect<SettlementPublicationResult, SettlementPublicationFailure> => {
884
+ if (response._tag === "PortFailed")
885
+ return isFailure(response.failure)
886
+ ? Effect.fail(response.failure)
887
+ : Effect.fail(
888
+ LedgerError.make({
889
+ operation: "route settlement publication",
890
+ message: "Unexpected publication failure",
891
+ cause: response.failure,
892
+ }),
893
+ );
894
+
895
+ return response.result._tag === "SettlementPublishResult"
896
+ ? Effect.succeed(response.result.result)
897
+ : Effect.fail(
898
+ LedgerError.make({
899
+ operation: "route settlement publication",
900
+ message: "Mismatched publication result",
901
+ }),
902
+ );
903
+ },
904
+ ),
905
+ );
906
+ },
907
+ });
908
+ }),
909
+ );
910
+
911
+ const makeRoutedStoreServices = Effect.fnUntraced(function* (options: RoutedPortOptions) {
812
912
  const local = yield* ThreadStore;
813
913
  const checkpoints = local.checkpoints;
814
- const recoveryCheckpoints = local.recoveryCheckpoints;
914
+ const promptCheckpoints = local.promptCheckpoints;
815
915
  const lifecyclePublications = local.lifecyclePublications;
816
916
  const acknowledgeMany = lifecyclePublications?.acknowledgeMany;
817
917
  const transport = yield* ThreadPortTransport;
@@ -885,6 +985,161 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
885
985
  };
886
986
 
887
987
  const routed = ThreadStore.of({
988
+ readExecution: (request) =>
989
+ options.ownsThread(request.threadId)
990
+ ? local.readExecution(request)
991
+ : foreignStoreCall(
992
+ "read execution",
993
+ request.threadId,
994
+ StoreReadExecutionCall.make({ request }),
995
+ StoreReadExecutionResult,
996
+ ThreadNotMaterialized,
997
+ ).pipe(Effect.map((reply) => reply.checkpoint)),
998
+ archives: {
999
+ page: (request) =>
1000
+ options.ownsThread(request.threadId)
1001
+ ? local.archives === undefined
1002
+ ? Effect.fail(crossThreadStoreError("archive unavailable", request.threadId))
1003
+ : local.archives.page(request)
1004
+ : foreignStoreCall(
1005
+ "archive page",
1006
+ request.threadId,
1007
+ StoreRangePageCall.make({ request }),
1008
+ StoreRangePageResult,
1009
+ ThreadNotMaterialized,
1010
+ ).pipe(Effect.map((reply) => reply.page)),
1011
+ seal: (request) =>
1012
+ options.ownsThread(request.threadId)
1013
+ ? local.archives === undefined
1014
+ ? Effect.fail(crossThreadStoreError("archive unavailable", request.threadId))
1015
+ : local.archives.seal(request)
1016
+ : foreignStoreCall(
1017
+ "archive seal",
1018
+ request.threadId,
1019
+ StoreRangeSealCall.make({ request }),
1020
+ StoreRangeSealResult,
1021
+ ThreadNotMaterialized,
1022
+ ).pipe(Effect.map((reply) => reply.range)),
1023
+ archive: (request) =>
1024
+ options.ownsThread(request.threadId)
1025
+ ? local.archives === undefined
1026
+ ? Effect.fail(crossThreadStoreError("archive unavailable", request.threadId))
1027
+ : local.archives.archive(request)
1028
+ : foreignStoreCall(
1029
+ "archive archive",
1030
+ request.threadId,
1031
+ StoreRangeArchiveCall.make({ request }),
1032
+ StoreRangeArchiveResult,
1033
+ ThreadNotMaterialized,
1034
+ ).pipe(Effect.map((reply) => reply.range)),
1035
+ verify: (request) =>
1036
+ options.ownsThread(request.threadId)
1037
+ ? local.archives === undefined
1038
+ ? Effect.fail(crossThreadStoreError("archive unavailable", request.threadId))
1039
+ : local.archives.verify(request)
1040
+ : foreignStoreCall(
1041
+ "archive verify",
1042
+ request.threadId,
1043
+ StoreRangeVerifyCall.make({ request }),
1044
+ StoreRangeVerifyResult,
1045
+ ThreadNotMaterialized,
1046
+ ).pipe(Effect.map((reply) => reply.range)),
1047
+ },
1048
+ work: {
1049
+ threads: (request) =>
1050
+ Effect.flatMap(CurrentNativeSource, (nativeSource) =>
1051
+ nativeSource && local.work !== undefined
1052
+ ? local.work.threads(request)
1053
+ : Effect.fail(
1054
+ ThreadStoreError.make({
1055
+ operation: "work threads",
1056
+ message: nativeSource
1057
+ ? "The local Thread owner does not provide work inventory."
1058
+ : "Global Thread enumeration is unsupported by the owner-local Durable Object namespace. Use the local owner's work storage.",
1059
+ }),
1060
+ ),
1061
+ ),
1062
+ page: (request) =>
1063
+ options.ownsThread(request.threadId)
1064
+ ? local.work === undefined
1065
+ ? Effect.fail(
1066
+ WorkDiscoveryUnavailable.make({
1067
+ threadId: request.threadId,
1068
+ reason: "unsupported",
1069
+ }),
1070
+ )
1071
+ : local.work.page(request)
1072
+ : foreignStoreCall(
1073
+ "work page",
1074
+ request.threadId,
1075
+ StoreWorkPageCall.make({ request }),
1076
+ StoreWorkPageResult,
1077
+ WorkDiscoveryUnavailable,
1078
+ ).pipe(Effect.map((reply) => reply.page)),
1079
+ rebuild: (request) =>
1080
+ options.ownsThread(request.threadId)
1081
+ ? local.work === undefined
1082
+ ? Effect.fail(
1083
+ WorkDiscoveryUnavailable.make({
1084
+ threadId: request.threadId,
1085
+ reason: "unsupported",
1086
+ }),
1087
+ )
1088
+ : local.work.rebuild(request)
1089
+ : foreignStoreCall(
1090
+ "rebuild work",
1091
+ request.threadId,
1092
+ StoreWorkRebuildCall.make({ request }),
1093
+ StoreWorkRebuildResult,
1094
+ WorkDiscoveryUnavailable,
1095
+ ).pipe(
1096
+ Effect.flatMap((reply) =>
1097
+ reply.progress.threadId === request.threadId
1098
+ ? Effect.succeed(reply.progress)
1099
+ : Effect.fail(
1100
+ ThreadStoreError.make({
1101
+ operation: "rebuild work",
1102
+ message: "The rebuild progress belongs to another Thread.",
1103
+ }),
1104
+ ),
1105
+ ),
1106
+ ),
1107
+ },
1108
+ readWorkerCapacity: (request) =>
1109
+ (options.ownsThread(request.threadId)
1110
+ ? local.readWorkerCapacity === undefined
1111
+ ? Effect.fail(crossThreadStoreError("worker capacity unavailable", request.threadId))
1112
+ : local.readWorkerCapacity(request)
1113
+ : foreignStoreCall(
1114
+ "worker capacity",
1115
+ request.threadId,
1116
+ StoreWorkerCapacityCall.make({ request }),
1117
+ StoreWorkerCapacityResult,
1118
+ ThreadNotMaterialized,
1119
+ ).pipe(Effect.map((reply) => reply.capacity))
1120
+ ).pipe(
1121
+ Effect.filterOrFail(
1122
+ (capacity) =>
1123
+ capacity.threadId === request.threadId &&
1124
+ capacity.tailSequence === request.expectedTailSequence,
1125
+ () => crossThreadStoreError("worker capacity response mismatch", request.threadId),
1126
+ ),
1127
+ ),
1128
+ verification: {
1129
+ verify: (request) =>
1130
+ options.ownsThread(request.threadId)
1131
+ ? local.verification === undefined
1132
+ ? Effect.fail(crossThreadStoreError("verification unavailable", request.threadId))
1133
+ : local.verification.verify(request)
1134
+ : foreignStoreCall(
1135
+ "thread verification",
1136
+ request.threadId,
1137
+ StoreVerificationCall.make({ request }),
1138
+ StoreVerificationResult,
1139
+ ThreadNotMaterialized,
1140
+ ).pipe(Effect.map((reply) => reply.report)),
1141
+ },
1142
+
888
1143
  countPeerMessages: (request) =>
889
1144
  options.ownsThread(request.threadId)
890
1145
  ? local.countPeerMessages === undefined
@@ -906,7 +1161,7 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
906
1161
  request.threadId,
907
1162
  StoreMaterializeCall.make({ request }),
908
1163
  StoreMaterializeResult,
909
- FenceRejected,
1164
+ NoAdditionalPortFailure,
910
1165
  ).pipe(Effect.asVoid),
911
1166
 
912
1167
  append: (request) =>
@@ -933,6 +1188,19 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
933
1188
  ).pipe(Effect.map((reply) => Stream.fromIterable(reply.records))),
934
1189
  ),
935
1190
 
1191
+ readPrompt: (request) =>
1192
+ options.ownsThread(request.threadId)
1193
+ ? local.readPrompt(request)
1194
+ : Stream.unwrap(
1195
+ foreignStoreCall(
1196
+ "thread read prompt",
1197
+ request.threadId,
1198
+ StoreReadPromptCall.make({ request }),
1199
+ StoreReadPromptResult,
1200
+ ThreadNotMaterialized,
1201
+ ).pipe(Effect.map((reply) => Stream.fromIterable(reply.records))),
1202
+ ),
1203
+
936
1204
  inspectTail: (request) =>
937
1205
  options.ownsThread(request.threadId)
938
1206
  ? local.inspectTail(request)
@@ -967,19 +1235,22 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
967
1235
  ),
968
1236
 
969
1237
  export: (request) =>
970
- options.ownsThread(request.threadId)
971
- ? local.export(request)
972
- : foreignStoreCall(
973
- "thread export",
974
- request.threadId,
975
- StoreExportCall.make({ request }),
976
- StoreExportResult,
977
- ThreadNotMaterialized,
978
- ).pipe(Effect.map((reply) => reply.export)),
1238
+ checkExportSource(request.threadId).pipe(
1239
+ Effect.andThen(
1240
+ options.ownsThread(request.threadId)
1241
+ ? local.export(request)
1242
+ : foreignStoreCall(
1243
+ "thread export",
1244
+ request.threadId,
1245
+ StoreExportCall.make({ request }),
1246
+ StoreExportResult,
1247
+ Schema.Union([ThreadNotMaterialized, ThreadExportRejected]),
1248
+ ).pipe(Effect.map((reply) => reply.export)),
1249
+ ),
1250
+ ),
979
1251
 
980
1252
  // Observation and checkpoints are lane-local: the closed route-capable store subset
981
- // is materialize/append/read/readIdentity/inspectTail/export. Recovery cache misses can fall back
982
- // to those canonical reads, including when the cache belongs to a foreign Object.
1253
+ // includes exact-Thread work pages and rebuild, but no global Object enumeration.
983
1254
  observe: (request) =>
984
1255
  options.ownsThread(request.threadId)
985
1256
  ? local.observe(request)
@@ -1015,39 +1286,34 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
1015
1286
  : Effect.fail(LifecyclePublicationError.make({ reason: "unavailable" })),
1016
1287
  },
1017
1288
  }),
1018
- ...(recoveryCheckpoints === undefined
1289
+ ...(checkpoints === undefined
1019
1290
  ? {}
1020
1291
  : {
1021
- recoveryCheckpoints: {
1292
+ checkpoints: {
1022
1293
  save: (request) =>
1023
1294
  options.ownsThread(request.checkpoint.threadId)
1024
- ? recoveryCheckpoints.save(request)
1295
+ ? checkpoints.save(request)
1025
1296
  : Effect.fail(
1026
- crossThreadStoreError(
1027
- "thread save recovery checkpoint",
1028
- request.checkpoint.threadId,
1029
- ),
1297
+ crossThreadStoreError("thread save checkpoint", request.checkpoint.threadId),
1030
1298
  ),
1031
1299
  load: (request) =>
1032
1300
  options.ownsThread(request.threadId)
1033
- ? recoveryCheckpoints.load(request)
1034
- : Effect.succeed(Option.none()),
1301
+ ? checkpoints.load(request)
1302
+ : Effect.fail(crossThreadStoreError("thread load checkpoint", request.threadId)),
1035
1303
  },
1036
1304
  }),
1037
- ...(checkpoints === undefined
1305
+ ...(promptCheckpoints === undefined
1038
1306
  ? {}
1039
1307
  : {
1040
- checkpoints: {
1308
+ promptCheckpoints: {
1041
1309
  save: (request) =>
1042
- options.ownsThread(request.checkpoint.threadId)
1043
- ? checkpoints.save(request)
1044
- : Effect.fail(
1045
- crossThreadStoreError("thread save checkpoint", request.checkpoint.threadId),
1046
- ),
1310
+ options.ownsThread(request.threadId)
1311
+ ? promptCheckpoints.save(request)
1312
+ : Effect.fail(crossThreadStoreError("save prompt checkpoint", request.threadId)),
1047
1313
  load: (request) =>
1048
1314
  options.ownsThread(request.threadId)
1049
- ? checkpoints.load(request)
1050
- : Effect.fail(crossThreadStoreError("thread load checkpoint", request.threadId)),
1315
+ ? promptCheckpoints.load(request)
1316
+ : Effect.fail(crossThreadStoreError("load prompt checkpoint", request.threadId)),
1051
1317
  },
1052
1318
  }),
1053
1319
  });
@@ -1069,14 +1335,16 @@ export const routedSubmissionLedgerLayer = (
1069
1335
 
1070
1336
  /**
1071
1337
  * Routing decorator over the LOCAL `ThreadStore` facet (plan §1.3): this-thread
1072
- * requests execute locally; foreign materialize/append/read/inspectTail/export travel the
1073
- * transport. Foreign recovery-cache loads return none for canonical replay; other foreign
1074
- * checkpoint operations and observation fail fast typed.
1338
+ * requests execute locally; foreign store reads, writes, and exact-Thread work pages/rebuild
1339
+ * travel the transport. Work enumeration stays local under CurrentNativeSource; global
1340
+ * enumeration, foreign checkpoints, and observation fail typed.
1075
1341
  */
1076
1342
  export const routedThreadStoreLayer = (
1077
1343
  options: RoutedPortOptions,
1078
- ): Layer.Layer<ThreadStore, never, ThreadStore | ThreadPortTransport> =>
1079
- Layer.effectContext(makeRoutedStoreServices(options));
1344
+ ): Layer.Layer<ThreadStore | ThreadReader, never, ThreadStore | ThreadPortTransport> =>
1345
+ ThreadReader.layer().pipe(
1346
+ Layer.provideMerge(Layer.effectContext(makeRoutedStoreServices(options))),
1347
+ );
1080
1348
 
1081
1349
  /** Route validated worker admission as one operation; source policy hooks stay at the caller. */
1082
1350
  export const routedWorkerAdmissionLayer = (options: RoutedPortOptions) =>
@@ -1223,14 +1491,105 @@ const capture = <Failure extends PortFailure, R>(
1223
1491
  * Failures never escape — every typed port failure becomes a `PortFailed` envelope that
1224
1492
  * re-decodes on the caller side.
1225
1493
  */
1226
- export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(function* (
1494
+ export const executePortRequest = Effect.fnUntraced(function* (
1227
1495
  request: PortRequest,
1228
1496
  ): Effect.fn.Return<
1229
1497
  PortResponse,
1230
1498
  never,
1231
- SubmissionLedger | ThreadStore | MessageDeliveryStore | WakeScheduler | DurableRuntimeFailpoint
1499
+ | SubmissionLedger
1500
+ | SettlementPublisher
1501
+ | ThreadStore
1502
+ | MessageDeliveryStore
1503
+ | WakeScheduler
1504
+ | DurableRuntimeFailpoint
1232
1505
  > {
1233
1506
  switch (request._tag) {
1507
+ case "StoreRangePage": {
1508
+ const store = yield* ThreadStore;
1509
+
1510
+ return yield* capture(
1511
+ store.archives === undefined
1512
+ ? crossThreadStoreError("archive unavailable", request.request.threadId)
1513
+ : store.archives
1514
+ .page(request.request)
1515
+ .pipe(Effect.map((page) => StoreRangePageResult.make({ page }))),
1516
+ );
1517
+ }
1518
+ case "StoreRangeSeal": {
1519
+ const store = yield* ThreadStore;
1520
+
1521
+ return yield* capture(
1522
+ store.archives === undefined
1523
+ ? crossThreadStoreError("archive unavailable", request.request.threadId)
1524
+ : store.archives
1525
+ .seal(request.request)
1526
+ .pipe(Effect.map((range) => StoreRangeSealResult.make({ range }))),
1527
+ );
1528
+ }
1529
+ case "StoreRangeArchive": {
1530
+ const store = yield* ThreadStore;
1531
+
1532
+ return yield* capture(
1533
+ store.archives === undefined
1534
+ ? crossThreadStoreError("archive unavailable", request.request.threadId)
1535
+ : store.archives
1536
+ .archive(request.request)
1537
+ .pipe(Effect.map((range) => StoreRangeArchiveResult.make({ range }))),
1538
+ );
1539
+ }
1540
+ case "StoreRangeVerify": {
1541
+ const store = yield* ThreadStore;
1542
+
1543
+ return yield* capture(
1544
+ store.archives === undefined
1545
+ ? crossThreadStoreError("archive unavailable", request.request.threadId)
1546
+ : store.archives
1547
+ .verify(request.request)
1548
+ .pipe(Effect.map((range) => StoreRangeVerifyResult.make({ range }))),
1549
+ );
1550
+ }
1551
+
1552
+ case "StoreWorkPage": {
1553
+ const store = yield* ThreadStore;
1554
+
1555
+ return yield* capture(
1556
+ store.work === undefined
1557
+ ? Effect.fail(
1558
+ WorkDiscoveryUnavailable.make({
1559
+ threadId: request.request.threadId,
1560
+ reason: "unsupported",
1561
+ }),
1562
+ )
1563
+ : store.work
1564
+ .page(request.request)
1565
+ .pipe(Effect.map((page) => StoreWorkPageResult.make({ page }))),
1566
+ );
1567
+ }
1568
+ case "StoreWorkRebuild": {
1569
+ const store = yield* ThreadStore;
1570
+
1571
+ return yield* capture(
1572
+ store.work === undefined
1573
+ ? Effect.fail(
1574
+ WorkDiscoveryUnavailable.make({
1575
+ threadId: request.request.threadId,
1576
+ reason: "unsupported",
1577
+ }),
1578
+ )
1579
+ : store.work
1580
+ .rebuild(request.request)
1581
+ .pipe(Effect.map((progress) => StoreWorkRebuildResult.make({ progress }))),
1582
+ );
1583
+ }
1584
+ case "SettlementPublish": {
1585
+ const publisher = yield* SettlementPublisher;
1586
+
1587
+ return yield* capture(
1588
+ publisher
1589
+ .publish(request.request)
1590
+ .pipe(Effect.map((result) => SettlementPublishResult.make({ result }))),
1591
+ );
1592
+ }
1234
1593
  case "MessageDeliveryComplete": {
1235
1594
  const store = yield* MessageDeliveryStore;
1236
1595
 
@@ -1301,6 +1660,22 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
1301
1660
  ),
1302
1661
  );
1303
1662
  }
1663
+ case "LedgerFundingOwner": {
1664
+ const ledger = yield* SubmissionLedger;
1665
+
1666
+ return yield* capture(
1667
+ ledger.resolveFundingOwner === undefined
1668
+ ? Effect.fail(
1669
+ LedgerError.make({
1670
+ operation: "resolve funding owner",
1671
+ message: "Native funding-owner lookup unavailable.",
1672
+ }),
1673
+ )
1674
+ : ledger
1675
+ .resolveFundingOwner(request.request)
1676
+ .pipe(Effect.map((funding) => LedgerFundingOwnerResult.make({ funding }))),
1677
+ );
1678
+ }
1304
1679
  case "LedgerResolveAdmission": {
1305
1680
  const ledger = yield* SubmissionLedger;
1306
1681
 
@@ -1383,6 +1758,16 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
1383
1758
  ),
1384
1759
  );
1385
1760
  }
1761
+ case "StoreReadPrompt": {
1762
+ const store = yield* ThreadStore;
1763
+
1764
+ return yield* capture(
1765
+ store.readPrompt(request.request).pipe(
1766
+ Stream.runCollect,
1767
+ Effect.map((records) => StoreReadPromptResult.make({ records })),
1768
+ ),
1769
+ );
1770
+ }
1386
1771
  case "StoreInspectTail": {
1387
1772
  const store = yield* ThreadStore;
1388
1773
 
@@ -1392,6 +1777,15 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
1392
1777
  .pipe(Effect.map((tail) => StoreInspectTailResult.make({ tail }))),
1393
1778
  );
1394
1779
  }
1780
+ case "StoreReadExecution": {
1781
+ const store = yield* ThreadStore;
1782
+
1783
+ return yield* capture(
1784
+ store
1785
+ .readExecution(request.request)
1786
+ .pipe(Effect.map((checkpoint) => StoreReadExecutionResult.make({ checkpoint }))),
1787
+ );
1788
+ }
1395
1789
  case "StoreReadIdentity": {
1396
1790
  const store = yield* ThreadStore;
1397
1791
 
@@ -1401,6 +1795,30 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
1401
1795
  .pipe(Effect.map((identity) => StoreReadIdentityResult.make({ identity }))),
1402
1796
  );
1403
1797
  }
1798
+ case "StoreWorkerCapacity": {
1799
+ const store = yield* ThreadStore;
1800
+
1801
+ return yield* capture(
1802
+ store.readWorkerCapacity === undefined
1803
+ ? Effect.fail(
1804
+ crossThreadStoreError("worker capacity unavailable", request.request.threadId),
1805
+ )
1806
+ : store
1807
+ .readWorkerCapacity(request.request)
1808
+ .pipe(Effect.map((capacity) => StoreWorkerCapacityResult.make({ capacity }))),
1809
+ );
1810
+ }
1811
+ case "StoreVerification": {
1812
+ const store = yield* ThreadStore;
1813
+
1814
+ return yield* capture(
1815
+ store.verification === undefined
1816
+ ? Effect.fail(crossThreadStoreError("verification unavailable", request.request.threadId))
1817
+ : store.verification
1818
+ .verify(request.request)
1819
+ .pipe(Effect.map((report) => StoreVerificationResult.make({ report }))),
1820
+ );
1821
+ }
1404
1822
  case "StoreExport": {
1405
1823
  const store = yield* ThreadStore;
1406
1824
 
@@ -1430,35 +1848,36 @@ const encodedProtocolFailure = (message: string): unknown => ({
1430
1848
  * cannot be encoded, answers `PortFailed(PortProtocolError)` instead of throwing, so the
1431
1849
  * transport never has to interpret exceptions as protocol answers.
1432
1850
  */
1433
- export const handleEncodedPortRequest = Effect.fn("DoPortRouting.handleEncodedPortRequest")(
1434
- function* (
1435
- encoded: unknown,
1436
- ): Effect.fn.Return<
1437
- unknown,
1438
- never,
1439
- SubmissionLedger | ThreadStore | MessageDeliveryStore | WakeScheduler | DurableRuntimeFailpoint
1440
- > {
1441
- const response = yield* decodePortRequest(encoded).pipe(
1442
- Effect.flatMap(executePortRequest),
1443
- Effect.catch((error) =>
1444
- Effect.succeed<PortResponse>(
1445
- PortFailed.make({
1446
- failure: PortProtocolError.make({
1447
- message: boundPortDiagnostic(
1448
- `The port request could not be decoded: ${error.message}`,
1449
- ),
1450
- }),
1851
+ export const handleEncodedPortRequest = Effect.fnUntraced(function* (
1852
+ encoded: unknown,
1853
+ ): Effect.fn.Return<
1854
+ unknown,
1855
+ never,
1856
+ | SubmissionLedger
1857
+ | SettlementPublisher
1858
+ | ThreadStore
1859
+ | MessageDeliveryStore
1860
+ | WakeScheduler
1861
+ | DurableRuntimeFailpoint
1862
+ > {
1863
+ const response = yield* decodePortRequest(encoded).pipe(
1864
+ Effect.flatMap(executePortRequest),
1865
+ Effect.catch((error) =>
1866
+ Effect.succeed<PortResponse>(
1867
+ PortFailed.make({
1868
+ failure: PortProtocolError.make({
1869
+ message: boundPortDiagnostic(`The port request could not be decoded: ${error.message}`),
1451
1870
  }),
1452
- ),
1871
+ }),
1453
1872
  ),
1454
- );
1873
+ ),
1874
+ );
1455
1875
 
1456
- return yield* encodePortResponse(response).pipe(
1457
- Effect.catch((error) =>
1458
- Effect.succeed(
1459
- encodedProtocolFailure(`The port response could not be encoded: ${error.message}`),
1460
- ),
1876
+ return yield* encodePortResponse(response).pipe(
1877
+ Effect.catch((error) =>
1878
+ Effect.succeed(
1879
+ encodedProtocolFailure(`The port response could not be encoded: ${error.message}`),
1461
1880
  ),
1462
- );
1463
- },
1464
- );
1881
+ ),
1882
+ );
1883
+ });