@effect-agent/storage-cloudflare 0.1.0-beta.38 → 0.1.0-beta.40

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/src/routing.ts CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  AdmissionConflict,
4
4
  AppendConflict,
5
5
  ChildAttachmentSnapshot,
6
- ConversationMaterialization,
7
- ConversationNotMaterialized,
8
- ConversationStore,
9
- ConversationStoreError,
6
+ ThreadMaterialization,
7
+ ThreadNotMaterialized,
8
+ ThreadStore,
9
+ ThreadStoreError,
10
10
  FenceRejected,
11
11
  JoinedToHost,
12
12
  LedgerError,
@@ -16,7 +16,7 @@ import {
16
16
  SubmissionLookupById,
17
17
  type SubmissionLookupByKey,
18
18
  type SubmissionSnapshot,
19
- } from "@effect-agent/session";
19
+ } from "@effect-agent/thread";
20
20
  import { Context, Effect, Layer, Option, Predicate, Schema, Stream } from "effect";
21
21
 
22
22
  import {
@@ -57,11 +57,11 @@ import {
57
57
  type PortResult,
58
58
  } from "./port-protocol.ts";
59
59
 
60
- type ConversationId = ConversationMaterialization["conversationId"];
60
+ type ThreadId = ThreadMaterialization["threadId"];
61
61
  type SubmissionId = SubmissionSnapshot["submissionId"];
62
62
 
63
- const ConversationIdSchema = ConversationMaterialization.fields.conversationId;
64
- const decodeConversationId = Schema.decodeUnknownEffect(ConversationIdSchema);
63
+ const ThreadIdSchema = ThreadMaterialization.fields.threadId;
64
+ const decodeThreadId = Schema.decodeUnknownEffect(ThreadIdSchema);
65
65
 
66
66
  /**
67
67
  * The ledger row bound routable Submission identities must respect (mirrors the local
@@ -76,7 +76,7 @@ const UUID_HEAD_PATTERN =
76
76
 
77
77
  /**
78
78
  * A transport could not deliver a port request to (or an answer from) the owning
79
- * Conversation's Durable Object. `retryable` carries the platform's own stub signal when one
79
+ * Thread's Durable Object. `retryable` carries the platform's own stub signal when one
80
80
  * exists. This error never crosses the wire — it is the CALLER-side evidence that the
81
81
  * authority was unreachable, which is exactly the case `AdmissionIndeterminate` was
82
82
  * specified for (SUB-031).
@@ -126,42 +126,42 @@ export const portTransportFailure = (target: string, cause: unknown): PortTransp
126
126
 
127
127
  /**
128
128
  * Delivery of Schema-encoded port envelopes to the Durable Object that owns a FOREIGN
129
- * Conversation (plan §1.3, D-P6-3). The shipped implementation (platform-cloudflare, WP3)
129
+ * Thread (plan §1.3, D-P6-3). The shipped implementation (platform-cloudflare, WP3)
130
130
  * calls the owner's `portCall` over native Durable Object JS RPC via
131
- * `namespace.idFromName(conversationId)`; the protocol is transport-agnostic and any carrier
131
+ * `namespace.idFromName(threadId)`; the protocol is transport-agnostic and any carrier
132
132
  * that moves the encoded envelopes verbatim satisfies this service. Implementations MUST
133
133
  * surface every delivery problem as `PortTransportError` and must never fabricate an answer.
134
134
  */
135
- export class ConversationPortTransport extends Context.Service<
136
- ConversationPortTransport,
135
+ export class ThreadPortTransport extends Context.Service<
136
+ ThreadPortTransport,
137
137
  {
138
138
  readonly call: (
139
- conversationId: ConversationId,
139
+ threadId: ThreadId,
140
140
  request: PortRequestEnvelope,
141
141
  ) => Effect.Effect<unknown, PortTransportError>;
142
142
  }
143
- >()("@effect-agent/storage-cloudflare/ConversationPortTransport") {}
143
+ >()("@effect-agent/storage-cloudflare/ThreadPortTransport") {}
144
144
 
145
145
  /** Construction options shared by both routed port Layers. */
146
146
  export interface RoutedPortOptions {
147
147
  /**
148
- * The Conversation this Durable Object owns (the Object identity rule is
149
- * `namespace.idFromName(conversationId)`). Requests addressed here execute on the local
148
+ * The Thread this Durable Object owns (the Object identity rule is
149
+ * `namespace.idFromName(threadId)`). Requests addressed here execute on the local
150
150
  * facet; requests addressed anywhere else route through the transport or fail fast typed.
151
151
  */
152
- readonly localConversationId: ConversationId;
152
+ readonly localThreadId: ThreadId;
153
153
  }
154
154
 
155
155
  /** Where one port request must execute. */
156
156
  type RouteTarget =
157
157
  | { readonly _tag: "local" }
158
- | { readonly _tag: "foreign"; readonly conversationId: ConversationId };
158
+ | { readonly _tag: "foreign"; readonly threadId: ThreadId };
159
159
 
160
160
  const LOCAL: RouteTarget = { _tag: "local" };
161
161
 
162
162
  /**
163
- * Parse a DC-minted routable Submission identity — `{uuidv7}:{conversationId}`, split at the
164
- * FIRST `:` because the Conversation tail may itself contain colons (D-P6-5). This adapter
163
+ * Parse a DC-minted routable Submission identity — `{uuidv7}:{threadId}`, split at the
164
+ * FIRST `:` because the Thread tail may itself contain colons (D-P6-5). This adapter
165
165
  * minted the format at admission and is the ONLY component that parses it; identities that do
166
166
  * not carry the minted shape (no separator, non-UUID head, empty tail) fall back to the local
167
167
  * facet, which is the only authority this Object can consult without inventing an owner.
@@ -169,7 +169,7 @@ const LOCAL: RouteTarget = { _tag: "local" };
169
169
  * refused them at admission, so they cannot name any stored row anywhere.
170
170
  */
171
171
  const routableSubmissionTarget = (
172
- localConversationId: ConversationId,
172
+ localThreadId: ThreadId,
173
173
  ): ((operation: string, submissionId: string) => Effect.Effect<RouteTarget, LedgerError>) =>
174
174
  Effect.fn("DoPortRouting.routableSubmissionTarget")(function* (
175
175
  operation: string,
@@ -188,47 +188,43 @@ const routableSubmissionTarget = (
188
188
  if (separator === -1) return LOCAL;
189
189
  if (!UUID_HEAD_PATTERN.test(submissionId.slice(0, separator))) return LOCAL;
190
190
  const tail = submissionId.slice(separator + 1);
191
- if (tail === localConversationId) return LOCAL;
192
- return yield* decodeConversationId(tail).pipe(
193
- Effect.map((conversationId): RouteTarget => ({ _tag: "foreign", conversationId })),
191
+ if (tail === localThreadId) return LOCAL;
192
+ return yield* decodeThreadId(tail).pipe(
193
+ Effect.map((threadId): RouteTarget => ({ _tag: "foreign", threadId })),
194
194
  Effect.orElseSucceed(() => LOCAL),
195
195
  );
196
196
  });
197
197
 
198
198
  const NoAdditionalPortFailure = Schema.Never;
199
199
  const AbortPortFailure = Schema.Union([SettlementConflict, JoinedToHost]);
200
- const AppendPortFailure = Schema.Union([
201
- ConversationNotMaterialized,
202
- AppendConflict,
203
- FenceRejected,
204
- ]);
200
+ const AppendPortFailure = Schema.Union([ThreadNotMaterialized, AppendConflict, FenceRejected]);
205
201
 
206
202
  /**
207
203
  * The fail-fast refusal for any foreign operation OUTSIDE the closed route-capable subset
208
204
  * (plan §1.3): honesty over accidental distribution.
209
205
  */
210
- const crossConversationLedgerError = (operation: string, target: string): LedgerError =>
206
+ const crossThreadLedgerError = (operation: string, target: string): LedgerError =>
211
207
  LedgerError.make({
212
208
  operation,
213
209
  message:
214
- `${operation} addressed to foreign Conversation ${target} is not route-capable; the ` +
210
+ `${operation} addressed to foreign Thread ${target} is not route-capable; the ` +
215
211
  "closed cross-Object subset is admit, markReady, lookup, resolveAdmission, " +
216
212
  "requestAbort, and recordChildSettled. Every other ledger operation is lane-local by " +
217
- "construction and must execute inside the owning Conversation's Durable Object.",
213
+ "construction and must execute inside the owning Thread's Durable Object.",
218
214
  });
219
215
 
220
- const crossConversationStoreError = (operation: string, target: string): ConversationStoreError =>
221
- ConversationStoreError.make({
216
+ const crossThreadStoreError = (operation: string, target: string): ThreadStoreError =>
217
+ ThreadStoreError.make({
222
218
  operation,
223
219
  message:
224
- `${operation} addressed to foreign Conversation ${target} is not route-capable; the ` +
220
+ `${operation} addressed to foreign Thread ${target} is not route-capable; the ` +
225
221
  "closed cross-Object subset is materialize, append, read (paged), inspectTail, and " +
226
222
  "export. Observation and checkpoints are lane-local by construction and must execute " +
227
- "inside the owning Conversation's Durable Object.",
223
+ "inside the owning Thread's Durable Object.",
228
224
  });
229
225
 
230
- const makeTransportCall = (transport: ConversationPortTransport["Service"]) =>
231
- Effect.fn("DoPortRouting.transportCall")(function* (target: ConversationId, call: PortRequest) {
226
+ const makeTransportCall = (transport: ThreadPortTransport["Service"]) =>
227
+ Effect.fn("DoPortRouting.transportCall")(function* (target: ThreadId, call: PortRequest) {
232
228
  const encoded = yield* encodePortRequest(call).pipe(
233
229
  Effect.mapError((error) =>
234
230
  PortProtocolError.make({
@@ -252,9 +248,9 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
252
248
  options: RoutedPortOptions,
253
249
  ) {
254
250
  const local = yield* SubmissionLedger;
255
- const transport = yield* ConversationPortTransport;
251
+ const transport = yield* ThreadPortTransport;
256
252
  const transportCall: TransportCall = makeTransportCall(transport);
257
- const submissionTarget = routableSubmissionTarget(options.localConversationId);
253
+ const submissionTarget = routableSubmissionTarget(options.localThreadId);
258
254
 
259
255
  const routeFailure =
260
256
  (operation: string, target: string) =>
@@ -262,7 +258,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
262
258
  LedgerError.make({
263
259
  operation,
264
260
  message: boundPortDiagnostic(
265
- `Routed ${operation} to the Conversation Object owning ${target} failed: ${error.message}`,
261
+ `Routed ${operation} to the Thread Object owning ${target} failed: ${error.message}`,
266
262
  ),
267
263
  cause: error,
268
264
  });
@@ -275,7 +271,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
275
271
  */
276
272
  const foreignLedgerCall = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
277
273
  operation: string,
278
- target: ConversationId,
274
+ target: ThreadId,
279
275
  call: PortRequest,
280
276
  resultSchema: ResultSchema,
281
277
  failureSchema: FailureSchema,
@@ -294,7 +290,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
294
290
  LedgerError.make({
295
291
  operation,
296
292
  message: boundPortDiagnostic(
297
- `The Conversation Object owning ${target} answered ${operation} with the ` +
293
+ `The Thread Object owning ${target} answered ${operation} with the ` +
298
294
  `out-of-contract failure ${failure._tag}: ${failure.message}`,
299
295
  ),
300
296
  cause: failure,
@@ -307,7 +303,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
307
303
  LedgerError.make({
308
304
  operation,
309
305
  message:
310
- `The Conversation Object owning ${target} answered ${operation} with the ` +
306
+ `The Thread Object owning ${target} answered ${operation} with the ` +
311
307
  `mismatched result ${result._tag}.`,
312
308
  }),
313
309
  );
@@ -330,7 +326,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
330
326
  * reached and reported its own storage failure.
331
327
  */
332
328
  const resolveForeignAdmission = (
333
- target: ConversationId,
329
+ target: ThreadId,
334
330
  request: SubmissionLookupByKey,
335
331
  ): Effect.Effect<
336
332
  | AdmissionIndeterminate
@@ -344,7 +340,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
344
340
  return Effect.succeed(
345
341
  AdmissionIndeterminate.make({
346
342
  reason: boundPortDiagnostic(
347
- `The Conversation Object owning ${target} answered resolveAdmission with the ` +
343
+ `The Thread Object owning ${target} answered resolveAdmission with the ` +
348
344
  `out-of-contract failure ${response.failure._tag}: ${response.failure.message}`,
349
345
  ),
350
346
  }),
@@ -354,7 +350,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
354
350
  return Effect.succeed(
355
351
  AdmissionIndeterminate.make({
356
352
  reason: boundPortDiagnostic(
357
- `The Conversation Object owning ${target} answered resolveAdmission with the ` +
353
+ `The Thread Object owning ${target} answered resolveAdmission with the ` +
358
354
  `mismatched result ${response.result._tag}.`,
359
355
  ),
360
356
  }),
@@ -367,7 +363,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
367
363
  Effect.succeed(
368
364
  AdmissionIndeterminate.make({
369
365
  reason: boundPortDiagnostic(
370
- `The Conversation Object owning ${target} is unreachable: ${error.message}`,
366
+ `The Thread Object owning ${target} is unreachable: ${error.message}`,
371
367
  ),
372
368
  }),
373
369
  ),
@@ -375,7 +371,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
375
371
  Effect.succeed(
376
372
  AdmissionIndeterminate.make({
377
373
  reason: boundPortDiagnostic(
378
- `The answer of the Conversation Object owning ${target} could not be ` +
374
+ `The answer of the Thread Object owning ${target} could not be ` +
379
375
  `understood: ${error.message}`,
380
376
  ),
381
377
  }),
@@ -386,7 +382,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
386
382
 
387
383
  const foreignLookupById = (
388
384
  operation: string,
389
- target: ConversationId,
385
+ target: ThreadId,
390
386
  submissionId: SubmissionId,
391
387
  ): Effect.Effect<Option.Option<SubmissionSnapshot>, LedgerError> =>
392
388
  foreignLedgerCall(
@@ -423,7 +419,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
423
419
  // A local or opaque child identity was already answered authoritatively by the local
424
420
  // facet; absence there means the child admission never committed.
425
421
  if (target._tag !== "foreign") continue;
426
- const child = yield* foreignLookupById(operation, target.conversationId, childSubmissionId);
422
+ const child = yield* foreignLookupById(operation, target.threadId, childSubmissionId);
427
423
  if (Option.isNone(child)) continue;
428
424
  attachments.set(
429
425
  childSubmissionId,
@@ -453,11 +449,11 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
453
449
  capabilities: local.capabilities,
454
450
 
455
451
  admit: (request) =>
456
- request.conversationId === options.localConversationId
452
+ request.threadId === options.localThreadId
457
453
  ? local.admit(request)
458
454
  : foreignLedgerCall(
459
455
  "ledger admit",
460
- request.conversationId,
456
+ request.threadId,
461
457
  LedgerAdmitCall.make({ request }),
462
458
  LedgerAdmitResult,
463
459
  AdmissionConflict,
@@ -470,7 +466,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
470
466
  ? local.markReady(request)
471
467
  : foreignLedgerCall(
472
468
  "ledger mark ready",
473
- target.conversationId,
469
+ target.threadId,
474
470
  LedgerMarkReadyCall.make({ request }),
475
471
  LedgerMarkReadyResult,
476
472
  NoAdditionalPortFailure,
@@ -484,14 +480,14 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
484
480
  Effect.flatMap((target) =>
485
481
  target._tag === "local"
486
482
  ? local.lookup(request)
487
- : foreignLookupById("ledger lookup", target.conversationId, request.submissionId),
483
+ : foreignLookupById("ledger lookup", target.threadId, request.submissionId),
488
484
  ),
489
485
  )
490
- : request.conversationId === options.localConversationId
486
+ : request.threadId === options.localThreadId
491
487
  ? local.lookup(request)
492
488
  : foreignLedgerCall(
493
489
  "ledger lookup",
494
- request.conversationId,
490
+ request.threadId,
495
491
  LedgerLookupCall.make({ request }),
496
492
  LedgerLookupResult,
497
493
  NoAdditionalPortFailure,
@@ -502,9 +498,9 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
502
498
  ),
503
499
 
504
500
  resolveAdmission: (request) =>
505
- request.conversationId === options.localConversationId
501
+ request.threadId === options.localThreadId
506
502
  ? local.resolveAdmission(request)
507
- : resolveForeignAdmission(request.conversationId, request),
503
+ : resolveForeignAdmission(request.threadId, request),
508
504
 
509
505
  requestAbort: (request) =>
510
506
  submissionTarget("ledger request abort", request.submissionId).pipe(
@@ -513,7 +509,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
513
509
  ? local.requestAbort(request)
514
510
  : foreignLedgerCall(
515
511
  "ledger request abort",
516
- target.conversationId,
512
+ target.threadId,
517
513
  LedgerRequestAbortCall.make({ request }),
518
514
  LedgerRequestAbortResult,
519
515
  AbortPortFailure,
@@ -528,7 +524,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
528
524
  ? local.recordChildSettled(request)
529
525
  : foreignLedgerCall(
530
526
  "ledger record child settled",
531
- target.conversationId,
527
+ target.threadId,
532
528
  LedgerRecordChildSettledCall.make({ request }),
533
529
  LedgerRecordChildSettledResult,
534
530
  NoAdditionalPortFailure,
@@ -539,23 +535,21 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
539
535
  // Every operation below is lane-local by construction (plan §1.3): a foreign address is
540
536
  // an out-of-contract call and fails fast typed instead of being quietly distributed.
541
537
  claim: (request) =>
542
- request.conversationId === options.localConversationId
538
+ request.threadId === options.localThreadId
543
539
  ? local.claim(request)
544
- : Effect.fail(crossConversationLedgerError("ledger claim", request.conversationId)),
540
+ : Effect.fail(crossThreadLedgerError("ledger claim", request.threadId)),
545
541
 
546
542
  claimJoining: (request) =>
547
- request.conversationId === options.localConversationId
543
+ request.threadId === options.localThreadId
548
544
  ? local.claimJoining(request)
549
- : Effect.fail(crossConversationLedgerError("ledger claim joining", request.conversationId)),
545
+ : Effect.fail(crossThreadLedgerError("ledger claim joining", request.threadId)),
550
546
 
551
547
  renewOwnership: (request) =>
552
548
  submissionTarget("ledger renew ownership", request.submissionId).pipe(
553
549
  Effect.flatMap((target) =>
554
550
  target._tag === "local"
555
551
  ? local.renewOwnership(request)
556
- : Effect.fail(
557
- crossConversationLedgerError("ledger renew ownership", target.conversationId),
558
- ),
552
+ : Effect.fail(crossThreadLedgerError("ledger renew ownership", target.threadId)),
559
553
  ),
560
554
  ),
561
555
 
@@ -564,9 +558,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
564
558
  Effect.flatMap((target) =>
565
559
  target._tag === "local"
566
560
  ? local.releaseOwnership(request)
567
- : Effect.fail(
568
- crossConversationLedgerError("ledger release ownership", target.conversationId),
569
- ),
561
+ : Effect.fail(crossThreadLedgerError("ledger release ownership", target.threadId)),
570
562
  ),
571
563
  ),
572
564
 
@@ -575,9 +567,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
575
567
  Effect.flatMap((target) =>
576
568
  target._tag === "local"
577
569
  ? local.markInputApplied(request)
578
- : Effect.fail(
579
- crossConversationLedgerError("ledger mark input applied", target.conversationId),
580
- ),
570
+ : Effect.fail(crossThreadLedgerError("ledger mark input applied", target.threadId)),
581
571
  ),
582
572
  ),
583
573
 
@@ -586,9 +576,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
586
576
  Effect.flatMap((target) =>
587
577
  target._tag === "local"
588
578
  ? local.reserveSettlement(request)
589
- : Effect.fail(
590
- crossConversationLedgerError("ledger reserve settlement", target.conversationId),
591
- ),
579
+ : Effect.fail(crossThreadLedgerError("ledger reserve settlement", target.threadId)),
592
580
  ),
593
581
  ),
594
582
 
@@ -597,9 +585,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
597
585
  Effect.flatMap((target) =>
598
586
  target._tag === "local"
599
587
  ? local.finalizeSettlement(request)
600
- : Effect.fail(
601
- crossConversationLedgerError("ledger finalize settlement", target.conversationId),
602
- ),
588
+ : Effect.fail(crossThreadLedgerError("ledger finalize settlement", target.threadId)),
603
589
  ),
604
590
  ),
605
591
 
@@ -608,9 +594,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
608
594
  Effect.flatMap((target) =>
609
595
  target._tag === "local"
610
596
  ? local.markJoined(request)
611
- : Effect.fail(
612
- crossConversationLedgerError("ledger mark joined", target.conversationId),
613
- ),
597
+ : Effect.fail(crossThreadLedgerError("ledger mark joined", target.threadId)),
614
598
  ),
615
599
  ),
616
600
 
@@ -619,9 +603,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
619
603
  Effect.flatMap((target) =>
620
604
  target._tag === "local"
621
605
  ? local.revertJoining(request)
622
- : Effect.fail(
623
- crossConversationLedgerError("ledger revert joining", target.conversationId),
624
- ),
606
+ : Effect.fail(crossThreadLedgerError("ledger revert joining", target.threadId)),
625
607
  ),
626
608
  ),
627
609
 
@@ -630,7 +612,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
630
612
  Effect.flatMap((target) =>
631
613
  target._tag === "local"
632
614
  ? local.suspend(request)
633
- : Effect.fail(crossConversationLedgerError("ledger suspend", target.conversationId)),
615
+ : Effect.fail(crossThreadLedgerError("ledger suspend", target.threadId)),
634
616
  ),
635
617
  ),
636
618
 
@@ -640,10 +622,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
640
622
  target._tag === "local"
641
623
  ? local.recordApprovalDecision(command)
642
624
  : Effect.fail(
643
- crossConversationLedgerError(
644
- "ledger record approval decision",
645
- target.conversationId,
646
- ),
625
+ crossThreadLedgerError("ledger record approval decision", target.threadId),
647
626
  ),
648
627
  ),
649
628
  ),
@@ -653,9 +632,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
653
632
  Effect.flatMap((target) =>
654
633
  target._tag === "local"
655
634
  ? local.markUnknown(request)
656
- : Effect.fail(
657
- crossConversationLedgerError("ledger mark unknown", target.conversationId),
658
- ),
635
+ : Effect.fail(crossThreadLedgerError("ledger mark unknown", target.threadId)),
659
636
  ),
660
637
  ),
661
638
 
@@ -665,10 +642,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
665
642
  target._tag === "local"
666
643
  ? local.recordUnknownResolution(command)
667
644
  : Effect.fail(
668
- crossConversationLedgerError(
669
- "ledger record unknown resolution",
670
- target.conversationId,
671
- ),
645
+ crossThreadLedgerError("ledger record unknown resolution", target.threadId),
672
646
  ),
673
647
  ),
674
648
  ),
@@ -678,20 +652,18 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
678
652
  Effect.flatMap((target) =>
679
653
  target._tag === "local"
680
654
  ? local.reserveChildBudget(request)
681
- : Effect.fail(
682
- crossConversationLedgerError("ledger reserve child budget", target.conversationId),
683
- ),
655
+ : Effect.fail(crossThreadLedgerError("ledger reserve child budget", target.threadId)),
684
656
  ),
685
657
  ),
686
658
 
687
- // Reservation identities carry no Conversation address; the reservation row lives in the
659
+ // Reservation identities carry no Thread address; the reservation row lives in the
688
660
  // parent's own Object and these transitions are parent-lane-local by construction, so
689
661
  // they always execute on the local facet (which fails typed for an unknown row).
690
662
  attachChildToReservation: local.attachChildToReservation,
691
663
  beginChildBudgetRelease: local.beginChildBudgetRelease,
692
664
  releaseChildBudget: local.releaseChildBudget,
693
665
 
694
- // The local scan IS the whole worklist: one Conversation per Object (durability §5).
666
+ // The local scan IS the whole worklist: one Thread per Object (durability §5).
695
667
  scanNonterminal: local.scanNonterminal,
696
668
 
697
669
  loadRecoverySnapshot: (request) =>
@@ -699,12 +671,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
699
671
  Effect.flatMap((target) =>
700
672
  target._tag === "local"
701
673
  ? local.loadRecoverySnapshot(request).pipe(Effect.flatMap(enrichChildAttachments))
702
- : Effect.fail(
703
- crossConversationLedgerError(
704
- "ledger load recovery snapshot",
705
- target.conversationId,
706
- ),
707
- ),
674
+ : Effect.fail(crossThreadLedgerError("ledger load recovery snapshot", target.threadId)),
708
675
  ),
709
676
  ),
710
677
  });
@@ -715,29 +682,30 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
715
682
  const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices")(function* (
716
683
  options: RoutedPortOptions,
717
684
  ) {
718
- const local = yield* ConversationStore;
719
- const transport = yield* ConversationPortTransport;
685
+ const local = yield* ThreadStore;
686
+ const checkpoints = local.checkpoints;
687
+ const transport = yield* ThreadPortTransport;
720
688
  const transportCall: TransportCall = makeTransportCall(transport);
721
689
 
722
690
  const routeFailure =
723
691
  (operation: string, target: string) =>
724
- (error: PortTransportError | PortProtocolError): ConversationStoreError =>
725
- ConversationStoreError.make({
692
+ (error: PortTransportError | PortProtocolError): ThreadStoreError =>
693
+ ThreadStoreError.make({
726
694
  operation,
727
695
  message: boundPortDiagnostic(
728
- `Routed ${operation} to the Conversation Object owning ${target} failed: ${error.message}`,
696
+ `Routed ${operation} to the Thread Object owning ${target} failed: ${error.message}`,
729
697
  ),
730
698
  cause: error,
731
699
  });
732
700
 
733
- /** The store twin of `foreignLedgerCall` with `ConversationStoreError` as the base error. */
701
+ /** The store twin of `foreignLedgerCall` with `ThreadStoreError` as the base error. */
734
702
  const foreignStoreCall = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
735
703
  operation: string,
736
- target: ConversationId,
704
+ target: ThreadId,
737
705
  call: PortRequest,
738
706
  resultSchema: ResultSchema,
739
707
  failureSchema: FailureSchema,
740
- ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ConversationStoreError> => {
708
+ ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ThreadStoreError> => {
741
709
  const isExpectedResult = Schema.is(resultSchema);
742
710
  const isExpectedFailure = Schema.is(failureSchema);
743
711
  return transportCall(target, call).pipe(
@@ -745,16 +713,16 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
745
713
  Effect.flatMap(
746
714
  (
747
715
  response,
748
- ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ConversationStoreError> => {
716
+ ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ThreadStoreError> => {
749
717
  if (response._tag === "PortFailed") {
750
718
  const failure = response.failure;
751
719
  if (isExpectedFailure(failure)) return Effect.fail(failure);
752
- if (failure._tag === "ConversationStoreError") return Effect.fail(failure);
720
+ if (failure._tag === "ThreadStoreError") return Effect.fail(failure);
753
721
  return Effect.fail(
754
- ConversationStoreError.make({
722
+ ThreadStoreError.make({
755
723
  operation,
756
724
  message: boundPortDiagnostic(
757
- `The Conversation Object owning ${target} answered ${operation} with the ` +
725
+ `The Thread Object owning ${target} answered ${operation} with the ` +
758
726
  `out-of-contract failure ${failure._tag}: ${failure.message}`,
759
727
  ),
760
728
  cause: failure,
@@ -764,10 +732,10 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
764
732
  const result = response.result;
765
733
  if (!isExpectedResult(result)) {
766
734
  return Effect.fail(
767
- ConversationStoreError.make({
735
+ ThreadStoreError.make({
768
736
  operation,
769
737
  message:
770
- `The Conversation Object owning ${target} answered ${operation} with the ` +
738
+ `The Thread Object owning ${target} answered ${operation} with the ` +
771
739
  `mismatched result ${result._tag}.`,
772
740
  }),
773
741
  );
@@ -781,117 +749,113 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
781
749
  );
782
750
  };
783
751
 
784
- const routed = ConversationStore.of({
752
+ const routed = ThreadStore.of({
785
753
  materialize: (request) =>
786
- request.conversationId === options.localConversationId
754
+ request.threadId === options.localThreadId
787
755
  ? local.materialize(request)
788
756
  : foreignStoreCall(
789
- "conversation materialize",
790
- request.conversationId,
757
+ "thread materialize",
758
+ request.threadId,
791
759
  StoreMaterializeCall.make({ request }),
792
760
  StoreMaterializeResult,
793
761
  FenceRejected,
794
762
  ).pipe(Effect.asVoid),
795
763
 
796
764
  append: (request) =>
797
- request.conversationId === options.localConversationId
765
+ request.threadId === options.localThreadId
798
766
  ? local.append(request)
799
767
  : foreignStoreCall(
800
- "conversation append",
801
- request.conversationId,
768
+ "thread append",
769
+ request.threadId,
802
770
  StoreAppendCall.make({ request }),
803
771
  StoreAppendResult,
804
772
  AppendPortFailure,
805
773
  ).pipe(Effect.map((reply) => reply.result)),
806
774
 
807
775
  read: (request) =>
808
- request.conversationId === options.localConversationId
776
+ request.threadId === options.localThreadId
809
777
  ? local.read(request)
810
778
  : Stream.unwrap(
811
779
  foreignStoreCall(
812
- "conversation read",
813
- request.conversationId,
780
+ "thread read",
781
+ request.threadId,
814
782
  StoreReadPageCall.make({ request }),
815
783
  StoreReadPageResult,
816
- ConversationNotMaterialized,
784
+ ThreadNotMaterialized,
817
785
  ).pipe(Effect.map((reply) => Stream.fromIterable(reply.records))),
818
786
  ),
819
787
 
820
788
  inspectTail: (request) =>
821
- request.conversationId === options.localConversationId
789
+ request.threadId === options.localThreadId
822
790
  ? local.inspectTail(request)
823
791
  : foreignStoreCall(
824
- "conversation inspect tail",
825
- request.conversationId,
792
+ "thread inspect tail",
793
+ request.threadId,
826
794
  StoreInspectTailCall.make({ request }),
827
795
  StoreInspectTailResult,
828
- ConversationNotMaterialized,
796
+ ThreadNotMaterialized,
829
797
  ).pipe(Effect.map((reply) => reply.tail)),
830
798
 
831
799
  export: (request) =>
832
- request.conversationId === options.localConversationId
800
+ request.threadId === options.localThreadId
833
801
  ? local.export(request)
834
802
  : foreignStoreCall(
835
- "conversation export",
836
- request.conversationId,
803
+ "thread export",
804
+ request.threadId,
837
805
  StoreExportCall.make({ request }),
838
806
  StoreExportResult,
839
- ConversationNotMaterialized,
807
+ ThreadNotMaterialized,
840
808
  ).pipe(Effect.map((reply) => reply.export)),
841
809
 
842
810
  // Observation and checkpoints are lane-local by construction (plan §1.3): the closed
843
811
  // route-capable store subset is materialize/append/read/inspectTail/export, and a
844
812
  // foreign address on anything else fails fast typed.
845
813
  observe: (request) =>
846
- request.conversationId === options.localConversationId
814
+ request.threadId === options.localThreadId
847
815
  ? local.observe(request)
848
- : Stream.unwrap(
849
- Effect.fail(
850
- crossConversationStoreError("conversation observe", request.conversationId),
851
- ),
852
- ),
853
-
854
- saveCheckpoint: (request) =>
855
- request.checkpoint.conversationId === options.localConversationId
856
- ? local.saveCheckpoint(request)
857
- : Effect.fail(
858
- crossConversationStoreError(
859
- "conversation save checkpoint",
860
- request.checkpoint.conversationId,
861
- ),
862
- ),
863
-
864
- loadCheckpoint: (request) =>
865
- request.conversationId === options.localConversationId
866
- ? local.loadCheckpoint(request)
867
- : Effect.fail(
868
- crossConversationStoreError("conversation load checkpoint", request.conversationId),
869
- ),
816
+ : Stream.unwrap(Effect.fail(crossThreadStoreError("thread observe", request.threadId))),
817
+
818
+ ...(checkpoints === undefined
819
+ ? {}
820
+ : {
821
+ checkpoints: {
822
+ save: (request) =>
823
+ request.checkpoint.threadId === options.localThreadId
824
+ ? checkpoints.save(request)
825
+ : Effect.fail(
826
+ crossThreadStoreError("thread save checkpoint", request.checkpoint.threadId),
827
+ ),
828
+ load: (request) =>
829
+ request.threadId === options.localThreadId
830
+ ? checkpoints.load(request)
831
+ : Effect.fail(crossThreadStoreError("thread load checkpoint", request.threadId)),
832
+ },
833
+ }),
870
834
  });
871
835
 
872
- return Context.make(ConversationStore, routed);
836
+ return Context.make(ThreadStore, routed);
873
837
  });
874
838
 
875
839
  /**
876
840
  * Routing decorator over the LOCAL `SubmissionLedger` facet (plan §1.3): a request addressing
877
- * this Object's Conversation executes locally; a route-capable request addressing another
878
- * Conversation is Schema-encoded onto the `ConversationPortTransport` and executed by the
841
+ * this Object's Thread executes locally; a route-capable request addressing another
842
+ * Thread is Schema-encoded onto the `ThreadPortTransport` and executed by the
879
843
  * owning Object's local facet; any other foreign request fails fast typed. Provide the WP1
880
844
  * local facet (`submissionLedgerLayer`/`ledgerLayer`) and a transport to close it.
881
845
  */
882
846
  export const routedSubmissionLedgerLayer = (
883
847
  options: RoutedPortOptions,
884
- ): Layer.Layer<SubmissionLedger, never, SubmissionLedger | ConversationPortTransport> =>
848
+ ): Layer.Layer<SubmissionLedger, never, SubmissionLedger | ThreadPortTransport> =>
885
849
  Layer.effectContext(makeRoutedLedgerServices(options));
886
850
 
887
851
  /**
888
- * Routing decorator over the LOCAL `ConversationStore` facet (plan §1.3): this-conversation
852
+ * Routing decorator over the LOCAL `ThreadStore` facet (plan §1.3): this-thread
889
853
  * requests execute locally; foreign materialize/append/read/inspectTail/export travel the
890
854
  * transport; foreign observation and checkpoints fail fast typed.
891
855
  */
892
- export const routedConversationStoreLayer = (
856
+ export const routedThreadStoreLayer = (
893
857
  options: RoutedPortOptions,
894
- ): Layer.Layer<ConversationStore, never, ConversationStore | ConversationPortTransport> =>
858
+ ): Layer.Layer<ThreadStore, never, ThreadStore | ThreadPortTransport> =>
895
859
  Layer.effectContext(makeRoutedStoreServices(options));
896
860
 
897
861
  // ---------------------------------------------------------------------------
@@ -911,13 +875,13 @@ const capture = <Failure extends PortFailure>(
911
875
  * Execute one decoded port request against THIS Object's LOCAL facets — the owner-side half
912
876
  * of the routed ports (plan §1.3). Callers must provide the WP1 local facets, never the
913
877
  * routed decorators: the routing layer already established that this Object owns the
914
- * addressed Conversation, and re-routing here could bounce a request between Objects.
878
+ * addressed Thread, and re-routing here could bounce a request between Objects.
915
879
  * Failures never escape — every typed port failure becomes a `PortFailed` envelope that
916
880
  * re-decodes on the caller side.
917
881
  */
918
882
  export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(function* (
919
883
  request: PortRequest,
920
- ): Effect.fn.Return<PortResponse, never, SubmissionLedger | ConversationStore> {
884
+ ): Effect.fn.Return<PortResponse, never, SubmissionLedger | ThreadStore> {
921
885
  switch (request._tag) {
922
886
  case "LedgerAdmit": {
923
887
  const ledger = yield* SubmissionLedger;
@@ -972,13 +936,13 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
972
936
  );
973
937
  }
974
938
  case "StoreMaterialize": {
975
- const store = yield* ConversationStore;
939
+ const store = yield* ThreadStore;
976
940
  return yield* capture(
977
941
  store.materialize(request.request).pipe(Effect.map(() => StoreMaterializeResult.make({}))),
978
942
  );
979
943
  }
980
944
  case "StoreAppend": {
981
- const store = yield* ConversationStore;
945
+ const store = yield* ThreadStore;
982
946
  return yield* capture(
983
947
  store
984
948
  .append(request.request)
@@ -986,7 +950,7 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
986
950
  );
987
951
  }
988
952
  case "StoreReadPage": {
989
- const store = yield* ConversationStore;
953
+ const store = yield* ThreadStore;
990
954
  return yield* capture(
991
955
  store.read(request.request).pipe(
992
956
  Stream.runCollect,
@@ -995,7 +959,7 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
995
959
  );
996
960
  }
997
961
  case "StoreInspectTail": {
998
- const store = yield* ConversationStore;
962
+ const store = yield* ThreadStore;
999
963
  return yield* capture(
1000
964
  store
1001
965
  .inspectTail(request.request)
@@ -1003,15 +967,11 @@ export const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(
1003
967
  );
1004
968
  }
1005
969
  case "StoreExport": {
1006
- const store = yield* ConversationStore;
970
+ const store = yield* ThreadStore;
1007
971
  return yield* capture(
1008
972
  store
1009
973
  .export(request.request)
1010
- .pipe(
1011
- Effect.map((conversationExport) =>
1012
- StoreExportResult.make({ export: conversationExport }),
1013
- ),
1014
- ),
974
+ .pipe(Effect.map((threadExport) => StoreExportResult.make({ export: threadExport }))),
1015
975
  );
1016
976
  }
1017
977
  }
@@ -1035,9 +995,7 @@ const encodedProtocolFailure = (message: string): unknown => ({
1035
995
  * transport never has to interpret exceptions as protocol answers.
1036
996
  */
1037
997
  export const handleEncodedPortRequest = Effect.fn("DoPortRouting.handleEncodedPortRequest")(
1038
- function* (
1039
- encoded: unknown,
1040
- ): Effect.fn.Return<unknown, never, SubmissionLedger | ConversationStore> {
998
+ function* (encoded: unknown): Effect.fn.Return<unknown, never, SubmissionLedger | ThreadStore> {
1041
999
  const response = yield* decodePortRequest(encoded).pipe(
1042
1000
  Effect.flatMap(executePortRequest),
1043
1001
  Effect.catch((error) =>