@effect-agent/storage-cloudflare 0.1.0-beta.37 → 0.1.0-beta.39

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,4 +1,5 @@
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, ScheduleCapacityError, ScheduleChange, ScheduleConflict, ScheduleDueCursor, ScheduleFailpoint, ScheduleId, ScheduleInstant, ScheduleKey, ScheduleNotFound, ScheduleOwner, SchedulePageRequest, ScheduleRecord, ScheduleStorageError, ScheduleStore, Settlement, SettlementConflict, SettlementFinalization, SettlementOutcome, SettlementReservation, SettlementReservationSnapshot, SubmissionLedger, SubmissionLookup, SubmissionLookupById, SubmissionLookupByKey, SubmissionSnapshot, SubmissionState, SuspendRequest, SuspensionReason, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, applyScheduleChange, defaultSchedulingLimits, digestCanonicalBatch, scheduleDeadline, scheduleUsesCapacity, settlementFailureFromRecord, submissionAbortRecordId } from "@effect-agent/session";
1
+ import { t as DoStorageFailpoint } from "./do-storage-failpoint-C-Qtchs7.mjs";
2
+ import { AbortCommand, AbortIntent, AcceptedEvent, 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, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeliveryChange, 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, ScheduleCapacityError, ScheduleChange, ScheduleConflict, ScheduleDueCursor, ScheduleFailpoint, ScheduleId, ScheduleInstant, ScheduleKey, ScheduleNotFound, ScheduleOwner, SchedulePageRequest, ScheduleRecord, ScheduleStorageError, ScheduleStore, Settlement, SettlementConflict, SettlementFinalization, SettlementOutcome, SettlementReservation, SettlementReservationSnapshot, SourcePartition, SubmissionLedger, SubmissionLookup, SubmissionLookupById, SubmissionLookupByKey, SubmissionSnapshot, SubmissionState, SubscriptionDelivery, SubscriptionDeliveryKey, SubscriptionError, SubscriptionFailpoint, SubscriptionKey, SubscriptionLimits, SubscriptionName, SubscriptionRecord, SubscriptionScanCursors, SubscriptionStore, SuspendRequest, SuspensionReason, SuspensionSnapshot, ThreadCheckpoint, ThreadExport, ThreadExportRequest, ThreadMaterialization, ThreadNotMaterialized, ThreadObservation, ThreadRead, ThreadStore, ThreadStoreError, ThreadTail, ThreadTailRequest, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, applyScheduleChange, applySubscriptionDeliveryChange, defaultSchedulingLimits, digestCanonicalBatch, sameAcceptedEventIdentity, sameSourcePartition, scheduleDeadline, scheduleUsesCapacity, settlementFailureFromRecord, submissionAbortRecordId, subscriptionCanSelect, subscriptionDeliveryCanSelect, subscriptionDeliveryKeyString } from "@effect-agent/thread";
2
3
  import { Clock, Context, Crypto, DateTime, Duration, Effect, Layer, Option, Predicate, Ref, Result, Schema, Stream } from "effect";
3
4
  import { SqliteClient, SqliteMigrator } from "@effect/sql-sqlite-do";
4
5
  import * as SqlClient from "effect/unstable/sql/SqlClient";
@@ -64,7 +65,7 @@ var DoAppendConflict = class extends Schema.TaggedError()("DoAppendConflict", {
64
65
  actualTailDigest: Schema.optionalKey(Schema.String)
65
66
  }) {};
66
67
  /**
67
- * A producer epoch does not match the Conversation's current writer registration. Appends
68
+ * A producer epoch does not match the Thread's current writer registration. Appends
68
69
  * require the exact registered epoch, so both older and newer unregistered epochs are fenced;
69
70
  * a newer epoch takes over by materializing first.
70
71
  */
@@ -91,7 +92,7 @@ const DoStorageFailpointLocation = Schema.Literals([
91
92
  "append:after-record-insert",
92
93
  "append:after-tail-update",
93
94
  "append:after",
94
- "export:after-conversation-read",
95
+ "export:after-thread-read",
95
96
  "save-checkpoint:before",
96
97
  "save-checkpoint:after",
97
98
  "ledger:admit:before",
@@ -151,9 +152,9 @@ var DoStorageFailpointError = class extends Schema.TaggedError()("DoStorageFailp
151
152
  * v1→v4 history to replay (deployment spec §9: no rolling data-version promise during
152
153
  * private development).
153
154
  */
154
- const CurrentDoStorageVersion = 1;
155
+ const CurrentDoStorageVersion = 2;
155
156
  /**
156
- * The Conversation Durable Object schema. Table names and columns mirror the Node/SQLite v4
157
+ * The Thread Durable Object schema. Table names and columns mirror the Node/SQLite v4
157
158
  * schema byte-for-byte (`packages/storage-sqlite/src/migrations.ts`, migrations 1–4 collapsed
158
159
  * into their final shape) so the shared conformance suites and crash-matrix rows address
159
160
  * identical durable state. Two DC-specific additions:
@@ -162,15 +163,15 @@ const CurrentDoStorageVersion = 1;
162
163
  * a meta table is portable regardless of which PRAGMAs Durable Object SQL storage allows.
163
164
  * 2. `effect_agent_child_settlements` is the durable cross-store notification marker the
164
165
  * SubmissionLedger port contract mandates for cross-store adapters (`suspend`'s covering
165
- * check and `recordChildSettled`'s wake both consult it): parent and child Conversations
166
+ * check and `recordChildSettled`'s wake both consult it): parent and child Threads
166
167
  * live in different Durable Objects, so a child settlement reported before the parent's
167
168
  * suspend commits must be observable from the PARENT's own storage.
168
169
  */
169
- const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_conversation_object": Effect.gen(function* () {
170
+ const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_thread_object": Effect.gen(function* () {
170
171
  const sql = yield* SqlClient.SqlClient;
171
172
  yield* sql`
172
- CREATE TABLE effect_agent_conversations (
173
- conversation_id TEXT PRIMARY KEY NOT NULL,
173
+ CREATE TABLE effect_agent_threads (
174
+ thread_id TEXT PRIMARY KEY NOT NULL,
174
175
  created_at TEXT NOT NULL,
175
176
  tail_sequence INTEGER NOT NULL,
176
177
  tail_digest TEXT NOT NULL,
@@ -179,53 +180,53 @@ const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_conversat
179
180
  `.withoutTransform;
180
181
  yield* sql`
181
182
  CREATE TABLE effect_agent_canonical_batches (
182
- conversation_id TEXT NOT NULL,
183
+ thread_id TEXT NOT NULL,
183
184
  batch_id TEXT NOT NULL,
184
185
  first_sequence INTEGER NOT NULL,
185
186
  last_sequence INTEGER NOT NULL,
186
187
  batch_digest TEXT NOT NULL,
187
188
  tail_digest TEXT NOT NULL,
188
189
  batch_json TEXT NOT NULL,
189
- PRIMARY KEY (conversation_id, batch_id),
190
- FOREIGN KEY (conversation_id)
191
- REFERENCES effect_agent_conversations(conversation_id)
190
+ PRIMARY KEY (thread_id, batch_id),
191
+ FOREIGN KEY (thread_id)
192
+ REFERENCES effect_agent_threads(thread_id)
192
193
  ON DELETE RESTRICT
193
194
  )
194
195
  `.withoutTransform;
195
196
  yield* sql`
196
197
  CREATE TABLE effect_agent_canonical_records (
197
- conversation_id TEXT NOT NULL,
198
+ thread_id TEXT NOT NULL,
198
199
  sequence INTEGER NOT NULL,
199
200
  record_id TEXT NOT NULL,
200
201
  batch_id TEXT NOT NULL,
201
202
  record_json TEXT NOT NULL,
202
- PRIMARY KEY (conversation_id, sequence),
203
- UNIQUE (conversation_id, record_id),
204
- FOREIGN KEY (conversation_id, batch_id)
205
- REFERENCES effect_agent_canonical_batches(conversation_id, batch_id)
203
+ PRIMARY KEY (thread_id, sequence),
204
+ UNIQUE (thread_id, record_id),
205
+ FOREIGN KEY (thread_id, batch_id)
206
+ REFERENCES effect_agent_canonical_batches(thread_id, batch_id)
206
207
  ON DELETE RESTRICT
207
208
  )
208
209
  `.withoutTransform;
209
210
  yield* sql`
210
211
  CREATE INDEX effect_agent_canonical_records_batch
211
- ON effect_agent_canonical_records (conversation_id, batch_id, sequence)
212
+ ON effect_agent_canonical_records (thread_id, batch_id, sequence)
212
213
  `.withoutTransform;
213
214
  yield* sql`
214
215
  CREATE TABLE effect_agent_checkpoints (
215
- conversation_id TEXT NOT NULL,
216
+ thread_id TEXT NOT NULL,
216
217
  through_sequence INTEGER NOT NULL,
217
218
  tail_digest TEXT NOT NULL,
218
219
  checkpoint_json TEXT NOT NULL,
219
- PRIMARY KEY (conversation_id, through_sequence),
220
- FOREIGN KEY (conversation_id)
221
- REFERENCES effect_agent_conversations(conversation_id)
220
+ PRIMARY KEY (thread_id, through_sequence),
221
+ FOREIGN KEY (thread_id)
222
+ REFERENCES effect_agent_threads(thread_id)
222
223
  ON DELETE RESTRICT
223
224
  )
224
225
  `.withoutTransform;
225
226
  yield* sql`
226
227
  CREATE TABLE effect_agent_submissions (
227
228
  submission_id TEXT PRIMARY KEY NOT NULL,
228
- conversation_id TEXT NOT NULL,
229
+ thread_id TEXT NOT NULL,
229
230
  queue_sequence INTEGER NOT NULL,
230
231
  principal TEXT NOT NULL,
231
232
  idempotency_key TEXT NOT NULL,
@@ -248,8 +249,8 @@ const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_conversat
248
249
  unknown_tool_call_ids_json TEXT,
249
250
  parent_submission_id TEXT,
250
251
  parent_tool_call_id TEXT,
251
- UNIQUE (conversation_id, principal, idempotency_key),
252
- UNIQUE (conversation_id, queue_sequence)
252
+ UNIQUE (thread_id, principal, idempotency_key),
253
+ UNIQUE (thread_id, queue_sequence)
253
254
  )
254
255
  `.withoutTransform;
255
256
  yield* sql`
@@ -277,7 +278,7 @@ const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_conversat
277
278
  CREATE TABLE effect_agent_attempts (
278
279
  attempt_id TEXT PRIMARY KEY NOT NULL,
279
280
  submission_id TEXT NOT NULL,
280
- conversation_id TEXT NOT NULL,
281
+ thread_id TEXT NOT NULL,
281
282
  owner_producer_id TEXT NOT NULL,
282
283
  producer_epoch INTEGER NOT NULL,
283
284
  claimed_at TEXT NOT NULL,
@@ -377,7 +378,7 @@ const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_conversat
377
378
  `.withoutTransform;
378
379
  yield* sql`
379
380
  INSERT INTO effect_agent_meta (key, value)
380
- VALUES ('storage_version', ${String(1)})
381
+ VALUES ('storage_version', ${String(2)})
381
382
  `.withoutTransform;
382
383
  }) });
383
384
  //#endregion
@@ -390,7 +391,7 @@ const doMigrations = SqliteMigrator.fromRecord({ "1_current_cloudflare_conversat
390
391
  */
391
392
  const BoundedStoredText$1 = Schema.String.check(Schema.isMaxLength(2e6));
392
393
  const BoundedIdentifier$1 = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
393
- const MAX_RECORDS_PER_CONVERSATION = 65536;
394
+ const MAX_RECORDS_PER_THREAD = 65536;
394
395
  const MAX_IDENTIFIER_LENGTH$1 = 1024;
395
396
  /** Durable Object SQL storage allows at most 100 bound parameters per statement. */
396
397
  const MAX_BOUND_PARAMETERS = 100;
@@ -403,8 +404,8 @@ const chunked = (values, size) => {
403
404
  };
404
405
  var DoMetaRow = class extends Schema.Class("DoMetaRow")({ value: Schema.NonEmptyString.check(Schema.isMaxLength(128)) }) {};
405
406
  var DoNameRow = class extends Schema.Class("DoNameRow")({ name: BoundedIdentifier$1 }) {};
406
- var ConversationRow = class extends Schema.Class("ConversationRow")({
407
- conversation_id: BoundedIdentifier$1,
407
+ var ThreadRow = class extends Schema.Class("ThreadRow")({
408
+ thread_id: BoundedIdentifier$1,
408
409
  created_at: Schema.NonEmptyString.check(Schema.isMaxLength(128)),
409
410
  producer_epoch: ProducerEpoch,
410
411
  tail_digest: BoundedStoredText$1,
@@ -414,21 +415,21 @@ var BatchRow = class extends Schema.Class("BatchRow")({
414
415
  batch_digest: BoundedStoredText$1,
415
416
  batch_id: BoundedIdentifier$1,
416
417
  batch_json: BoundedStoredText$1,
417
- conversation_id: BoundedIdentifier$1,
418
+ thread_id: BoundedIdentifier$1,
418
419
  first_sequence: CanonicalSequence,
419
420
  last_sequence: CanonicalSequence,
420
421
  tail_digest: BoundedStoredText$1
421
422
  }) {};
422
423
  var RecordRow = class extends Schema.Class("RecordRow")({
423
424
  batch_id: BoundedIdentifier$1,
424
- conversation_id: BoundedIdentifier$1,
425
+ thread_id: BoundedIdentifier$1,
425
426
  record_id: BoundedIdentifier$1,
426
427
  record_json: BoundedStoredText$1,
427
428
  sequence: CanonicalSequence
428
429
  }) {};
429
430
  var CheckpointRow = class extends Schema.Class("CheckpointRow")({
430
431
  checkpoint_json: BoundedStoredText$1,
431
- conversation_id: BoundedIdentifier$1,
432
+ thread_id: BoundedIdentifier$1,
432
433
  tail_digest: BoundedStoredText$1,
433
434
  through_sequence: CanonicalSequence
434
435
  }) {};
@@ -440,7 +441,7 @@ var RawAppendRequest = class extends Schema.Class("@effect-agent/storage-cloudfl
440
441
  batchDigest: BoundedStoredText$1,
441
442
  batchId: BoundedIdentifier$1,
442
443
  batchJson: BoundedStoredText$1,
443
- conversationId: BoundedIdentifier$1,
444
+ threadId: BoundedIdentifier$1,
444
445
  expectedTailDigest: BoundedStoredText$1,
445
446
  expectedTailSequence: CanonicalSequence,
446
447
  producerEpoch: ProducerEpoch,
@@ -454,36 +455,36 @@ var RawAppendResult = class extends Schema.Class("@effect-agent/storage-cloudfla
454
455
  tailDigest: BoundedStoredText$1
455
456
  }) {};
456
457
  var RawReadRequest = class extends Schema.Class("@effect-agent/storage-cloudflare/RawReadRequest")({
457
- conversationId: BoundedIdentifier$1,
458
+ threadId: BoundedIdentifier$1,
458
459
  fromSequenceExclusive: CanonicalSequence,
459
460
  limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1024))
460
461
  }) {};
461
462
  var RawCheckpoint = class extends Schema.Class("@effect-agent/storage-cloudflare/RawCheckpoint")({
462
463
  checkpointJson: BoundedStoredText$1,
463
- conversationId: BoundedIdentifier$1,
464
+ threadId: BoundedIdentifier$1,
464
465
  tailDigest: BoundedStoredText$1,
465
466
  throughSequence: CanonicalSequence
466
467
  }) {};
467
- var RawConversationExport = class extends Schema.Class("@effect-agent/storage-cloudflare/RawConversationExport")({
468
+ var RawThreadExport = class extends Schema.Class("@effect-agent/storage-cloudflare/RawThreadExport")({
468
469
  batches: Schema.Array(BatchRow),
469
470
  checkpoints: Schema.Array(CheckpointRow),
470
- conversation: ConversationRow,
471
+ thread: ThreadRow,
471
472
  records: Schema.Array(RecordRow)
472
473
  }) {};
473
- const noFailpoint$1 = () => Effect.void;
474
+ const noFailpoint = () => Effect.void;
474
475
  const storageError = (operation) => (error) => DoStorageError.make({
475
476
  cause: error,
476
477
  operation,
477
478
  message: error.message
478
479
  });
479
480
  /** Decode raw Durable Object SQLite rows against a Schema, reporting failures as typed corruption. */
480
- const decodeRows$1 = Effect.fn((schema, table, rowKey, rows) => Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError((error) => DoStorageCorruptionError.make({
481
+ const decodeRows$2 = Effect.fn((schema, table, rowKey, rows) => Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError((error) => DoStorageCorruptionError.make({
481
482
  table,
482
483
  rowKey,
483
484
  message: String(error)
484
485
  }))));
485
486
  /** Decode exactly one raw row against a Schema, reporting failures as typed corruption. */
486
- const decodeSingleRow = Effect.fn((schema, table, rowKey, rows) => decodeRows$1(schema, table, rowKey, rows).pipe(Effect.flatMap((decoded) => decoded.length === 1 ? Effect.succeed(decoded[0]) : Effect.fail(DoStorageCorruptionError.make({
487
+ const decodeSingleRow = Effect.fn((schema, table, rowKey, rows) => decodeRows$2(schema, table, rowKey, rows).pipe(Effect.flatMap((decoded) => decoded.length === 1 ? Effect.succeed(decoded[0]) : Effect.fail(DoStorageCorruptionError.make({
487
488
  table,
488
489
  rowKey,
489
490
  message: `Expected exactly one row but found ${decoded.length}.`
@@ -497,7 +498,7 @@ const REQUIRED_TABLES = [
497
498
  "effect_agent_checkpoints",
498
499
  "effect_agent_child_reservations",
499
500
  "effect_agent_child_settlements",
500
- "effect_agent_conversations",
501
+ "effect_agent_threads",
501
502
  "effect_agent_meta",
502
503
  "effect_agent_settlement_reservations",
503
504
  "effect_agent_submission_ownership",
@@ -511,14 +512,14 @@ const REQUIRED_TABLES = [
511
512
  * through output gates) and no busy timeout (a Durable Object has exactly one writer): the
512
513
  * Node machinery those served has no DC analogue and is deliberately absent.
513
514
  */
514
- const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(function* (sql, failpoint = noFailpoint$1, maxStoredValueBytes) {
515
+ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(function* (sql, failpoint = noFailpoint, maxStoredValueBytes) {
515
516
  const metaTableRows = yield* sql`
516
517
  SELECT name
517
518
  FROM sqlite_master
518
519
  WHERE type = 'table'
519
520
  AND name = 'effect_agent_meta'
520
521
  `.pipe(Effect.mapError(storageError("read storage version table")));
521
- if ((yield* decodeRows$1(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_meta", metaTableRows)).length === 0) {
522
+ if ((yield* decodeRows$2(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_meta", metaTableRows)).length === 0) {
522
523
  const existingRows = yield* sql`
523
524
  SELECT name
524
525
  FROM sqlite_master
@@ -526,9 +527,9 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
526
527
  AND name LIKE 'effect_agent_%'
527
528
  ORDER BY name
528
529
  `.pipe(Effect.mapError(storageError("inspect unversioned storage")));
529
- if ((yield* decodeRows$1(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_%", existingRows)).length > 0) return yield* DoStorageCompatibilityError.make({
530
+ if ((yield* decodeRows$2(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_%", existingRows)).length > 0) return yield* DoStorageCompatibilityError.make({
530
531
  actualVersion: 0,
531
- supportedVersion: 1,
532
+ supportedVersion: 2,
532
533
  message: "The Durable Object contains unversioned Effect Agent tables. Reset the development namespace explicitly; refusing to mutate ambiguous stored data."
533
534
  });
534
535
  yield* SqliteMigrator.run({ loader: doMigrations }).pipe(Effect.provideService(SqlClient.SqlClient, sql), Effect.mapError((error) => DoStorageError.make({
@@ -543,12 +544,12 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
543
544
  WHERE key = 'storage_version'
544
545
  `.pipe(Effect.mapError(storageError("read storage version")));
545
546
  const version = yield* decodeSingleRow(Schema.Array(DoMetaRow), "effect_agent_meta", "storage_version", versionRows);
546
- if (version.value !== String(1)) {
547
+ if (version.value !== String(2)) {
547
548
  const actualVersion = Number.parseInt(version.value, 10);
548
549
  return yield* DoStorageCompatibilityError.make({
549
550
  actualVersion: Number.isSafeInteger(actualVersion) ? actualVersion : -1,
550
- supportedVersion: 1,
551
- message: `The Durable Object uses private-development storage version ${version.value}; this build supports exactly version 1. Replace the development namespace explicitly; automatic stored-data migrations are not provided during private development.`
551
+ supportedVersion: 2,
552
+ message: `The Durable Object uses private-development storage version ${version.value}; this build supports exactly version 2. Replace the development namespace explicitly; automatic stored-data migrations are not provided during private development.`
552
553
  });
553
554
  }
554
555
  }
@@ -559,9 +560,9 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
559
560
  AND name IN ${sql.in([...REQUIRED_TABLES])}
560
561
  ORDER BY name
561
562
  `.pipe(Effect.mapError(storageError("verify storage tables")));
562
- if ((yield* decodeRows$1(Schema.Array(DoNameRow), "sqlite_master", "required_tables", requiredRows)).length !== REQUIRED_TABLES.length) return yield* DoStorageCompatibilityError.make({
563
- actualVersion: 1,
564
- supportedVersion: 1,
563
+ if ((yield* decodeRows$2(Schema.Array(DoNameRow), "sqlite_master", "required_tables", requiredRows)).length !== REQUIRED_TABLES.length) return yield* DoStorageCompatibilityError.make({
564
+ actualVersion: 2,
565
+ supportedVersion: 2,
565
566
  message: "The Durable Object claims the current format but is missing required tables. Reset the corrupt private-development data."
566
567
  });
567
568
  return makeJournal(sql, failpoint, maxStoredValueBytes);
@@ -589,45 +590,45 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
589
590
  * transaction.
590
591
  */
591
592
  const withWriteTransaction = (operation) => (effect) => sql.withTransaction(effect).pipe(Effect.mapError((error) => isSqlError(error) ? storageError(operation)(error) : error), Effect.withSpan("DoJournal.withWriteTransaction", { attributes: { operation } }));
592
- const materialize = Effect.fn("DoJournal.materialize")(function* (conversationId, createdAt, emptyTailDigest, producerEpoch) {
593
- if (conversationId.length > MAX_IDENTIFIER_LENGTH$1) return yield* DoStorageError.make({
594
- operation: "materialize conversation",
595
- message: "Conversation identity exceeds the Durable Object storage bounds."
593
+ const materialize = Effect.fn("DoJournal.materialize")(function* (threadId, createdAt, emptyTailDigest, producerEpoch) {
594
+ if (threadId.length > MAX_IDENTIFIER_LENGTH$1) return yield* DoStorageError.make({
595
+ operation: "materialize thread",
596
+ message: "Thread identity exceeds the Durable Object storage bounds."
596
597
  });
597
- yield* checkValueBound("materialize conversation", emptyTailDigest);
598
+ yield* checkValueBound("materialize thread", emptyTailDigest);
598
599
  yield* withWriteTransaction("materialize transaction")(Effect.gen(function* () {
599
600
  const existingRows = yield* sql`
600
601
  SELECT
601
- conversation_id,
602
+ thread_id,
602
603
  created_at,
603
604
  tail_sequence,
604
605
  tail_digest,
605
606
  producer_epoch
606
- FROM effect_agent_conversations
607
- WHERE conversation_id = ${conversationId}
608
- `.pipe(Effect.mapError(storageError("read materialized conversation")));
609
- const existing = yield* decodeRows$1(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, existingRows);
607
+ FROM effect_agent_threads
608
+ WHERE thread_id = ${threadId}
609
+ `.pipe(Effect.mapError(storageError("read materialized thread")));
610
+ const existing = yield* decodeRows$2(Schema.Array(ThreadRow), "effect_agent_threads", threadId, existingRows);
610
611
  if (existing.length > 1) return yield* DoStorageCorruptionError.make({
611
- table: "effect_agent_conversations",
612
- rowKey: conversationId,
613
- message: "A conversation primary key returned more than one row."
612
+ table: "effect_agent_threads",
613
+ rowKey: threadId,
614
+ message: "A thread primary key returned more than one row."
614
615
  });
615
616
  if (existing.length === 0) {
616
617
  yield* sql`
617
- INSERT INTO effect_agent_conversations (
618
- conversation_id,
618
+ INSERT INTO effect_agent_threads (
619
+ thread_id,
619
620
  created_at,
620
621
  tail_sequence,
621
622
  tail_digest,
622
623
  producer_epoch
623
624
  ) VALUES (
624
- ${conversationId},
625
+ ${threadId},
625
626
  ${createdAt},
626
627
  0,
627
628
  ${emptyTailDigest},
628
629
  ${producerEpoch}
629
630
  )
630
- `.pipe(Effect.mapError(storageError("materialize conversation")));
631
+ `.pipe(Effect.mapError(storageError("materialize thread")));
631
632
  return;
632
633
  }
633
634
  if (producerEpoch < existing[0].producer_epoch) return yield* DoFenceRejected.make({
@@ -636,27 +637,27 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
636
637
  message: `Producer epoch ${producerEpoch} is stale; current epoch is ${existing[0].producer_epoch}.`
637
638
  });
638
639
  if (producerEpoch > existing[0].producer_epoch) yield* sql`
639
- UPDATE effect_agent_conversations
640
+ UPDATE effect_agent_threads
640
641
  SET producer_epoch = ${producerEpoch}
641
- WHERE conversation_id = ${conversationId}
642
+ WHERE thread_id = ${threadId}
642
643
  `.pipe(Effect.mapError(storageError("advance materialization epoch")));
643
644
  }));
644
645
  });
645
- const getConversation = Effect.fn("DoJournal.getConversation")(function* (conversationId) {
646
+ const getThread = Effect.fn("DoJournal.getThread")(function* (threadId) {
646
647
  const rows = yield* sql`
647
648
  SELECT
648
- conversation_id,
649
+ thread_id,
649
650
  created_at,
650
651
  tail_sequence,
651
652
  tail_digest,
652
653
  producer_epoch
653
- FROM effect_agent_conversations
654
- WHERE conversation_id = ${conversationId}
655
- `.pipe(Effect.mapError(storageError("read conversation")));
656
- return yield* decodeRows$1(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, rows);
654
+ FROM effect_agent_threads
655
+ WHERE thread_id = ${threadId}
656
+ `.pipe(Effect.mapError(storageError("read thread")));
657
+ return yield* decodeRows$2(Schema.Array(ThreadRow), "effect_agent_threads", threadId, rows);
657
658
  });
658
659
  const append = Effect.fn("DoJournal.append")(function* (request) {
659
- if (request.conversationId.length > MAX_IDENTIFIER_LENGTH$1 || request.batchId.length > MAX_IDENTIFIER_LENGTH$1 || request.records.some((record) => record.recordId.length > MAX_IDENTIFIER_LENGTH$1)) return yield* DoStorageError.make({
660
+ if (request.threadId.length > MAX_IDENTIFIER_LENGTH$1 || request.batchId.length > MAX_IDENTIFIER_LENGTH$1 || request.records.some((record) => record.recordId.length > MAX_IDENTIFIER_LENGTH$1)) return yield* DoStorageError.make({
660
661
  operation: "append canonical batch",
661
662
  message: "Canonical identifiers exceed the Durable Object storage bounds."
662
663
  });
@@ -670,25 +671,25 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
670
671
  message: `Batch ${request.batchId} contains duplicate canonical record IDs.`,
671
672
  reason: "record-identity"
672
673
  });
673
- const conversationRows = yield* sql`
674
+ const threadRows = yield* sql`
674
675
  SELECT
675
- conversation_id,
676
+ thread_id,
676
677
  created_at,
677
678
  tail_sequence,
678
679
  tail_digest,
679
680
  producer_epoch
680
- FROM effect_agent_conversations
681
- WHERE conversation_id = ${request.conversationId}
681
+ FROM effect_agent_threads
682
+ WHERE thread_id = ${request.threadId}
682
683
  `.pipe(Effect.mapError(storageError("read append tail")));
683
- const conversation = yield* decodeSingleRow(Schema.Array(ConversationRow), "effect_agent_conversations", request.conversationId, conversationRows);
684
- if (request.producerEpoch !== conversation.producer_epoch) return yield* DoFenceRejected.make({
684
+ const thread = yield* decodeSingleRow(Schema.Array(ThreadRow), "effect_agent_threads", request.threadId, threadRows);
685
+ if (request.producerEpoch !== thread.producer_epoch) return yield* DoFenceRejected.make({
685
686
  producerEpoch: request.producerEpoch,
686
- actualEpoch: conversation.producer_epoch,
687
- message: `Producer epoch ${request.producerEpoch} is not the current epoch ${conversation.producer_epoch}.`
687
+ actualEpoch: thread.producer_epoch,
688
+ message: `Producer epoch ${request.producerEpoch} is not the current epoch ${thread.producer_epoch}.`
688
689
  });
689
690
  const batchRows = yield* sql`
690
691
  SELECT
691
- conversation_id,
692
+ thread_id,
692
693
  batch_id,
693
694
  first_sequence,
694
695
  last_sequence,
@@ -696,13 +697,13 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
696
697
  tail_digest,
697
698
  batch_json
698
699
  FROM effect_agent_canonical_batches
699
- WHERE conversation_id = ${request.conversationId}
700
+ WHERE thread_id = ${request.threadId}
700
701
  AND batch_id = ${request.batchId}
701
702
  `.pipe(Effect.mapError(storageError("read idempotent batch")));
702
- const batches = yield* decodeRows$1(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${request.conversationId}/${request.batchId}`, batchRows);
703
+ const batches = yield* decodeRows$2(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${request.threadId}/${request.batchId}`, batchRows);
703
704
  if (batches.length > 1) return yield* DoStorageCorruptionError.make({
704
705
  table: "effect_agent_canonical_batches",
705
- rowKey: `${request.conversationId}/${request.batchId}`,
706
+ rowKey: `${request.threadId}/${request.batchId}`,
706
707
  message: "A canonical batch primary key returned more than one row."
707
708
  });
708
709
  if (batches.length === 1) {
@@ -718,37 +719,37 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
718
719
  tailDigest: existing.tail_digest
719
720
  });
720
721
  }
721
- if (request.expectedTailSequence !== conversation.tail_sequence || request.expectedTailDigest !== conversation.tail_digest) return yield* DoAppendConflict.make({
722
- message: `Expected tail ${request.expectedTailSequence}/${request.expectedTailDigest} but found ${conversation.tail_sequence}/${conversation.tail_digest}.`,
722
+ if (request.expectedTailSequence !== thread.tail_sequence || request.expectedTailDigest !== thread.tail_digest) return yield* DoAppendConflict.make({
723
+ message: `Expected tail ${request.expectedTailSequence}/${request.expectedTailDigest} but found ${thread.tail_sequence}/${thread.tail_digest}.`,
723
724
  reason: "tail",
724
- actualTailSequence: conversation.tail_sequence,
725
- actualTailDigest: conversation.tail_digest
725
+ actualTailSequence: thread.tail_sequence,
726
+ actualTailDigest: thread.tail_digest
726
727
  });
727
- if (conversation.tail_sequence + request.records.length > MAX_RECORDS_PER_CONVERSATION) return yield* DoStorageError.make({
728
+ if (thread.tail_sequence + request.records.length > MAX_RECORDS_PER_THREAD) return yield* DoStorageError.make({
728
729
  operation: "append canonical batch",
729
- message: `Conversation record limit ${MAX_RECORDS_PER_CONVERSATION} would be exceeded.`
730
+ message: `Thread record limit ${MAX_RECORDS_PER_THREAD} would be exceeded.`
730
731
  });
731
732
  const existingRecords = [];
732
733
  for (const chunk of chunked(recordIds, MAX_BOUND_PARAMETERS - 10)) {
733
734
  const existingRecordRows = yield* sql`
734
735
  SELECT
735
- conversation_id,
736
+ thread_id,
736
737
  sequence,
737
738
  record_id,
738
739
  batch_id,
739
740
  record_json
740
741
  FROM effect_agent_canonical_records
741
- WHERE conversation_id = ${request.conversationId}
742
+ WHERE thread_id = ${request.threadId}
742
743
  AND record_id IN ${sql.in([...chunk])}
743
744
  ORDER BY sequence
744
745
  `.pipe(Effect.mapError(storageError("check canonical record identities")));
745
- existingRecords.push(...yield* decodeRows$1(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.conversationId}/record_ids`, existingRecordRows));
746
+ existingRecords.push(...yield* decodeRows$2(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.threadId}/record_ids`, existingRecordRows));
746
747
  }
747
748
  if (existingRecords.length > 0) return yield* DoAppendConflict.make({
748
749
  message: `Canonical record ID ${existingRecords[0].record_id} already exists.`,
749
750
  reason: "record-identity"
750
751
  });
751
- const firstSequence = yield* Schema.decodeUnknownEffect(CanonicalSequence)(conversation.tail_sequence + 1).pipe(Effect.mapError((error) => DoStorageError.make({
752
+ const firstSequence = yield* Schema.decodeUnknownEffect(CanonicalSequence)(thread.tail_sequence + 1).pipe(Effect.mapError((error) => DoStorageError.make({
752
753
  cause: error,
753
754
  operation: "append canonical batch",
754
755
  message: error.message
@@ -760,7 +761,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
760
761
  })));
761
762
  yield* sql`
762
763
  INSERT INTO effect_agent_canonical_batches (
763
- conversation_id,
764
+ thread_id,
764
765
  batch_id,
765
766
  first_sequence,
766
767
  last_sequence,
@@ -768,7 +769,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
768
769
  tail_digest,
769
770
  batch_json
770
771
  ) VALUES (
771
- ${request.conversationId},
772
+ ${request.threadId},
772
773
  ${request.batchId},
773
774
  ${firstSequence},
774
775
  ${lastSequence},
@@ -781,13 +782,13 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
781
782
  yield* Effect.forEach(request.records, (record, index) => Effect.gen(function* () {
782
783
  yield* sql`
783
784
  INSERT INTO effect_agent_canonical_records (
784
- conversation_id,
785
+ thread_id,
785
786
  sequence,
786
787
  record_id,
787
788
  batch_id,
788
789
  record_json
789
790
  ) VALUES (
790
- ${request.conversationId},
791
+ ${request.threadId},
791
792
  ${firstSequence + index},
792
793
  ${record.recordId},
793
794
  ${request.batchId},
@@ -797,13 +798,13 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
797
798
  yield* failpoint("append:after-record-insert");
798
799
  }), { discard: true });
799
800
  yield* sql`
800
- UPDATE effect_agent_conversations
801
+ UPDATE effect_agent_threads
801
802
  SET
802
803
  tail_sequence = ${lastSequence},
803
804
  tail_digest = ${request.tailDigest},
804
805
  producer_epoch = ${request.producerEpoch}
805
- WHERE conversation_id = ${request.conversationId}
806
- `.pipe(Effect.mapError(storageError("advance conversation tail")));
806
+ WHERE thread_id = ${request.threadId}
807
+ `.pipe(Effect.mapError(storageError("advance thread tail")));
807
808
  yield* failpoint("append:after-tail-update");
808
809
  return RawAppendResult.make({
809
810
  firstSequence,
@@ -816,36 +817,36 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
816
817
  const read = Effect.fn("DoJournal.read")(function* (request) {
817
818
  const rows = yield* sql`
818
819
  SELECT
819
- conversation_id,
820
+ thread_id,
820
821
  sequence,
821
822
  record_id,
822
823
  batch_id,
823
824
  record_json
824
825
  FROM effect_agent_canonical_records
825
- WHERE conversation_id = ${request.conversationId}
826
+ WHERE thread_id = ${request.threadId}
826
827
  AND sequence > ${request.fromSequenceExclusive}
827
828
  ORDER BY sequence
828
829
  LIMIT ${request.limit}
829
830
  `.pipe(Effect.mapError(storageError("read canonical records")));
830
- return yield* decodeRows$1(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.conversationId}>${request.fromSequenceExclusive}`, rows);
831
+ return yield* decodeRows$2(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.threadId}>${request.fromSequenceExclusive}`, rows);
831
832
  });
832
- const exportConversation = Effect.fn("DoJournal.exportConversation")(function* (conversationId) {
833
+ const exportThread = Effect.fn("DoJournal.exportThread")(function* (threadId) {
833
834
  return yield* sql.withTransaction(Effect.gen(function* () {
834
- const conversationRows = yield* sql`
835
+ const threadRows = yield* sql`
835
836
  SELECT
836
- conversation_id,
837
+ thread_id,
837
838
  created_at,
838
839
  tail_sequence,
839
840
  tail_digest,
840
841
  producer_epoch
841
- FROM effect_agent_conversations
842
- WHERE conversation_id = ${conversationId}
843
- `.pipe(Effect.mapError(storageError("export conversation")));
844
- const conversation = yield* decodeSingleRow(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, conversationRows);
845
- yield* failpoint("export:after-conversation-read");
842
+ FROM effect_agent_threads
843
+ WHERE thread_id = ${threadId}
844
+ `.pipe(Effect.mapError(storageError("export thread")));
845
+ const thread = yield* decodeSingleRow(Schema.Array(ThreadRow), "effect_agent_threads", threadId, threadRows);
846
+ yield* failpoint("export:after-thread-read");
846
847
  const batchRows = yield* sql`
847
848
  SELECT
848
- conversation_id,
849
+ thread_id,
849
850
  batch_id,
850
851
  first_sequence,
851
852
  last_sequence,
@@ -853,71 +854,71 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
853
854
  tail_digest,
854
855
  batch_json
855
856
  FROM effect_agent_canonical_batches
856
- WHERE conversation_id = ${conversationId}
857
+ WHERE thread_id = ${threadId}
857
858
  ORDER BY first_sequence
858
859
  `.pipe(Effect.mapError(storageError("export canonical batches")));
859
860
  const recordRows = yield* sql`
860
861
  SELECT
861
- conversation_id,
862
+ thread_id,
862
863
  sequence,
863
864
  record_id,
864
865
  batch_id,
865
866
  record_json
866
867
  FROM effect_agent_canonical_records
867
- WHERE conversation_id = ${conversationId}
868
+ WHERE thread_id = ${threadId}
868
869
  ORDER BY sequence
869
870
  `.pipe(Effect.mapError(storageError("export canonical records")));
870
871
  const checkpointRows = yield* sql`
871
872
  SELECT
872
- conversation_id,
873
+ thread_id,
873
874
  through_sequence,
874
875
  tail_digest,
875
876
  checkpoint_json
876
877
  FROM effect_agent_checkpoints
877
- WHERE conversation_id = ${conversationId}
878
+ WHERE thread_id = ${threadId}
878
879
  ORDER BY through_sequence
879
880
  `.pipe(Effect.mapError(storageError("export checkpoints")));
880
- return RawConversationExport.make({
881
- conversation,
882
- batches: yield* decodeRows$1(Schema.Array(BatchRow), "effect_agent_canonical_batches", conversationId, batchRows),
883
- records: yield* decodeRows$1(Schema.Array(RecordRow), "effect_agent_canonical_records", conversationId, recordRows),
884
- checkpoints: yield* decodeRows$1(Schema.Array(CheckpointRow), "effect_agent_checkpoints", conversationId, checkpointRows)
881
+ return RawThreadExport.make({
882
+ thread,
883
+ batches: yield* decodeRows$2(Schema.Array(BatchRow), "effect_agent_canonical_batches", threadId, batchRows),
884
+ records: yield* decodeRows$2(Schema.Array(RecordRow), "effect_agent_canonical_records", threadId, recordRows),
885
+ checkpoints: yield* decodeRows$2(Schema.Array(CheckpointRow), "effect_agent_checkpoints", threadId, checkpointRows)
885
886
  });
886
887
  })).pipe(Effect.catchTag("SqlError", (error) => Effect.fail(storageError("export transaction")(error))));
887
888
  });
888
889
  const saveCheckpoint = Effect.fn("DoJournal.saveCheckpoint")(function* (checkpoint) {
889
- if (checkpoint.conversationId.length > MAX_IDENTIFIER_LENGTH$1) return yield* DoStorageError.make({
890
+ if (checkpoint.threadId.length > MAX_IDENTIFIER_LENGTH$1) return yield* DoStorageError.make({
890
891
  operation: "save checkpoint",
891
892
  message: "Checkpoint identity exceeds the Durable Object storage bounds."
892
893
  });
893
894
  yield* checkValueBound("save checkpoint", checkpoint.checkpointJson);
894
895
  yield* withWriteTransaction("checkpoint transaction")(Effect.gen(function* () {
895
- const conversationRows = yield* sql`
896
+ const threadRows = yield* sql`
896
897
  SELECT
897
- conversation_id,
898
+ thread_id,
898
899
  created_at,
899
900
  tail_sequence,
900
901
  tail_digest,
901
902
  producer_epoch
902
- FROM effect_agent_conversations
903
- WHERE conversation_id = ${checkpoint.conversationId}
903
+ FROM effect_agent_threads
904
+ WHERE thread_id = ${checkpoint.threadId}
904
905
  `.pipe(Effect.mapError(storageError("read checkpoint tail")));
905
- const conversation = yield* decodeSingleRow(Schema.Array(ConversationRow), "effect_agent_conversations", checkpoint.conversationId, conversationRows);
906
- if (checkpoint.throughSequence > conversation.tail_sequence) return yield* DoCheckpointConflict.make({ message: `Checkpoint sequence ${checkpoint.throughSequence} is after canonical tail ${conversation.tail_sequence}.` });
906
+ const thread = yield* decodeSingleRow(Schema.Array(ThreadRow), "effect_agent_threads", checkpoint.threadId, threadRows);
907
+ if (checkpoint.throughSequence > thread.tail_sequence) return yield* DoCheckpointConflict.make({ message: `Checkpoint sequence ${checkpoint.throughSequence} is after canonical tail ${thread.tail_sequence}.` });
907
908
  const checkpointRows = yield* sql`
908
909
  SELECT
909
- conversation_id,
910
+ thread_id,
910
911
  through_sequence,
911
912
  tail_digest,
912
913
  checkpoint_json
913
914
  FROM effect_agent_checkpoints
914
- WHERE conversation_id = ${checkpoint.conversationId}
915
+ WHERE thread_id = ${checkpoint.threadId}
915
916
  AND through_sequence = ${checkpoint.throughSequence}
916
917
  `.pipe(Effect.mapError(storageError("read idempotent checkpoint")));
917
- const existing = yield* decodeRows$1(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${checkpoint.conversationId}/${checkpoint.throughSequence}`, checkpointRows);
918
+ const existing = yield* decodeRows$2(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${checkpoint.threadId}/${checkpoint.throughSequence}`, checkpointRows);
918
919
  if (existing.length > 1) return yield* DoStorageCorruptionError.make({
919
920
  table: "effect_agent_checkpoints",
920
- rowKey: `${checkpoint.conversationId}/${checkpoint.throughSequence}`,
921
+ rowKey: `${checkpoint.threadId}/${checkpoint.throughSequence}`,
921
922
  message: "A checkpoint primary key returned more than one row."
922
923
  });
923
924
  if (existing.length === 1) {
@@ -926,12 +927,12 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
926
927
  }
927
928
  yield* sql`
928
929
  INSERT INTO effect_agent_checkpoints (
929
- conversation_id,
930
+ thread_id,
930
931
  through_sequence,
931
932
  tail_digest,
932
933
  checkpoint_json
933
934
  ) VALUES (
934
- ${checkpoint.conversationId},
935
+ ${checkpoint.threadId},
935
936
  ${checkpoint.throughSequence},
936
937
  ${checkpoint.tailDigest},
937
938
  ${checkpoint.checkpointJson}
@@ -939,34 +940,34 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
939
940
  `.pipe(Effect.mapError(storageError("insert checkpoint")));
940
941
  }));
941
942
  });
942
- const loadCheckpoint = Effect.fn("DoJournal.loadCheckpoint")(function* (conversationId, atOrBeforeSequence) {
943
+ const loadCheckpoint = Effect.fn("DoJournal.loadCheckpoint")(function* (threadId, atOrBeforeSequence) {
943
944
  const rows = yield* sql`
944
945
  SELECT
945
- conversation_id,
946
+ thread_id,
946
947
  through_sequence,
947
948
  tail_digest,
948
949
  checkpoint_json
949
950
  FROM effect_agent_checkpoints
950
- WHERE conversation_id = ${conversationId}
951
+ WHERE thread_id = ${threadId}
951
952
  AND through_sequence <= ${atOrBeforeSequence}
952
953
  ORDER BY through_sequence DESC
953
954
  LIMIT 1
954
955
  `.pipe(Effect.mapError(storageError("load checkpoint")));
955
- return yield* decodeRows$1(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${conversationId}<=${atOrBeforeSequence}`, rows);
956
+ return yield* decodeRows$2(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${threadId}<=${atOrBeforeSequence}`, rows);
956
957
  });
957
958
  return {
958
959
  append,
959
960
  checkValueBound,
960
- exportConversation,
961
- getConversation,
962
- getTailDigestAt: Effect.fn("DoJournal.getTailDigestAt")(function* (conversationId, sequence) {
961
+ exportThread,
962
+ getThread,
963
+ getTailDigestAt: Effect.fn("DoJournal.getTailDigestAt")(function* (threadId, sequence) {
963
964
  if (sequence === 0) {
964
- const conversations = yield* getConversation(conversationId);
965
- return conversations.length === 0 ? [] : [conversations[0].tail_sequence === 0 ? conversations[0].tail_digest : void 0].filter((value) => value !== void 0);
965
+ const threads = yield* getThread(threadId);
966
+ return threads.length === 0 ? [] : [threads[0].tail_sequence === 0 ? threads[0].tail_digest : void 0].filter((value) => value !== void 0);
966
967
  }
967
968
  const rows = yield* sql`
968
969
  SELECT
969
- conversation_id,
970
+ thread_id,
970
971
  batch_id,
971
972
  first_sequence,
972
973
  last_sequence,
@@ -974,10 +975,10 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
974
975
  tail_digest,
975
976
  batch_json
976
977
  FROM effect_agent_canonical_batches
977
- WHERE conversation_id = ${conversationId}
978
+ WHERE thread_id = ${threadId}
978
979
  AND last_sequence = ${sequence}
979
980
  `.pipe(Effect.mapError(storageError("read canonical digest at sequence")));
980
- return (yield* decodeRows$1(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${conversationId}/${sequence}`, rows)).map((batch) => batch.tail_digest);
981
+ return (yield* decodeRows$2(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${threadId}/${sequence}`, rows)).map((batch) => batch.tail_digest);
981
982
  }),
982
983
  loadCheckpoint,
983
984
  materialize,
@@ -985,19 +986,19 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
985
986
  saveCheckpoint,
986
987
  scanStoredPayloads: Effect.fn("DoJournal.scanStoredPayloads")(function* () {
987
988
  return yield* sql.withTransaction(Effect.gen(function* () {
988
- const conversations = yield* sql`
989
+ const threads = yield* sql`
989
990
  SELECT
990
- conversation_id,
991
+ thread_id,
991
992
  created_at,
992
993
  tail_sequence,
993
994
  tail_digest,
994
995
  producer_epoch
995
- FROM effect_agent_conversations
996
- ORDER BY conversation_id
997
- `.pipe(Effect.mapError(storageError("scan conversations")));
996
+ FROM effect_agent_threads
997
+ ORDER BY thread_id
998
+ `.pipe(Effect.mapError(storageError("scan threads")));
998
999
  const batches = yield* sql`
999
1000
  SELECT
1000
- conversation_id,
1001
+ thread_id,
1001
1002
  batch_id,
1002
1003
  first_sequence,
1003
1004
  last_sequence,
@@ -1005,32 +1006,32 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
1005
1006
  tail_digest,
1006
1007
  batch_json
1007
1008
  FROM effect_agent_canonical_batches
1008
- ORDER BY conversation_id, first_sequence
1009
+ ORDER BY thread_id, first_sequence
1009
1010
  `.pipe(Effect.mapError(storageError("scan canonical batches")));
1010
1011
  const records = yield* sql`
1011
1012
  SELECT
1012
- conversation_id,
1013
+ thread_id,
1013
1014
  sequence,
1014
1015
  record_id,
1015
1016
  batch_id,
1016
1017
  record_json
1017
1018
  FROM effect_agent_canonical_records
1018
- ORDER BY conversation_id, sequence
1019
+ ORDER BY thread_id, sequence
1019
1020
  `.pipe(Effect.mapError(storageError("scan canonical records")));
1020
1021
  const checkpoints = yield* sql`
1021
1022
  SELECT
1022
- conversation_id,
1023
+ thread_id,
1023
1024
  through_sequence,
1024
1025
  tail_digest,
1025
1026
  checkpoint_json
1026
1027
  FROM effect_agent_checkpoints
1027
- ORDER BY conversation_id, through_sequence
1028
+ ORDER BY thread_id, through_sequence
1028
1029
  `.pipe(Effect.mapError(storageError("scan checkpoints")));
1029
1030
  return {
1030
- conversations: yield* decodeRows$1(Schema.Array(ConversationRow), "effect_agent_conversations", "startup_scan", conversations),
1031
- batches: yield* decodeRows$1(Schema.Array(BatchRow), "effect_agent_canonical_batches", "startup_scan", batches),
1032
- records: yield* decodeRows$1(Schema.Array(RecordRow), "effect_agent_canonical_records", "startup_scan", records),
1033
- checkpoints: yield* decodeRows$1(Schema.Array(CheckpointRow), "effect_agent_checkpoints", "startup_scan", checkpoints)
1031
+ threads: yield* decodeRows$2(Schema.Array(ThreadRow), "effect_agent_threads", "startup_scan", threads),
1032
+ batches: yield* decodeRows$2(Schema.Array(BatchRow), "effect_agent_canonical_batches", "startup_scan", batches),
1033
+ records: yield* decodeRows$2(Schema.Array(RecordRow), "effect_agent_canonical_records", "startup_scan", records),
1034
+ checkpoints: yield* decodeRows$2(Schema.Array(CheckpointRow), "effect_agent_checkpoints", "startup_scan", checkpoints)
1034
1035
  };
1035
1036
  })).pipe(Effect.catchTag("SqlError", (error) => Effect.fail(storageError("startup scan transaction")(error))));
1036
1037
  }),
@@ -1081,41 +1082,7 @@ var DoStorageConfigValue = class extends Schema.Class("@effect-agent/storage-clo
1081
1082
  /** Explicit Durable Object storage configuration authority. */
1082
1083
  var DoStorageConfig = class extends Context.Service()("@effect-agent/storage-cloudflare/DoStorageConfig") {};
1083
1084
  //#endregion
1084
- //#region src/do-storage-failpoint.ts
1085
- const noFailpoint = () => Effect.void;
1086
- /** Test-only control for replacing the active Durable Object failpoint handler. */
1087
- var DoStorageFailpointTestControl = class extends Context.Service()("@effect-agent/storage-cloudflare/DoStorageFailpointTestControl") {};
1088
- /** Explicit fault-injection authority used at Durable Object storage operation boundaries. */
1089
- var DoStorageFailpoint = class DoStorageFailpoint extends Context.Service()("@effect-agent/storage-cloudflare/DoStorageFailpoint") {
1090
- /** Production default: no fault injection. */
1091
- static layer = Layer.succeed(this)({ hit: noFailpoint });
1092
- /** Reusable test Layer with a control service backed by the same handler Ref. */
1093
- static layerTest = Layer.effectContext(Effect.gen(function* () {
1094
- const handler = yield* Ref.make(noFailpoint);
1095
- return Context.make(DoStorageFailpoint, DoStorageFailpoint.of({ hit: (location) => Ref.get(handler).pipe(Effect.flatMap((current) => current(location))) })).pipe(Context.add(DoStorageFailpointTestControl, DoStorageFailpointTestControl.of({
1096
- clear: Ref.set(handler, noFailpoint),
1097
- setHandler: (next) => Ref.set(handler, next)
1098
- })));
1099
- }));
1100
- };
1101
- /**
1102
- * The DC-specific eviction failpoint mode: instead of failing typed, an armed hit evicts the
1103
- * Durable Object through an injected `evict` thunk — in production-shaped harnesses that thunk
1104
- * is `() => ctx.abort()`, the platform's real failure mode. `ctx.abort()` never returns (it
1105
- * throws while destroying the in-memory instance and every in-flight implicit or explicit
1106
- * storage transaction rolls back), so an armed hit ends the current Attempt exactly like an
1107
- * unannounced platform eviction; DO storage — the only correctness-critical state — survives
1108
- * for the next incarnation, which the persisted alarm wakes without any incoming request.
1109
- *
1110
- * The handles stay injected: this package never imports `cloudflare:workers`, so the harness
1111
- * that owns a `DurableObjectState` supplies the thunk.
1112
- */
1113
- const evictionFailpointHandler = (options) => (location) => options.isArmed(location).pipe(Effect.flatMap((armed) => armed ? Effect.sync(() => {
1114
- options.evict(location);
1115
- throw new Error(`Durable Object eviction did not interrupt execution at ${location}.`);
1116
- }) : Effect.void));
1117
- //#endregion
1118
- //#region src/do-conversation-store.ts
1085
+ //#region src/do-thread-store.ts
1119
1086
  const OffsetText = Schema.String.check(Schema.isMaxLength(4 * 1024));
1120
1087
  const DO_OFFSET_PREFIX = "effect-agent-do@1:";
1121
1088
  const ZERO_CANONICAL_SEQUENCE = Schema.decodeSync(CanonicalSequence)(0);
@@ -1123,36 +1090,36 @@ const isDigest = Schema.is(Digest);
1123
1090
  const isDoFenceRejected = Schema.is(DoFenceRejected);
1124
1091
  const isDoAppendConflict = Schema.is(DoAppendConflict);
1125
1092
  const isDoCheckpointConflict = Schema.is(DoCheckpointConflict);
1126
- const storeError = (operation, error) => ConversationStoreError.make({
1093
+ const storeError = (operation, error) => ThreadStoreError.make({
1127
1094
  cause: error,
1128
1095
  operation,
1129
1096
  message: error.message
1130
1097
  });
1131
- const schemaStoreError = (operation, error) => ConversationStoreError.make({
1098
+ const schemaStoreError = (operation, error) => ThreadStoreError.make({
1132
1099
  cause: error,
1133
1100
  operation,
1134
1101
  message: error.message
1135
1102
  });
1136
- const makeOffset = Effect.fn(function* (conversationId, sequence) {
1137
- return yield* Schema.decodeUnknownEffect(CanonicalSequence)(sequence).pipe(Effect.flatMap((validatedSequence) => Schema.decodeUnknownEffect(ObservationOffset)(`${DO_OFFSET_PREFIX}${encodeURIComponent(conversationId)}:${validatedSequence}`)), Effect.mapError((error) => schemaStoreError("encode observation offset", error)));
1103
+ const makeOffset = Effect.fn(function* (threadId, sequence) {
1104
+ return yield* Schema.decodeUnknownEffect(CanonicalSequence)(sequence).pipe(Effect.flatMap((validatedSequence) => Schema.decodeUnknownEffect(ObservationOffset)(`${DO_OFFSET_PREFIX}${encodeURIComponent(threadId)}:${validatedSequence}`)), Effect.mapError((error) => schemaStoreError("encode observation offset", error)));
1138
1105
  });
1139
- const parseOffset = Effect.fn(function* (conversationId, offset) {
1106
+ const parseOffset = Effect.fn(function* (threadId, offset) {
1140
1107
  if (offset === void 0) return ZERO_CANONICAL_SEQUENCE;
1141
1108
  const text = yield* Schema.decodeUnknownEffect(OffsetText)(offset).pipe(Effect.mapError((error) => schemaStoreError("decode observation offset", error)));
1142
- const conversationPrefix = `${DO_OFFSET_PREFIX}${encodeURIComponent(conversationId)}:`;
1143
- if (!text.startsWith(conversationPrefix)) return yield* ConversationStoreError.make({
1109
+ const threadPrefix = `${DO_OFFSET_PREFIX}${encodeURIComponent(threadId)}:`;
1110
+ if (!text.startsWith(threadPrefix)) return yield* ThreadStoreError.make({
1144
1111
  operation: "decode observation offset",
1145
- message: "The observation offset belongs to a different adapter, storage version, or Conversation."
1112
+ message: "The observation offset belongs to a different adapter, storage version, or Thread."
1146
1113
  });
1147
- const sequenceText = text.slice(conversationPrefix.length);
1148
- if (!/^(0|[1-9][0-9]*)$/.test(sequenceText)) return yield* ConversationStoreError.make({
1114
+ const sequenceText = text.slice(threadPrefix.length);
1115
+ if (!/^(0|[1-9][0-9]*)$/.test(sequenceText)) return yield* ThreadStoreError.make({
1149
1116
  operation: "decode observation offset",
1150
1117
  message: "The observation offset is malformed."
1151
1118
  });
1152
1119
  return yield* Schema.decodeUnknownEffect(CanonicalSequence)(Number(sequenceText)).pipe(Effect.mapError((error) => schemaStoreError("decode observation offset", error)));
1153
1120
  });
1154
- const mapFence = (conversationId, error) => FenceRejected.make({
1155
- conversationId,
1121
+ const mapFence = (threadId, error) => FenceRejected.make({
1122
+ threadId,
1156
1123
  actualEpoch: error.actualEpoch,
1157
1124
  attemptedEpoch: error.producerEpoch
1158
1125
  });
@@ -1163,18 +1130,18 @@ const encodeCanonicalBatch = Effect.fn(function* (batch) {
1163
1130
  return yield* Schema.encodeEffect(Schema.fromJsonString(CanonicalBatch))(batch).pipe(Effect.mapError((error) => schemaStoreError("encode canonical batch", error)));
1164
1131
  });
1165
1132
  const encodeCheckpoint = Effect.fn(function* (checkpoint) {
1166
- return yield* Schema.encodeEffect(Schema.fromJsonString(ConversationCheckpoint))(checkpoint).pipe(Effect.mapError((error) => schemaStoreError("encode checkpoint", error)));
1133
+ return yield* Schema.encodeEffect(Schema.fromJsonString(ThreadCheckpoint))(checkpoint).pipe(Effect.mapError((error) => schemaStoreError("encode checkpoint", error)));
1167
1134
  });
1168
1135
  const decodeEnvelope = Effect.fn(function* (row) {
1169
- const record = yield* Schema.decodeEffect(Schema.fromJsonString(CanonicalRecord))(row.record_json).pipe(Effect.mapError((error) => ConversationStoreError.make({
1136
+ const record = yield* Schema.decodeEffect(Schema.fromJsonString(CanonicalRecord))(row.record_json).pipe(Effect.mapError((error) => ThreadStoreError.make({
1170
1137
  operation: "decode canonical record",
1171
1138
  message: error.message
1172
1139
  })));
1173
- const conversationId = yield* Schema.decodeUnknownEffect(CanonicalRecordEnvelope.fields.conversationId)(row.conversation_id).pipe(Effect.mapError((error) => schemaStoreError("decode conversation identity", error)));
1174
- const offset = yield* makeOffset(conversationId, row.sequence);
1140
+ const threadId = yield* Schema.decodeUnknownEffect(CanonicalRecordEnvelope.fields.threadId)(row.thread_id).pipe(Effect.mapError((error) => schemaStoreError("decode thread identity", error)));
1141
+ const offset = yield* makeOffset(threadId, row.sequence);
1175
1142
  const batchId = yield* Schema.decodeUnknownEffect(CanonicalRecordEnvelope.fields.batchId)(row.batch_id).pipe(Effect.mapError((error) => schemaStoreError("decode batch identity", error)));
1176
1143
  return CanonicalRecordEnvelope.make({
1177
- conversationId,
1144
+ threadId,
1178
1145
  batchId,
1179
1146
  sequence: row.sequence,
1180
1147
  offset,
@@ -1182,18 +1149,18 @@ const decodeEnvelope = Effect.fn(function* (row) {
1182
1149
  });
1183
1150
  });
1184
1151
  const decodeCheckpoint = Effect.fn(function* (checkpointJson) {
1185
- return yield* Schema.decodeEffect(Schema.fromJsonString(ConversationCheckpoint))(checkpointJson).pipe(Effect.mapError((error) => schemaStoreError("decode checkpoint", error)));
1152
+ return yield* Schema.decodeEffect(Schema.fromJsonString(ThreadCheckpoint))(checkpointJson).pipe(Effect.mapError((error) => schemaStoreError("decode checkpoint", error)));
1186
1153
  });
1187
- const requireConversation = Effect.fn("DoConversationStore.requireConversation")(function* (journal, conversationId) {
1188
- const rows = yield* journal.getConversation(conversationId).pipe(Effect.mapError((error) => storeError("read conversation", error)));
1189
- if (rows.length === 0) return yield* ConversationNotMaterialized.make({ conversationId });
1154
+ const requireThread = Effect.fn("DoThreadStore.requireThread")(function* (journal, threadId) {
1155
+ const rows = yield* journal.getThread(threadId).pipe(Effect.mapError((error) => storeError("read thread", error)));
1156
+ if (rows.length === 0) return yield* ThreadNotMaterialized.make({ threadId });
1190
1157
  return rows[0];
1191
1158
  });
1192
- const tailDigestAt = Effect.fn("DoConversationStore.tailDigestAt")(function* (journal, conversationId, sequence) {
1159
+ const tailDigestAt = Effect.fn("DoThreadStore.tailDigestAt")(function* (journal, threadId, sequence) {
1193
1160
  if (sequence === 0) return EMPTY_TAIL_DIGEST;
1194
- const digests = yield* journal.getTailDigestAt(conversationId, sequence).pipe(Effect.mapError((error) => storeError("read checkpoint digest", error)));
1161
+ const digests = yield* journal.getTailDigestAt(threadId, sequence).pipe(Effect.mapError((error) => storeError("read checkpoint digest", error)));
1195
1162
  if (digests.length !== 1) return yield* CheckpointRejected.make({
1196
- conversationId,
1163
+ threadId,
1197
1164
  reason: "digest-mismatch"
1198
1165
  });
1199
1166
  return yield* Schema.decodeUnknownEffect(Digest)(digests[0]).pipe(Effect.mapError((error) => schemaStoreError("decode checkpoint digest", error)));
@@ -1212,14 +1179,14 @@ const groupByKey = (rows, key) => {
1212
1179
  * and re-digested against the canonical chain. Routine opens skip this scan: per-operation
1213
1180
  * Schema decoding plus the digest chain already fail clearly on corrupt rows.
1214
1181
  */
1215
- const decodeStartupPayloads = Effect.fn("DoConversationStore.decodeStartupPayloads")(function* (journal, crypto) {
1182
+ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(function* (journal, crypto) {
1216
1183
  const stored = yield* journal.scanStoredPayloads();
1217
1184
  const batches = yield* Effect.forEach(stored.batches, (batch) => Schema.decodeEffect(Schema.fromJsonString(CanonicalBatch))(batch.batch_json).pipe(Effect.map((decoded) => ({
1218
1185
  decoded,
1219
1186
  row: batch
1220
1187
  })), Effect.mapError((error) => DoStorageCorruptionError.make({
1221
1188
  table: "effect_agent_canonical_batches",
1222
- rowKey: `${batch.conversation_id}/${batch.batch_id}`,
1189
+ rowKey: `${batch.thread_id}/${batch.batch_id}`,
1223
1190
  message: error.message
1224
1191
  }))));
1225
1192
  const records = yield* Effect.forEach(stored.records, (record) => Schema.decodeEffect(Schema.fromJsonString(CanonicalRecord))(record.record_json).pipe(Effect.map((decoded) => ({
@@ -1227,31 +1194,31 @@ const decodeStartupPayloads = Effect.fn("DoConversationStore.decodeStartupPayloa
1227
1194
  row: record
1228
1195
  })), Effect.mapError((error) => DoStorageCorruptionError.make({
1229
1196
  table: "effect_agent_canonical_records",
1230
- rowKey: `${record.conversation_id}/${record.sequence}`,
1197
+ rowKey: `${record.thread_id}/${record.sequence}`,
1231
1198
  message: error.message
1232
1199
  }))));
1233
- const checkpoints = yield* Effect.forEach(stored.checkpoints, (checkpoint) => Schema.decodeEffect(Schema.fromJsonString(ConversationCheckpoint))(checkpoint.checkpoint_json).pipe(Effect.map((decoded) => ({
1200
+ const checkpoints = yield* Effect.forEach(stored.checkpoints, (checkpoint) => Schema.decodeEffect(Schema.fromJsonString(ThreadCheckpoint))(checkpoint.checkpoint_json).pipe(Effect.map((decoded) => ({
1234
1201
  decoded,
1235
1202
  row: checkpoint
1236
1203
  })), Effect.mapError((error) => DoStorageCorruptionError.make({
1237
1204
  table: "effect_agent_checkpoints",
1238
- rowKey: `${checkpoint.conversation_id}/${checkpoint.through_sequence}`,
1205
+ rowKey: `${checkpoint.thread_id}/${checkpoint.through_sequence}`,
1239
1206
  message: error.message
1240
1207
  }))));
1241
- const batchesByConversation = groupByKey(batches, ({ row }) => row.conversation_id);
1242
- const recordsByConversation = groupByKey(records, ({ row }) => row.conversation_id);
1243
- const checkpointsByConversation = groupByKey(checkpoints, ({ row }) => row.conversation_id);
1244
- const materializedIds = new Set(stored.conversations.map((conversation) => conversation.conversation_id));
1245
- for (const conversation of stored.conversations) {
1246
- const conversationBatches = batchesByConversation.get(conversation.conversation_id) ?? [];
1247
- const conversationRecords = recordsByConversation.get(conversation.conversation_id) ?? [];
1248
- const conversationCheckpoints = checkpointsByConversation.get(conversation.conversation_id) ?? [];
1249
- const recordsByBatch = groupByKey(conversationRecords, ({ row }) => row.batch_id);
1208
+ const batchesByThread = groupByKey(batches, ({ row }) => row.thread_id);
1209
+ const recordsByThread = groupByKey(records, ({ row }) => row.thread_id);
1210
+ const checkpointsByThread = groupByKey(checkpoints, ({ row }) => row.thread_id);
1211
+ const materializedIds = new Set(stored.threads.map((thread) => thread.thread_id));
1212
+ for (const thread of stored.threads) {
1213
+ const threadBatches = batchesByThread.get(thread.thread_id) ?? [];
1214
+ const threadRecords = recordsByThread.get(thread.thread_id) ?? [];
1215
+ const threadCheckpoints = checkpointsByThread.get(thread.thread_id) ?? [];
1216
+ const recordsByBatch = groupByKey(threadRecords, ({ row }) => row.batch_id);
1250
1217
  let previousDigest = EMPTY_TAIL_DIGEST;
1251
1218
  let expectedSequence = 1;
1252
1219
  const tailDigests = /* @__PURE__ */ new Map([[0, EMPTY_TAIL_DIGEST]]);
1253
- for (const { decoded: canonicalBatch, row: batchRow } of conversationBatches) {
1254
- const key = `${batchRow.conversation_id}/${batchRow.batch_id}`;
1220
+ for (const { decoded: canonicalBatch, row: batchRow } of threadBatches) {
1221
+ const key = `${batchRow.thread_id}/${batchRow.batch_id}`;
1255
1222
  if (canonicalBatch.batchId !== batchRow.batch_id || batchRow.first_sequence !== expectedSequence || batchRow.last_sequence !== batchRow.first_sequence + canonicalBatch.records.length - 1) return yield* DoStorageCorruptionError.make({
1256
1223
  table: "effect_agent_canonical_batches",
1257
1224
  rowKey: key,
@@ -1296,24 +1263,24 @@ const decodeStartupPayloads = Effect.fn("DoConversationStore.decodeStartupPayloa
1296
1263
  expectedSequence = batchRow.last_sequence + 1;
1297
1264
  tailDigests.set(batchRow.last_sequence, digest);
1298
1265
  }
1299
- if (conversationRecords.length !== conversation.tail_sequence || conversation.tail_sequence !== expectedSequence - 1 || conversation.tail_digest !== previousDigest) return yield* DoStorageCorruptionError.make({
1300
- table: "effect_agent_conversations",
1301
- rowKey: conversation.conversation_id,
1302
- message: "Conversation tail does not match its canonical batch chain."
1266
+ if (threadRecords.length !== thread.tail_sequence || thread.tail_sequence !== expectedSequence - 1 || thread.tail_digest !== previousDigest) return yield* DoStorageCorruptionError.make({
1267
+ table: "effect_agent_threads",
1268
+ rowKey: thread.thread_id,
1269
+ message: "Thread tail does not match its canonical batch chain."
1303
1270
  });
1304
- for (const checkpoint of conversationCheckpoints) if (checkpoint.decoded.conversationId !== conversation.conversation_id || checkpoint.decoded.throughSequence !== checkpoint.row.through_sequence || checkpoint.decoded.tailDigest !== checkpoint.row.tail_digest || tailDigests.get(checkpoint.row.through_sequence) !== checkpoint.row.tail_digest) return yield* DoStorageCorruptionError.make({
1271
+ for (const checkpoint of threadCheckpoints) if (checkpoint.decoded.threadId !== thread.thread_id || checkpoint.decoded.throughSequence !== checkpoint.row.through_sequence || checkpoint.decoded.tailDigest !== checkpoint.row.tail_digest || tailDigests.get(checkpoint.row.through_sequence) !== checkpoint.row.tail_digest) return yield* DoStorageCorruptionError.make({
1305
1272
  table: "effect_agent_checkpoints",
1306
- rowKey: `${conversation.conversation_id}/${checkpoint.row.through_sequence}`,
1273
+ rowKey: `${thread.thread_id}/${checkpoint.row.through_sequence}`,
1307
1274
  message: "Checkpoint identity or digest is not bound to a canonical batch tail."
1308
1275
  });
1309
1276
  }
1310
- if (batches.some(({ row }) => !materializedIds.has(row.conversation_id)) || records.some(({ row }) => !materializedIds.has(row.conversation_id)) || checkpoints.some(({ row }) => !materializedIds.has(row.conversation_id))) return yield* DoStorageCorruptionError.make({
1311
- table: "effect_agent_conversations",
1277
+ if (batches.some(({ row }) => !materializedIds.has(row.thread_id)) || records.some(({ row }) => !materializedIds.has(row.thread_id)) || checkpoints.some(({ row }) => !materializedIds.has(row.thread_id))) return yield* DoStorageCorruptionError.make({
1278
+ table: "effect_agent_threads",
1312
1279
  rowKey: "startup_scan",
1313
- message: "Canonical rows exist without a materialized Conversation."
1280
+ message: "Canonical rows exist without a materialized Thread."
1314
1281
  });
1315
1282
  });
1316
- const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* () {
1283
+ const makeServices$2 = Effect.fn("DoThreadStore.makeServices")(function* () {
1317
1284
  const config = yield* DoStorageConfig;
1318
1285
  const failpoint = yield* DoStorageFailpoint;
1319
1286
  const sql = yield* SqlClient.SqlClient;
@@ -1322,16 +1289,16 @@ const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* (
1322
1289
  if (config.verifyOnOpen) yield* decodeStartupPayloads(journal, crypto);
1323
1290
  const provideCrypto = (effect) => Effect.provideService(effect, Crypto.Crypto, crypto);
1324
1291
  const hitFailpoint = Effect.fn((location) => failpoint.hit(location).pipe(Effect.mapError((error) => storeError(`storage failpoint ${location}`, error))));
1325
- const materialize = Effect.fn("DoConversationStore.materialize")(function* (request) {
1326
- const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ConversationMaterialization))(request).pipe(Effect.mapError((error) => schemaStoreError("validate materialization", error)));
1292
+ const materialize = Effect.fn("DoThreadStore.materialize")(function* (request) {
1293
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadMaterialization))(request).pipe(Effect.mapError((error) => schemaStoreError("validate materialization", error)));
1327
1294
  const now = yield* Clock.currentTimeMillis;
1328
1295
  yield* hitFailpoint("materialize:before");
1329
- yield* journal.materialize(validated.conversationId, new Date(now).toISOString(), EMPTY_TAIL_DIGEST, validated.producerEpoch).pipe(Effect.mapError((error) => error._tag === "DoFenceRejected" ? mapFence(validated.conversationId, error) : storeError("materialize conversation", error)));
1296
+ yield* journal.materialize(validated.threadId, new Date(now).toISOString(), EMPTY_TAIL_DIGEST, validated.producerEpoch).pipe(Effect.mapError((error) => error._tag === "DoFenceRejected" ? mapFence(validated.threadId, error) : storeError("materialize thread", error)));
1330
1297
  yield* hitFailpoint("materialize:after");
1331
1298
  });
1332
- const append = Effect.fn("DoConversationStore.append")(function* (request) {
1299
+ const append = Effect.fn("DoThreadStore.append")(function* (request) {
1333
1300
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(FencedAppendRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate canonical append", error)));
1334
- yield* requireConversation(journal, validated.conversationId);
1301
+ yield* requireThread(journal, validated.threadId);
1335
1302
  const tailDigest = yield* provideCrypto(digestCanonicalBatch(validated.expectedTailDigest, validated.batch)).pipe(Effect.mapError((error) => storeError("digest canonical append", error)));
1336
1303
  const batchJson = yield* encodeCanonicalBatch(validated.batch);
1337
1304
  const rawRecords = yield* Effect.forEach(validated.batch.records, (record) => encodeCanonicalRecord(record).pipe(Effect.map((recordJson) => ({
@@ -1339,7 +1306,7 @@ const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* (
1339
1306
  recordJson
1340
1307
  }))));
1341
1308
  const rawRequest = yield* Schema.decodeUnknownEffect(RawAppendRequest)({
1342
- conversationId: validated.conversationId,
1309
+ threadId: validated.threadId,
1343
1310
  batchId: validated.batch.batchId,
1344
1311
  batchDigest: tailDigest,
1345
1312
  batchJson,
@@ -1351,15 +1318,15 @@ const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* (
1351
1318
  }).pipe(Effect.mapError((error) => schemaStoreError("encode canonical append", error)));
1352
1319
  yield* hitFailpoint("append:before");
1353
1320
  const result = yield* journal.append(rawRequest).pipe(Effect.mapError((error) => {
1354
- if (isDoFenceRejected(error)) return mapFence(validated.conversationId, error);
1321
+ if (isDoFenceRejected(error)) return mapFence(validated.threadId, error);
1355
1322
  if (isDoAppendConflict(error)) return error.actualTailSequence !== void 0 && isDigest(error.actualTailDigest) ? AppendConflict.make({
1356
- conversationId: validated.conversationId,
1323
+ threadId: validated.threadId,
1357
1324
  batchId: validated.batch.batchId,
1358
1325
  reason: error.reason,
1359
1326
  actualTailSequence: error.actualTailSequence,
1360
1327
  actualTailDigest: error.actualTailDigest
1361
1328
  }) : AppendConflict.make({
1362
- conversationId: validated.conversationId,
1329
+ threadId: validated.threadId,
1363
1330
  batchId: validated.batch.batchId,
1364
1331
  reason: error.reason
1365
1332
  });
@@ -1368,30 +1335,30 @@ const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* (
1368
1335
  yield* hitFailpoint("append:after");
1369
1336
  return result;
1370
1337
  });
1371
- const loadRecords = Effect.fn("DoConversationStore.loadRecords")(function* (request) {
1338
+ const loadRecords = Effect.fn("DoThreadStore.loadRecords")(function* (request) {
1372
1339
  const rows = yield* journal.read(request).pipe(Effect.mapError((error) => storeError("read canonical records", error)));
1373
1340
  return yield* Effect.forEach(rows, decodeEnvelope);
1374
1341
  });
1375
- const readEffect = Effect.fn("DoConversationStore.read")(function* (request) {
1376
- const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ConversationRead))(request).pipe(Effect.mapError((error) => schemaStoreError("validate conversation read", error)));
1377
- yield* requireConversation(journal, validated.conversationId);
1342
+ const readEffect = Effect.fn("DoThreadStore.read")(function* (request) {
1343
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadRead))(request).pipe(Effect.mapError((error) => schemaStoreError("validate thread read", error)));
1344
+ yield* requireThread(journal, validated.threadId);
1378
1345
  const records = yield* loadRecords(RawReadRequest.make({
1379
- conversationId: validated.conversationId,
1346
+ threadId: validated.threadId,
1380
1347
  fromSequenceExclusive: validated.afterSequence ?? ZERO_CANONICAL_SEQUENCE,
1381
1348
  limit: validated.limit
1382
1349
  }));
1383
1350
  return Stream.fromIterable(records);
1384
1351
  });
1385
1352
  const read = (request) => Stream.unwrap(readEffect(request));
1386
- const observeEffect = Effect.fn("DoConversationStore.observe")(function* (request) {
1387
- const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ConversationObservation))(request).pipe(Effect.mapError((error) => schemaStoreError("validate conversation observation", error)));
1388
- yield* requireConversation(journal, validated.conversationId);
1389
- const initialSequence = yield* parseOffset(validated.conversationId, validated.afterOffset);
1353
+ const observeEffect = Effect.fn("DoThreadStore.observe")(function* (request) {
1354
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadObservation))(request).pipe(Effect.mapError((error) => schemaStoreError("validate thread observation", error)));
1355
+ yield* requireThread(journal, validated.threadId);
1356
+ const initialSequence = yield* parseOffset(validated.threadId, validated.afterOffset);
1390
1357
  const cursor = yield* Ref.make(initialSequence);
1391
- const poll = Effect.fn("DoConversationStore.observePoll")(function* () {
1358
+ const poll = Effect.fn("DoThreadStore.observePoll")(function* () {
1392
1359
  const fromSequenceExclusive = yield* Ref.get(cursor);
1393
1360
  const records = yield* loadRecords(RawReadRequest.make({
1394
- conversationId: validated.conversationId,
1361
+ threadId: validated.threadId,
1395
1362
  fromSequenceExclusive,
1396
1363
  limit: 1024
1397
1364
  }));
@@ -1405,96 +1372,98 @@ const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* (
1405
1372
  return Stream.fromIterableEffectRepeat(poll());
1406
1373
  });
1407
1374
  const observe = (request) => Stream.unwrap(observeEffect(request));
1408
- const exportConversation = Effect.fn("DoConversationStore.export")(function* (request) {
1409
- const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ConversationExportRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate conversation export", error)));
1410
- yield* requireConversation(journal, validated.conversationId);
1411
- const exported = yield* journal.exportConversation(validated.conversationId).pipe(Effect.mapError((error) => storeError("export conversation", error)));
1375
+ const exportThread = Effect.fn("DoThreadStore.export")(function* (request) {
1376
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadExportRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate thread export", error)));
1377
+ yield* requireThread(journal, validated.threadId);
1378
+ const exported = yield* journal.exportThread(validated.threadId).pipe(Effect.mapError((error) => storeError("export thread", error)));
1412
1379
  const records = yield* Effect.forEach(exported.records, decodeEnvelope);
1413
- if (records.length > 65536) return yield* ConversationStoreError.make({
1414
- operation: "decode conversation export",
1415
- message: "The conversation exceeds the current export record limit."
1380
+ if (records.length > 65536) return yield* ThreadStoreError.make({
1381
+ operation: "decode thread export",
1382
+ message: "The thread exceeds the current export record limit."
1416
1383
  });
1417
- const tailDigest = yield* Schema.decodeUnknownEffect(Digest)(exported.conversation.tail_digest).pipe(Effect.mapError((error) => schemaStoreError("decode export tail digest", error)));
1418
- return ConversationExport.make({
1419
- format: "effect-agent/conversation@1",
1420
- conversationId: validated.conversationId,
1421
- tailSequence: exported.conversation.tail_sequence,
1384
+ const tailDigest = yield* Schema.decodeUnknownEffect(Digest)(exported.thread.tail_digest).pipe(Effect.mapError((error) => schemaStoreError("decode export tail digest", error)));
1385
+ return ThreadExport.make({
1386
+ format: "effect-agent/thread@1",
1387
+ threadId: validated.threadId,
1388
+ tailSequence: exported.thread.tail_sequence,
1422
1389
  tailDigest,
1423
1390
  records
1424
1391
  });
1425
1392
  });
1426
- const inspectTail = Effect.fn("DoConversationStore.inspectTail")(function* (request) {
1427
- const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ConversationTailRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate tail inspection", error)));
1428
- const conversation = yield* requireConversation(journal, validated.conversationId);
1429
- const tailDigest = yield* Schema.decodeUnknownEffect(Digest)(conversation.tail_digest).pipe(Effect.mapError((error) => schemaStoreError("decode tail digest", error)));
1430
- return ConversationTail.make({
1431
- conversationId: validated.conversationId,
1432
- tailSequence: conversation.tail_sequence,
1393
+ const inspectTail = Effect.fn("DoThreadStore.inspectTail")(function* (request) {
1394
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadTailRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate tail inspection", error)));
1395
+ const thread = yield* requireThread(journal, validated.threadId);
1396
+ const tailDigest = yield* Schema.decodeUnknownEffect(Digest)(thread.tail_digest).pipe(Effect.mapError((error) => schemaStoreError("decode tail digest", error)));
1397
+ return ThreadTail.make({
1398
+ threadId: validated.threadId,
1399
+ tailSequence: thread.tail_sequence,
1433
1400
  tailDigest,
1434
- producerEpoch: conversation.producer_epoch
1401
+ producerEpoch: thread.producer_epoch
1435
1402
  });
1436
1403
  });
1437
- const saveCheckpoint = Effect.fn("DoConversationStore.saveCheckpoint")(function* (request) {
1404
+ const saveCheckpoint = Effect.fn("DoThreadStore.saveCheckpoint")(function* (request) {
1438
1405
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SaveCheckpointRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate checkpoint", error)));
1439
- const conversation = yield* requireConversation(journal, validated.checkpoint.conversationId);
1440
- if (validated.checkpoint.throughSequence > conversation.tail_sequence) return yield* CheckpointRejected.make({
1441
- conversationId: validated.checkpoint.conversationId,
1406
+ const thread = yield* requireThread(journal, validated.checkpoint.threadId);
1407
+ if (validated.checkpoint.throughSequence > thread.tail_sequence) return yield* CheckpointRejected.make({
1408
+ threadId: validated.checkpoint.threadId,
1442
1409
  reason: "ahead-of-tail"
1443
1410
  });
1444
- if ((yield* tailDigestAt(journal, validated.checkpoint.conversationId, validated.checkpoint.throughSequence)) !== validated.checkpoint.tailDigest) return yield* CheckpointRejected.make({
1445
- conversationId: validated.checkpoint.conversationId,
1411
+ if ((yield* tailDigestAt(journal, validated.checkpoint.threadId, validated.checkpoint.throughSequence)) !== validated.checkpoint.tailDigest) return yield* CheckpointRejected.make({
1412
+ threadId: validated.checkpoint.threadId,
1446
1413
  reason: "digest-mismatch"
1447
1414
  });
1448
1415
  const checkpointJson = yield* encodeCheckpoint(validated.checkpoint);
1449
1416
  const raw = RawCheckpoint.make({
1450
- conversationId: validated.checkpoint.conversationId,
1417
+ threadId: validated.checkpoint.threadId,
1451
1418
  throughSequence: validated.checkpoint.throughSequence,
1452
1419
  tailDigest: validated.checkpoint.tailDigest,
1453
1420
  checkpointJson
1454
1421
  });
1455
1422
  yield* hitFailpoint("save-checkpoint:before");
1456
1423
  yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => isDoCheckpointConflict(error) ? CheckpointRejected.make({
1457
- conversationId: validated.checkpoint.conversationId,
1424
+ threadId: validated.checkpoint.threadId,
1458
1425
  reason: "digest-mismatch"
1459
1426
  }) : storeError("save checkpoint", error)));
1460
1427
  yield* hitFailpoint("save-checkpoint:after");
1461
1428
  });
1462
- const loadCheckpoint = Effect.fn("DoConversationStore.loadCheckpoint")(function* (request) {
1429
+ const loadCheckpoint = Effect.fn("DoThreadStore.loadCheckpoint")(function* (request) {
1463
1430
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(LoadCheckpointRequest))(request).pipe(Effect.mapError((error) => schemaStoreError("validate checkpoint lookup", error)));
1464
- const conversation = yield* requireConversation(journal, validated.conversationId);
1465
- const rows = yield* journal.loadCheckpoint(validated.conversationId, validated.atOrBeforeSequence ?? conversation.tail_sequence).pipe(Effect.mapError((error) => storeError("load checkpoint", error)));
1431
+ const thread = yield* requireThread(journal, validated.threadId);
1432
+ const rows = yield* journal.loadCheckpoint(validated.threadId, validated.atOrBeforeSequence ?? thread.tail_sequence).pipe(Effect.mapError((error) => storeError("load checkpoint", error)));
1466
1433
  if (rows.length === 0) return Option.none();
1467
- if (rows.length !== 1) return yield* ConversationStoreError.make({
1434
+ if (rows.length !== 1) return yield* ThreadStoreError.make({
1468
1435
  operation: "load checkpoint",
1469
1436
  message: `Expected at most one checkpoint row but found ${rows.length}.`
1470
1437
  });
1471
1438
  const checkpoint = yield* decodeCheckpoint(rows[0].checkpoint_json);
1472
- if ((yield* tailDigestAt(journal, checkpoint.conversationId, checkpoint.throughSequence)) !== checkpoint.tailDigest) return yield* CheckpointRejected.make({
1473
- conversationId: checkpoint.conversationId,
1439
+ if ((yield* tailDigestAt(journal, checkpoint.threadId, checkpoint.throughSequence)) !== checkpoint.tailDigest) return yield* CheckpointRejected.make({
1440
+ threadId: checkpoint.threadId,
1474
1441
  reason: "digest-mismatch"
1475
1442
  });
1476
1443
  return Option.some(checkpoint);
1477
1444
  });
1478
- const conversationStore = ConversationStore.of({
1445
+ const threadStore = ThreadStore.of({
1479
1446
  append,
1480
- export: exportConversation,
1447
+ export: exportThread,
1481
1448
  inspectTail,
1482
- loadCheckpoint,
1483
1449
  materialize,
1484
1450
  observe,
1485
1451
  read,
1486
- saveCheckpoint
1452
+ checkpoints: {
1453
+ save: saveCheckpoint,
1454
+ load: loadCheckpoint
1455
+ }
1487
1456
  });
1488
- return Context.make(ConversationStore, conversationStore);
1457
+ return Context.make(ThreadStore, threadStore);
1489
1458
  });
1490
1459
  /**
1491
- * Durable Object Conversation Store implementation with configuration, failpoint, SQL, and
1460
+ * Durable Object Thread Store implementation with configuration, failpoint, SQL, and
1492
1461
  * Crypto authority kept visible in its input channel.
1493
1462
  */
1494
- const conversationStoreLayer = Layer.effectContext(makeServices$2());
1463
+ const threadStoreLayer = Layer.effectContext(makeServices$2());
1495
1464
  /**
1496
1465
  * Validated Durable Object storage configuration Layer with the documented defaults applied.
1497
- * Shared by the ConversationStore and SubmissionLedger convenience layers so their defaults
1466
+ * Shared by the ThreadStore and SubmissionLedger convenience layers so their defaults
1498
1467
  * cannot drift.
1499
1468
  */
1500
1469
  const storageConfigLayer = (options) => Layer.effect(DoStorageConfig)(Schema.decodeUnknownEffect(DoStorageConfigValue)({
@@ -1510,12 +1479,12 @@ const storageConfigLayer = (options) => Layer.effect(DoStorageConfig)(Schema.dec
1510
1479
  /** The failpoint Layer selected by convenience options: explicit handler or the no-op default. */
1511
1480
  const storageFailpointLayer = (options) => options.failpoint === void 0 ? DoStorageFailpoint.layer : Layer.succeed(DoStorageFailpoint)({ hit: options.failpoint });
1512
1481
  /**
1513
- * A composition-root convenience Layer for canonical Conversations inside one Durable Object,
1482
+ * A composition-root convenience Layer for canonical Threads inside one Durable Object,
1514
1483
  * built over `ctx.storage`. Durable accepted work is served by the separate SubmissionLedger
1515
1484
  * port; point both at the SAME `ctx.storage` so claims fence the same producer epochs
1516
1485
  * (ADR-0011 D7's "same file" rule, transposed to one object's private database).
1517
1486
  */
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)));
1487
+ const layer = (options) => Layer.unwrap(Effect.map(DoStorageConfig, (config) => threadStoreLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(DoStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ storage: options.storage }), BrowserCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
1519
1488
  /** Create an adapter-owned resumable observation offset for a known canonical sequence. */
1520
1489
  const observationOffsetAt = makeOffset;
1521
1490
  //#endregion
@@ -1537,7 +1506,7 @@ const WOKEN = "woken";
1537
1506
  const MAX_IDENTIFIER_LENGTH = 1024;
1538
1507
  var SubmissionRow = class extends Schema.Class("SubmissionRow")({
1539
1508
  submission_id: BoundedIdentifier,
1540
- conversation_id: BoundedIdentifier,
1509
+ thread_id: BoundedIdentifier,
1541
1510
  queue_sequence: QueueSequence,
1542
1511
  principal: BoundedIdentifier,
1543
1512
  idempotency_key: BoundedIdentifier,
@@ -1625,7 +1594,7 @@ var MaxQueueSequenceRow = class extends Schema.Class("MaxQueueSequenceRow")({ ma
1625
1594
  var CanonicalRecordIdRow = class extends Schema.Class("CanonicalRecordIdRow")({ record_id: BoundedIdentifier }) {};
1626
1595
  const SUBMISSION_COLUMNS = `
1627
1596
  submission_id,
1628
- conversation_id,
1597
+ thread_id,
1629
1598
  queue_sequence,
1630
1599
  principal,
1631
1600
  idempotency_key,
@@ -1662,7 +1631,7 @@ const CHILD_RESERVATION_COLUMNS = `
1662
1631
  release_began_at,
1663
1632
  released_at
1664
1633
  `;
1665
- /** The branded ToolCallId schema, reached through the session port so no core import is needed. */
1634
+ /** The branded ToolCallId schema, reached through the thread port so no core import is needed. */
1666
1635
  const ToolCallIdSchema = ApprovalDecisionCommand.fields.toolCallId;
1667
1636
  const ToolCallIdList = Schema.Array(ToolCallIdSchema);
1668
1637
  const encodePersistedJsonText = Schema.encodeEffect(Schema.fromJsonString(PersistedJson));
@@ -1736,7 +1705,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1736
1705
  iso: new Date(millis).toISOString()
1737
1706
  }));
1738
1707
  const timestampMillis = (operation, rowKey) => (timestamp) => decodeUtcInstant(timestamp).pipe(Effect.map(DateTime.toEpochMillis), Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submission_ownership", rowKey, error.message)));
1739
- const decodeSubmissionRows = (operation, rowKey, rows) => decodeRows$1(Schema.Array(SubmissionRow), "effect_agent_submissions", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
1708
+ const decodeSubmissionRows = (operation, rowKey, rows) => decodeRows$2(Schema.Array(SubmissionRow), "effect_agent_submissions", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
1740
1709
  const readSubmission = Effect.fn("DoSubmissionLedger.readSubmission")(function* (operation, submissionId) {
1741
1710
  const rows = yield* sql`
1742
1711
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
@@ -1767,23 +1736,23 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1767
1736
  FROM effect_agent_submission_ownership
1768
1737
  WHERE submission_id = ${submissionId}
1769
1738
  `.pipe(Effect.mapError(sqlFailure(operation)));
1770
- const decoded = yield* decodeRows$1(Schema.Array(OwnershipRow), "effect_agent_submission_ownership", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1739
+ const decoded = yield* decodeRows$2(Schema.Array(OwnershipRow), "effect_agent_submission_ownership", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1771
1740
  if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submission_ownership", submissionId, "An ownership primary key returned more than one row.");
1772
1741
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
1773
1742
  });
1774
- const conversationEpoch = Effect.fn("DoSubmissionLedger.conversationEpoch")(function* (operation, conversationId) {
1775
- const conversations = yield* journal.getConversation(conversationId).pipe(Effect.mapError(internalFailure(operation)));
1776
- return conversations.length === 0 ? EPOCH_ZERO : conversations[0].producer_epoch;
1743
+ const threadEpoch = Effect.fn("DoSubmissionLedger.threadEpoch")(function* (operation, threadId) {
1744
+ const threads = yield* journal.getThread(threadId).pipe(Effect.mapError(internalFailure(operation)));
1745
+ return threads.length === 0 ? EPOCH_ZERO : threads[0].producer_epoch;
1777
1746
  });
1778
1747
  /**
1779
1748
  * Verify inside the surrounding write transaction that the presented token still owns the
1780
1749
  * Submission's lane; a superseded or missing token fails with OwnershipLost carrying the
1781
- * Conversation's current producer epoch (DUR-006).
1750
+ * Thread's current producer epoch (DUR-006).
1782
1751
  */
1783
1752
  const requireOwnership = Effect.fn("DoSubmissionLedger.requireOwnership")(function* (operation, submission, ownershipToken) {
1784
1753
  const ownership = yield* readOwnership(operation, submission.submission_id);
1785
1754
  if (Option.isNone(ownership) || ownership.value.ownership_token !== ownershipToken) {
1786
- const actualEpoch = yield* conversationEpoch(operation, submission.conversation_id);
1755
+ const actualEpoch = yield* threadEpoch(operation, submission.thread_id);
1787
1756
  const submissionId = yield* Schema.decodeUnknownEffect(SubmissionSnapshot.fields.submissionId)(submission.submission_id).pipe(Effect.mapError(internalFailure(operation)));
1788
1757
  return yield* OwnershipLost.make({
1789
1758
  submissionId,
@@ -1798,7 +1767,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1798
1767
  if (row.parent_submission_id === null !== (row.parent_tool_call_id === null)) return yield* corruptionFailure(operation, "effect_agent_submissions", row.submission_id, "A parent linkage must record both the parent Submission and the parent Tool Call.");
1799
1768
  return yield* decodeSubmissionSnapshotUnknown({
1800
1769
  submissionId: row.submission_id,
1801
- conversationId: row.conversation_id,
1770
+ threadId: row.thread_id,
1802
1771
  queueSequence: row.queue_sequence,
1803
1772
  principal: row.principal,
1804
1773
  idempotencyKey: row.idempotency_key,
@@ -1832,7 +1801,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1832
1801
  FROM effect_agent_settlement_reservations
1833
1802
  WHERE submission_id = ${submissionId}
1834
1803
  `.pipe(Effect.mapError(sqlFailure(operation)));
1835
- const decoded = yield* decodeRows$1(Schema.Array(ReservationRow), "effect_agent_settlement_reservations", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1804
+ const decoded = yield* decodeRows$2(Schema.Array(ReservationRow), "effect_agent_settlement_reservations", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1836
1805
  if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_settlement_reservations", submissionId, "A settlement reservation primary key returned more than one row.");
1837
1806
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
1838
1807
  });
@@ -1847,7 +1816,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1847
1816
  FROM effect_agent_abort_intents
1848
1817
  WHERE submission_id = ${submissionId}
1849
1818
  `.pipe(Effect.mapError(sqlFailure(operation)));
1850
- const decoded = yield* decodeRows$1(Schema.Array(AbortIntentRow), "effect_agent_abort_intents", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1819
+ const decoded = yield* decodeRows$2(Schema.Array(AbortIntentRow), "effect_agent_abort_intents", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1851
1820
  if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_abort_intents", submissionId, "An abort intent primary key returned more than one row.");
1852
1821
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
1853
1822
  });
@@ -1867,7 +1836,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1867
1836
  WHERE parent_submission_id = ${parentSubmissionId}
1868
1837
  ORDER BY child_submission_id ASC
1869
1838
  `.pipe(Effect.mapError(sqlFailure(operation)));
1870
- return yield* decodeRows$1(Schema.Array(ChildSettlementMarkerRow), "effect_agent_child_settlements", parentSubmissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1839
+ return yield* decodeRows$2(Schema.Array(ChildSettlementMarkerRow), "effect_agent_child_settlements", parentSubmissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1871
1840
  });
1872
1841
  /**
1873
1842
  * Whether one listed child is provably settled from THIS store: either its own row lives
@@ -1880,7 +1849,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1880
1849
  const childRow = yield* readSubmission(operation, childSubmissionId);
1881
1850
  return Option.isSome(childRow) && childRow.value.state === "settled";
1882
1851
  });
1883
- const decodeChildReservationRows = (operation, rowKey, rows) => decodeRows$1(Schema.Array(ChildReservationRow), "effect_agent_child_reservations", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
1852
+ const decodeChildReservationRows = (operation, rowKey, rows) => decodeRows$2(Schema.Array(ChildReservationRow), "effect_agent_child_reservations", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
1884
1853
  const readChildReservation = Effect.fn("DoSubmissionLedger.readChildReservation")(function* (operation, reservationId) {
1885
1854
  const rows = yield* sql`
1886
1855
  SELECT ${sql.literal(CHILD_RESERVATION_COLUMNS)}
@@ -1933,7 +1902,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1933
1902
  WHERE submission_id = ${submissionId}
1934
1903
  ORDER BY tool_call_id ASC
1935
1904
  `.pipe(Effect.mapError(sqlFailure(operation)));
1936
- return yield* decodeRows$1(Schema.Array(ApprovalDecisionRow), "effect_agent_approval_decisions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1905
+ return yield* decodeRows$2(Schema.Array(ApprovalDecisionRow), "effect_agent_approval_decisions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1937
1906
  });
1938
1907
  const approvalIntentFromRow = Effect.fn("DoSubmissionLedger.approvalIntentFromRow")(function* (operation, row) {
1939
1908
  return yield* decodeApprovalDecisionIntent({
@@ -1958,7 +1927,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1958
1927
  WHERE submission_id = ${submissionId}
1959
1928
  ORDER BY tool_call_id ASC
1960
1929
  `.pipe(Effect.mapError(sqlFailure(operation)));
1961
- return yield* decodeRows$1(Schema.Array(UnknownResolutionRow), "effect_agent_unknown_resolutions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1930
+ return yield* decodeRows$2(Schema.Array(UnknownResolutionRow), "effect_agent_unknown_resolutions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
1962
1931
  });
1963
1932
  const unknownResolutionIntentFromRow = Effect.fn("DoSubmissionLedger.unknownResolutionIntentFromRow")(function* (operation, row) {
1964
1933
  const resolution = yield* parseStoredJsonText(row.resolution_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_unknown_resolutions", `${row.submission_id}/${row.tool_call_id}`, error.message)));
@@ -1981,18 +1950,18 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
1981
1950
  * derived from the shared canonical-records table using the deterministic abort record
1982
1951
  * identity, never from a cached ledger marker.
1983
1952
  */
1984
- const canonicalAbortRecordId = Effect.fn("DoSubmissionLedger.canonicalAbortRecordId")(function* (operation, conversationId, submissionId) {
1953
+ const canonicalAbortRecordId = Effect.fn("DoSubmissionLedger.canonicalAbortRecordId")(function* (operation, threadId, submissionId) {
1985
1954
  const recordId = submissionAbortRecordId(submissionId);
1986
1955
  const rows = yield* sql`
1987
1956
  SELECT record_id
1988
1957
  FROM effect_agent_canonical_records
1989
- WHERE conversation_id = ${conversationId}
1958
+ WHERE thread_id = ${threadId}
1990
1959
  AND record_id = ${recordId}
1991
1960
  `.pipe(Effect.mapError(sqlFailure(operation)));
1992
- return (yield* decodeRows$1(Schema.Array(CanonicalRecordIdRow), "effect_agent_canonical_records", `${conversationId}/${recordId}`, rows).pipe(Effect.mapError(internalFailure(operation)))).length === 0 ? void 0 : recordId;
1961
+ return (yield* decodeRows$2(Schema.Array(CanonicalRecordIdRow), "effect_agent_canonical_records", `${threadId}/${recordId}`, rows).pipe(Effect.mapError(internalFailure(operation)))).length === 0 ? void 0 : recordId;
1993
1962
  });
1994
1963
  const abortIntentFromRow = Effect.fn("DoSubmissionLedger.abortIntentFromRow")(function* (operation, submission, submissionId, row) {
1995
- const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.conversation_id, submissionId);
1964
+ const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.thread_id, submissionId);
1996
1965
  return yield* decodeAbortIntent({
1997
1966
  submissionId: row.submission_id,
1998
1967
  author: row.author,
@@ -2008,19 +1977,19 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2008
1977
  const inputJson = yield* encodePersistedJsonText(validated.inputPayload).pipe(Effect.mapError(internalFailure(operation)));
2009
1978
  yield* journal.checkValueBound(operation, inputJson).pipe(Effect.mapError(internalFailure(operation)));
2010
1979
  const agentDigestsJson = yield* encodeDefinitionDigestsText(validated.agentDigests).pipe(Effect.mapError(internalFailure(operation)));
2011
- const mintedSubmissionId = `${yield* mintUuid(operation)}:${validated.conversationId}`;
1980
+ const mintedSubmissionId = `${yield* mintUuid(operation)}:${validated.threadId}`;
2012
1981
  if (mintedSubmissionId.length > MAX_IDENTIFIER_LENGTH) return yield* LedgerError.make({
2013
1982
  operation,
2014
- message: `A routable Submission identity of ${mintedSubmissionId.length} characters exceeds the ${MAX_IDENTIFIER_LENGTH}-character ledger row bound; shorten the Conversation identity.`
1983
+ message: `A routable Submission identity of ${mintedSubmissionId.length} characters exceeds the ${MAX_IDENTIFIER_LENGTH}-character ledger row bound; shorten the Thread identity.`
2015
1984
  });
2016
1985
  const mintedReceiptId = `receipt-${yield* mintUuid(operation)}`;
2017
1986
  yield* hitFailpoint("ledger:admit:before", operation);
2018
1987
  const result = yield* inWriteTransaction(operation, Effect.gen(function* () {
2019
- const keyRowKey = `${validated.conversationId}/${validated.principal}/${validated.idempotencyKey}`;
1988
+ const keyRowKey = `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`;
2020
1989
  const existingRows = yield* sql`
2021
1990
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
2022
1991
  FROM effect_agent_submissions
2023
- WHERE conversation_id = ${validated.conversationId}
1992
+ WHERE thread_id = ${validated.threadId}
2024
1993
  AND principal = ${validated.principal}
2025
1994
  AND idempotency_key = ${validated.idempotencyKey}
2026
1995
  `.pipe(Effect.mapError(sqlFailure(operation)));
@@ -2029,7 +1998,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2029
1998
  if (existing.length === 1) {
2030
1999
  const sameLinkage = validated.parentLinkage === void 0 ? existing[0].parent_submission_id === null && existing[0].parent_tool_call_id === null : existing[0].parent_submission_id === validated.parentLinkage.parentSubmissionId && existing[0].parent_tool_call_id === validated.parentLinkage.parentToolCallId;
2031
2000
  if (existing[0].input_digest !== validated.inputDigest || !sameLinkage) return yield* AdmissionConflict.make({
2032
- conversationId: validated.conversationId,
2001
+ threadId: validated.threadId,
2033
2002
  principal: validated.principal,
2034
2003
  idempotencyKey: validated.idempotencyKey,
2035
2004
  existingInputDigest: existing[0].input_digest,
@@ -2046,15 +2015,15 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2046
2015
  const maxRows = yield* sql`
2047
2016
  SELECT COALESCE(MAX(queue_sequence), 0) AS max_queue_sequence
2048
2017
  FROM effect_agent_submissions
2049
- WHERE conversation_id = ${validated.conversationId}
2018
+ WHERE thread_id = ${validated.threadId}
2050
2019
  `.pipe(Effect.mapError(sqlFailure(operation)));
2051
- const decodedMax = yield* decodeRows$1(Schema.Array(MaxQueueSequenceRow), "effect_agent_submissions", validated.conversationId, maxRows).pipe(Effect.mapError(internalFailure(operation)));
2020
+ const decodedMax = yield* decodeRows$2(Schema.Array(MaxQueueSequenceRow), "effect_agent_submissions", validated.threadId, maxRows).pipe(Effect.mapError(internalFailure(operation)));
2052
2021
  const queueSequence = yield* decodeQueueSequence((decodedMax[0]?.max_queue_sequence ?? 0) + 1).pipe(Effect.mapError(internalFailure(operation)));
2053
2022
  const now = yield* currentInstant;
2054
2023
  yield* sql`
2055
2024
  INSERT INTO effect_agent_submissions (
2056
2025
  submission_id,
2057
- conversation_id,
2026
+ thread_id,
2058
2027
  queue_sequence,
2059
2028
  principal,
2060
2029
  idempotency_key,
@@ -2070,7 +2039,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2070
2039
  parent_tool_call_id
2071
2040
  ) VALUES (
2072
2041
  ${mintedSubmissionId},
2073
- ${validated.conversationId},
2042
+ ${validated.threadId},
2074
2043
  ${queueSequence},
2075
2044
  ${validated.principal},
2076
2045
  ${validated.idempotencyKey},
@@ -2123,12 +2092,12 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2123
2092
  const rows = yield* sql`
2124
2093
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
2125
2094
  FROM effect_agent_submissions
2126
- WHERE conversation_id = ${validated.conversationId}
2095
+ WHERE thread_id = ${validated.threadId}
2127
2096
  AND principal = ${validated.principal}
2128
2097
  AND idempotency_key = ${validated.idempotencyKey}
2129
2098
  `.pipe(Effect.mapError(sqlFailure(operation)));
2130
- const decoded = yield* decodeSubmissionRows(operation, `${validated.conversationId}/${validated.principal}/${validated.idempotencyKey}`, rows);
2131
- if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", `${validated.conversationId}/${validated.principal}/${validated.idempotencyKey}`, "An admission idempotency key returned more than one row.");
2099
+ const decoded = yield* decodeSubmissionRows(operation, `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, rows);
2100
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, "An admission idempotency key returned more than one row.");
2132
2101
  if (decoded.length === 0) return Option.none();
2133
2102
  return Option.some(yield* decodeSubmissionSnapshot(operation, decoded[0]));
2134
2103
  });
@@ -2138,12 +2107,12 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2138
2107
  const rows = yield* sql`
2139
2108
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
2140
2109
  FROM effect_agent_submissions
2141
- WHERE conversation_id = ${validated.conversationId}
2110
+ WHERE thread_id = ${validated.threadId}
2142
2111
  AND principal = ${validated.principal}
2143
2112
  AND idempotency_key = ${validated.idempotencyKey}
2144
2113
  `.pipe(Effect.mapError(sqlFailure(operation)));
2145
- const decoded = yield* decodeSubmissionRows(operation, `${validated.conversationId}/${validated.principal}/${validated.idempotencyKey}`, rows);
2146
- if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", `${validated.conversationId}/${validated.principal}/${validated.idempotencyKey}`, "An admission idempotency key returned more than one row.");
2114
+ const decoded = yield* decodeSubmissionRows(operation, `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, rows);
2115
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, "An admission idempotency key returned more than one row.");
2147
2116
  if (decoded.length === 0) return AdmissionNotAdmitted.make();
2148
2117
  return AdmissionAdmitted.make({ submission: yield* decodeSubmissionSnapshot(operation, decoded[0]) });
2149
2118
  });
@@ -2157,12 +2126,12 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2157
2126
  const headRows = yield* sql`
2158
2127
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
2159
2128
  FROM effect_agent_submissions
2160
- WHERE conversation_id = ${validated.conversationId}
2129
+ WHERE thread_id = ${validated.threadId}
2161
2130
  AND state <> 'settled'
2162
2131
  ORDER BY queue_sequence ASC
2163
2132
  LIMIT 1
2164
2133
  `.pipe(Effect.mapError(sqlFailure(operation)));
2165
- const heads = yield* decodeSubmissionRows(operation, validated.conversationId, headRows);
2134
+ const heads = yield* decodeSubmissionRows(operation, validated.threadId, headRows);
2166
2135
  if (heads.length === 0) return Option.none();
2167
2136
  const head = heads[0];
2168
2137
  if (head.state === "joining" || head.state === "joined" || head.state === "suspended" || head.state === "unknown" && Option.isNone(yield* readAbortIntent(operation, head.submission_id))) return Option.none();
@@ -2171,19 +2140,19 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2171
2140
  if (Option.isSome(ownership)) {
2172
2141
  if ((yield* timestampMillis(operation, head.submission_id)(ownership.value.lease_expires_at)) > now.millis) return Option.none();
2173
2142
  }
2174
- const conversations = yield* journal.getConversation(head.conversation_id).pipe(Effect.mapError(internalFailure(operation)));
2143
+ const threads = yield* journal.getThread(head.thread_id).pipe(Effect.mapError(internalFailure(operation)));
2175
2144
  let producerEpoch;
2176
- if (conversations.length === 0) {
2145
+ if (threads.length === 0) {
2177
2146
  producerEpoch = 1;
2178
2147
  yield* sql`
2179
- INSERT INTO effect_agent_conversations (
2180
- conversation_id,
2148
+ INSERT INTO effect_agent_threads (
2149
+ thread_id,
2181
2150
  created_at,
2182
2151
  tail_sequence,
2183
2152
  tail_digest,
2184
2153
  producer_epoch
2185
2154
  ) VALUES (
2186
- ${head.conversation_id},
2155
+ ${head.thread_id},
2187
2156
  ${now.iso},
2188
2157
  0,
2189
2158
  ${EMPTY_TAIL_DIGEST},
@@ -2191,11 +2160,11 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2191
2160
  )
2192
2161
  `.pipe(Effect.mapError(sqlFailure(operation)));
2193
2162
  } else {
2194
- producerEpoch = conversations[0].producer_epoch + 1;
2163
+ producerEpoch = threads[0].producer_epoch + 1;
2195
2164
  yield* sql`
2196
- UPDATE effect_agent_conversations
2165
+ UPDATE effect_agent_threads
2197
2166
  SET producer_epoch = ${producerEpoch}
2198
- WHERE conversation_id = ${head.conversation_id}
2167
+ WHERE thread_id = ${head.thread_id}
2199
2168
  `.pipe(Effect.mapError(sqlFailure(operation)));
2200
2169
  }
2201
2170
  const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
@@ -2226,14 +2195,14 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2226
2195
  INSERT INTO effect_agent_attempts (
2227
2196
  attempt_id,
2228
2197
  submission_id,
2229
- conversation_id,
2198
+ thread_id,
2230
2199
  owner_producer_id,
2231
2200
  producer_epoch,
2232
2201
  claimed_at
2233
2202
  ) VALUES (
2234
2203
  ${attemptId},
2235
2204
  ${head.submission_id},
2236
- ${head.conversation_id},
2205
+ ${head.thread_id},
2237
2206
  ${validated.producerId},
2238
2207
  ${producerEpoch},
2239
2208
  ${now.iso}
@@ -2494,7 +2463,7 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2494
2463
  ${now.iso}
2495
2464
  )
2496
2465
  `.pipe(Effect.mapError(sqlFailure(operation)));
2497
- const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.conversation_id, validated.submissionId);
2466
+ const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.thread_id, validated.submissionId);
2498
2467
  return yield* decodeAbortIntent({
2499
2468
  submissionId: validated.submissionId,
2500
2469
  author: validated.author,
@@ -2512,19 +2481,19 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
2512
2481
  yield* hitFailpoint("ledger:claim-joining:before", operation);
2513
2482
  const claims = yield* inWriteTransaction(operation, Effect.gen(function* () {
2514
2483
  const host = yield* requireSubmission(operation, validated.hostSubmissionId);
2515
- if (host.conversation_id !== validated.conversationId) return yield* LedgerError.make({
2484
+ if (host.thread_id !== validated.threadId) return yield* LedgerError.make({
2516
2485
  operation,
2517
- message: `Host submission ${validated.hostSubmissionId} does not belong to conversation ${validated.conversationId}.`
2486
+ message: `Host submission ${validated.hostSubmissionId} does not belong to thread ${validated.threadId}.`
2518
2487
  });
2519
2488
  yield* requireOwnership(operation, host, validated.ownershipToken);
2520
2489
  const laterRows = yield* sql`
2521
2490
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
2522
2491
  FROM effect_agent_submissions
2523
- WHERE conversation_id = ${validated.conversationId}
2492
+ WHERE thread_id = ${validated.threadId}
2524
2493
  AND queue_sequence > ${host.queue_sequence}
2525
2494
  ORDER BY queue_sequence ASC
2526
2495
  `.pipe(Effect.mapError(sqlFailure(operation)));
2527
- const later = yield* decodeSubmissionRows(operation, validated.conversationId, laterRows);
2496
+ const later = yield* decodeSubmissionRows(operation, validated.threadId, laterRows);
2528
2497
  const claimed = [];
2529
2498
  for (const row of later) {
2530
2499
  if (claimed.length >= validated.maxCount) break;
@@ -3034,27 +3003,27 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
3034
3003
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
3035
3004
  FROM effect_agent_submissions
3036
3005
  WHERE state <> 'settled'
3037
- ORDER BY conversation_id ASC, queue_sequence ASC
3006
+ ORDER BY thread_id ASC, queue_sequence ASC
3038
3007
  LIMIT ${SCAN_PAGE_SIZE}
3039
3008
  ` : sql`
3040
3009
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
3041
3010
  FROM effect_agent_submissions
3042
3011
  WHERE state <> 'settled'
3043
3012
  AND (
3044
- conversation_id > ${cursor.conversationId}
3013
+ thread_id > ${cursor.threadId}
3045
3014
  OR (
3046
- conversation_id = ${cursor.conversationId}
3015
+ thread_id = ${cursor.threadId}
3047
3016
  AND queue_sequence > ${cursor.queueSequence}
3048
3017
  )
3049
3018
  )
3050
- ORDER BY conversation_id ASC, queue_sequence ASC
3019
+ ORDER BY thread_id ASC, queue_sequence ASC
3051
3020
  LIMIT ${SCAN_PAGE_SIZE}
3052
3021
  `).pipe(Effect.mapError(sqlFailure(operation)));
3053
3022
  const decoded = yield* decodeSubmissionRows(operation, "nonterminal_scan", rows);
3054
3023
  const snapshots = yield* Effect.forEach(decoded, (row) => decodeSubmissionSnapshot(operation, row));
3055
3024
  const last = decoded[decoded.length - 1];
3056
3025
  return [snapshots, last === void 0 || decoded.length < SCAN_PAGE_SIZE ? Option.none() : Option.some({
3057
- conversationId: last.conversation_id,
3026
+ threadId: last.thread_id,
3058
3027
  queueSequence: last.queue_sequence
3059
3028
  })];
3060
3029
  });
@@ -3211,12 +3180,12 @@ const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* ()
3211
3180
  const submissionLedgerLayer = Layer.effectContext(makeServices$1());
3212
3181
  /**
3213
3182
  * A composition-root convenience Layer for the durable Submission Ledger. Point it at the
3214
- * same `ctx.storage` as the ConversationStore so claims fence the same producer epochs.
3183
+ * same `ctx.storage` as the ThreadStore so claims fence the same producer epochs.
3215
3184
  */
3216
3185
  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)));
3217
3186
  //#endregion
3218
3187
  //#region src/do-schedule-store.ts
3219
- const CURRENT_SCHEDULE_STORE_VERSION = 1;
3188
+ const CURRENT_SCHEDULE_STORE_VERSION = 2;
3220
3189
  const StoredScheduleJson = Schema.String.check(Schema.isMaxLength(19e5));
3221
3190
  const StoredDeadline = Schema.NullOr(ScheduleInstant);
3222
3191
  var ScheduleRow = class extends Schema.Class("@effect-agent/storage-cloudflare/ScheduleRow")({
@@ -3246,26 +3215,26 @@ var ScheduleTableNameRow = class extends Schema.Class("@effect-agent/storage-clo
3246
3215
  var DoScheduleTransaction = class extends Context.Service()("@effect-agent/storage-cloudflare/DoScheduleTransaction") {};
3247
3216
  /** Platform driver operations that use the same schedule-state and alarm transaction. */
3248
3217
  var DoScheduleAlarmControl = class extends Context.Service()("@effect-agent/storage-cloudflare/DoScheduleAlarmControl") {};
3249
- const unavailable = (operation) => ScheduleStorageError.make({
3218
+ const unavailable$1 = (operation) => ScheduleStorageError.make({
3250
3219
  operation,
3251
3220
  reason: "unavailable"
3252
3221
  });
3253
- const corrupt = (operation) => ScheduleStorageError.make({
3222
+ const corrupt$1 = (operation) => ScheduleStorageError.make({
3254
3223
  operation,
3255
3224
  reason: "corrupt"
3256
3225
  });
3257
- const decodeRows = Effect.fn("DoScheduleStore.decodeRows")(function* (schema, rows, operation) {
3258
- return yield* Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError(() => corrupt(operation)));
3226
+ const decodeRows$1 = Effect.fn("DoScheduleStore.decodeRows")(function* (schema, rows, operation) {
3227
+ return yield* Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError(() => corrupt$1(operation)));
3259
3228
  });
3260
- const decodeBoundary = (schema, value, operation) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => corrupt(operation)));
3229
+ const decodeBoundary = (schema, value, operation) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => corrupt$1(operation)));
3261
3230
  const decodeRecord = Effect.fn("DoScheduleStore.decodeRecord")(function* (row) {
3262
- const record = yield* Schema.decodeEffect(Schema.fromJsonString(ScheduleRecord))(row.record_json).pipe(Effect.mapError(() => corrupt("decode schedule")));
3263
- if (record.owner.tenantId !== row.tenant_id || record.owner.ownerId !== row.owner_id || record.scheduleId !== row.schedule_id || scheduleDeadline(record) !== row.deadline_at_millis) return yield* corrupt("decode schedule index");
3231
+ const record = yield* Schema.decodeEffect(Schema.fromJsonString(ScheduleRecord))(row.record_json).pipe(Effect.mapError(() => corrupt$1("decode schedule")));
3232
+ if (record.owner.tenantId !== row.tenant_id || record.owner.ownerId !== row.owner_id || record.scheduleId !== row.schedule_id || scheduleDeadline(record) !== row.deadline_at_millis) return yield* corrupt$1("decode schedule index");
3264
3233
  return record;
3265
3234
  });
3266
3235
  const encodeRecord = Effect.fn("DoScheduleStore.encodeRecord")(function* (record) {
3267
- const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(ScheduleRecord))(record).pipe(Effect.mapError(() => corrupt("encode schedule")));
3268
- return yield* Schema.decodeUnknownEffect(StoredScheduleJson)(encoded).pipe(Effect.mapError(() => corrupt("encode schedule bounds")));
3236
+ const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(ScheduleRecord))(record).pipe(Effect.mapError(() => corrupt$1("encode schedule")));
3237
+ return yield* Schema.decodeUnknownEffect(StoredScheduleJson)(encoded).pipe(Effect.mapError(() => corrupt$1("encode schedule bounds")));
3269
3238
  });
3270
3239
  const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function* () {
3271
3240
  const sql = yield* SqlClient.SqlClient;
@@ -3279,12 +3248,12 @@ const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function
3279
3248
  'effect_agent_schedules'
3280
3249
  )
3281
3250
  ORDER BY name
3282
- `.pipe(Effect.mapError(() => unavailable(operation)));
3283
- const tables = yield* decodeRows(Schema.Array(ScheduleTableNameRow), rawTables, operation);
3251
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3252
+ const tables = yield* decodeRows$1(Schema.Array(ScheduleTableNameRow), rawTables, operation);
3284
3253
  const hasState = tables.some((row) => row.name === "effect_agent_schedule_store_state");
3285
3254
  const hasSchedules = tables.some((row) => row.name === "effect_agent_schedules");
3286
3255
  if (!hasState) {
3287
- if (hasSchedules) return yield* corrupt(operation);
3256
+ if (hasSchedules) return yield* corrupt$1(operation);
3288
3257
  yield* sql.withTransaction(Effect.gen(function* () {
3289
3258
  yield* sql`
3290
3259
  CREATE TABLE effect_agent_schedule_store_state (
@@ -3318,17 +3287,17 @@ const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function
3318
3287
  singleton, storage_version, alarm_generation
3319
3288
  ) VALUES (1, ${CURRENT_SCHEDULE_STORE_VERSION}, 0)
3320
3289
  `.withoutTransform;
3321
- })).pipe(Effect.mapError(() => unavailable(operation)));
3290
+ })).pipe(Effect.mapError(() => unavailable$1(operation)));
3322
3291
  return;
3323
3292
  }
3324
- if (!hasSchedules) return yield* corrupt(operation);
3293
+ if (!hasSchedules) return yield* corrupt$1(operation);
3325
3294
  const rawState = yield* sql`
3326
3295
  SELECT storage_version, alarm_generation
3327
3296
  FROM effect_agent_schedule_store_state
3328
3297
  WHERE singleton = 1
3329
- `.pipe(Effect.mapError(() => unavailable(operation)));
3330
- const state = yield* decodeRows(Schema.Array(ScheduleStoreStateRow), rawState, operation);
3331
- if (state.length !== 1 || state[0].storage_version !== CURRENT_SCHEDULE_STORE_VERSION) return yield* corrupt(operation);
3298
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3299
+ const state = yield* decodeRows$1(Schema.Array(ScheduleStoreStateRow), rawState, operation);
3300
+ if (state.length !== 1 || state[0].storage_version !== CURRENT_SCHEDULE_STORE_VERSION) return yield* corrupt$1(state.length === 1 ? `${operation}: incompatible storage version ${state[0].storage_version}; expected ${CURRENT_SCHEDULE_STORE_VERSION}` : `${operation}: invalid storage version row`);
3332
3301
  });
3333
3302
  const makeServices = Effect.gen(function* () {
3334
3303
  const sql = yield* SqlClient.SqlClient;
@@ -3342,13 +3311,13 @@ const makeServices = Effect.gen(function* () {
3342
3311
  WHERE tenant_id = ${key.owner.tenantId}
3343
3312
  AND owner_id = ${key.owner.ownerId}
3344
3313
  AND schedule_id = ${key.scheduleId}
3345
- `.pipe(Effect.mapError(() => unavailable(operation)));
3346
- return yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
3314
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3315
+ return yield* decodeRows$1(Schema.Array(ScheduleRow), rows, operation);
3347
3316
  });
3348
3317
  const readOne = Effect.fn("DoScheduleStore.readOne")(function* (key, operation) {
3349
3318
  const rows = yield* readRows(key, operation);
3350
3319
  if (rows.length === 0) return null;
3351
- if (rows.length !== 1) return yield* corrupt(operation);
3320
+ if (rows.length !== 1) return yield* corrupt$1(operation);
3352
3321
  return yield* decodeRecord(rows[0]);
3353
3322
  });
3354
3323
  const readNextDeadline = Effect.fn("DoScheduleStore.readNextDeadline")(function* (owner, operation) {
@@ -3356,15 +3325,15 @@ const makeServices = Effect.gen(function* () {
3356
3325
  SELECT MIN(deadline_at_millis) AS deadline_at_millis
3357
3326
  FROM effect_agent_schedules
3358
3327
  WHERE deadline_at_millis IS NOT NULL
3359
- `.pipe(Effect.mapError(() => unavailable(operation))) : yield* sql`
3328
+ `.pipe(Effect.mapError(() => unavailable$1(operation))) : yield* sql`
3360
3329
  SELECT MIN(deadline_at_millis) AS deadline_at_millis
3361
3330
  FROM effect_agent_schedules
3362
3331
  WHERE tenant_id = ${owner.tenantId}
3363
3332
  AND owner_id = ${owner.ownerId}
3364
3333
  AND deadline_at_millis IS NOT NULL
3365
- `.pipe(Effect.mapError(() => unavailable(operation)));
3366
- const decoded = yield* decodeRows(Schema.Array(ScheduleDeadlineRow), rows, operation);
3367
- if (decoded.length !== 1) return yield* corrupt(operation);
3334
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3335
+ const decoded = yield* decodeRows$1(Schema.Array(ScheduleDeadlineRow), rows, operation);
3336
+ if (decoded.length !== 1) return yield* corrupt$1(operation);
3368
3337
  return decoded[0].deadline_at_millis;
3369
3338
  });
3370
3339
  const replaceAlarm = Effect.fn("DoScheduleStore.replaceAlarm")(function* (replace, deadlineAtMillis, operation) {
@@ -3373,9 +3342,9 @@ const makeServices = Effect.gen(function* () {
3373
3342
  SET alarm_generation = alarm_generation + 1
3374
3343
  WHERE singleton = 1
3375
3344
  RETURNING storage_version, alarm_generation
3376
- `.pipe(Effect.mapError(() => unavailable(operation)));
3377
- const state = yield* decodeRows(Schema.Array(ScheduleStoreStateRow), rawState, operation);
3378
- if (state.length !== 1 || state[0].storage_version !== CURRENT_SCHEDULE_STORE_VERSION) return yield* corrupt(operation);
3345
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3346
+ const state = yield* decodeRows$1(Schema.Array(ScheduleStoreStateRow), rawState, operation);
3347
+ if (state.length !== 1 || state[0].storage_version !== CURRENT_SCHEDULE_STORE_VERSION) return yield* corrupt$1(operation);
3379
3348
  yield* scheduleFailpoint.hit("schedule:alarm:before");
3380
3349
  yield* replace({
3381
3350
  deadlineAtMillis,
@@ -3406,9 +3375,9 @@ const makeServices = Effect.gen(function* () {
3406
3375
  AND owner_id = ${canonical.owner.ownerId}
3407
3376
  AND (json_extract(record_json, '$.pending') IS NOT NULL OR
3408
3377
  (json_extract(record_json, '$.state') != 'cancelled' AND json_extract(record_json, '$.nextAtMillis') IS NOT NULL))
3409
- `.pipe(Effect.mapError(() => unavailable(operation)));
3410
- const counts = yield* decodeRows(Schema.Array(ScheduleCountRow), rawCounts, operation);
3411
- if (counts.length !== 1) return yield* corrupt(operation);
3378
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3379
+ const counts = yield* decodeRows$1(Schema.Array(ScheduleCountRow), rawCounts, operation);
3380
+ if (counts.length !== 1) return yield* corrupt$1(operation);
3412
3381
  if (counts[0].schedule_count >= ownerLimit) return yield* ScheduleCapacityError.make({ limit: ownerLimit });
3413
3382
  yield* scheduleFailpoint.hit("schedule:insert:before");
3414
3383
  yield* sql`
@@ -3421,7 +3390,7 @@ const makeServices = Effect.gen(function* () {
3421
3390
  ${scheduleDeadline(canonical)},
3422
3391
  ${recordJson}
3423
3392
  )
3424
- `.pipe(Effect.mapError(() => unavailable(operation)));
3393
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3425
3394
  const deadline = yield* readNextDeadline(void 0, operation);
3426
3395
  yield* replaceAlarm(replace, deadline, operation);
3427
3396
  return {
@@ -3446,7 +3415,7 @@ const makeServices = Effect.gen(function* () {
3446
3415
  AND owner_id = ${request.owner.ownerId}
3447
3416
  ORDER BY schedule_id
3448
3417
  LIMIT ${request.limit + 1}
3449
- `.pipe(Effect.mapError(() => unavailable(operation))) : yield* sql`
3418
+ `.pipe(Effect.mapError(() => unavailable$1(operation))) : yield* sql`
3450
3419
  SELECT tenant_id, owner_id, schedule_id, deadline_at_millis, record_json
3451
3420
  FROM effect_agent_schedules
3452
3421
  WHERE tenant_id = ${request.owner.tenantId}
@@ -3454,8 +3423,8 @@ const makeServices = Effect.gen(function* () {
3454
3423
  AND schedule_id > ${request.after}
3455
3424
  ORDER BY schedule_id
3456
3425
  LIMIT ${request.limit + 1}
3457
- `.pipe(Effect.mapError(() => unavailable(operation)));
3458
- const decoded = yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
3426
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3427
+ const decoded = yield* decodeRows$1(Schema.Array(ScheduleRow), rows, operation);
3459
3428
  const records = yield* Effect.forEach(decoded, decodeRecord);
3460
3429
  const hasNext = records.length > request.limit;
3461
3430
  const items = hasNext ? records.slice(0, request.limit) : records;
@@ -3480,9 +3449,9 @@ const makeServices = Effect.gen(function* () {
3480
3449
  WHERE tenant_id = ${key.owner.tenantId} AND owner_id = ${key.owner.ownerId}
3481
3450
  AND (json_extract(record_json, '$.pending') IS NOT NULL OR
3482
3451
  (json_extract(record_json, '$.state') != 'cancelled' AND json_extract(record_json, '$.nextAtMillis') IS NOT NULL))
3483
- `.pipe(Effect.mapError(() => unavailable(operation)));
3484
- const counts = yield* decodeRows(Schema.Array(ScheduleCountRow), rawCounts, operation);
3485
- if (counts.length !== 1) return yield* corrupt(operation);
3452
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3453
+ const counts = yield* decodeRows$1(Schema.Array(ScheduleCountRow), rawCounts, operation);
3454
+ if (counts.length !== 1) return yield* corrupt$1(operation);
3486
3455
  if (counts[0].schedule_count >= ownerLimit) return yield* ScheduleCapacityError.make({ limit: ownerLimit });
3487
3456
  }
3488
3457
  if (next === current) return {
@@ -3497,7 +3466,7 @@ const makeServices = Effect.gen(function* () {
3497
3466
  WHERE tenant_id = ${canonicalKey.owner.tenantId}
3498
3467
  AND owner_id = ${canonicalKey.owner.ownerId}
3499
3468
  AND schedule_id = ${canonicalKey.scheduleId}
3500
- `.pipe(Effect.mapError(() => unavailable(operation)));
3469
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3501
3470
  const deadline = yield* readNextDeadline(void 0, operation);
3502
3471
  yield* replaceAlarm(replace, deadline, operation);
3503
3472
  return {
@@ -3510,7 +3479,7 @@ const makeServices = Effect.gen(function* () {
3510
3479
  });
3511
3480
  const due = Effect.fn("DoScheduleStore.due")(function* (nowMillis, limit, owner, after) {
3512
3481
  const operation = "query due schedules";
3513
- const cursor = after === void 0 ? void 0 : yield* Schema.decodeUnknownEffect(ScheduleDueCursor)(after).pipe(Effect.mapError(() => corrupt(operation)));
3482
+ const cursor = after === void 0 ? void 0 : yield* Schema.decodeUnknownEffect(ScheduleDueCursor)(after).pipe(Effect.mapError(() => corrupt$1(operation)));
3514
3483
  const continuation = cursor === void 0 ? sql`1 = 1` : sql`
3515
3484
  (deadline_at_millis, tenant_id, owner_id, schedule_id) >
3516
3485
  (${cursor.deadlineAtMillis}, ${cursor.owner.tenantId}, ${cursor.owner.ownerId}, ${cursor.scheduleId})`;
@@ -3520,7 +3489,7 @@ const makeServices = Effect.gen(function* () {
3520
3489
  WHERE deadline_at_millis <= ${nowMillis} AND ${continuation}
3521
3490
  ORDER BY deadline_at_millis, tenant_id, owner_id, schedule_id
3522
3491
  LIMIT ${limit}
3523
- `.pipe(Effect.mapError(() => unavailable(operation))) : yield* sql`
3492
+ `.pipe(Effect.mapError(() => unavailable$1(operation))) : yield* sql`
3524
3493
  SELECT tenant_id, owner_id, schedule_id, deadline_at_millis
3525
3494
  FROM effect_agent_schedules
3526
3495
  WHERE tenant_id = ${owner.tenantId}
@@ -3528,8 +3497,8 @@ const makeServices = Effect.gen(function* () {
3528
3497
  AND deadline_at_millis <= ${nowMillis} AND ${continuation}
3529
3498
  ORDER BY deadline_at_millis, schedule_id
3530
3499
  LIMIT ${limit}
3531
- `.pipe(Effect.mapError(() => unavailable(operation)));
3532
- return (yield* decodeRows(Schema.Array(ScheduleDueRow), rows, operation)).map((row) => ({
3500
+ `.pipe(Effect.mapError(() => unavailable$1(operation)));
3501
+ return (yield* decodeRows$1(Schema.Array(ScheduleDueRow), rows, operation)).map((row) => ({
3533
3502
  owner: {
3534
3503
  tenantId: row.tenant_id,
3535
3504
  ownerId: row.owner_id
@@ -3571,18 +3540,723 @@ const makeServices = Effect.gen(function* () {
3571
3540
  */
3572
3541
  const scheduleStoreLayer = Layer.effectContext(makeServices);
3573
3542
  //#endregion
3543
+ //#region src/do-subscription-store.ts
3544
+ const CURRENT_SUBSCRIPTION_STORE_VERSION = 2;
3545
+ const StoredJson = Schema.String.check(Schema.isMaxLength(19e5));
3546
+ const CountRow = Schema.Struct({ count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) });
3547
+ const SequenceRow = Schema.Struct({ sequence: Schema.Natural });
3548
+ const ScanRow = Schema.Struct({
3549
+ event_scan_cursor: Schema.String,
3550
+ delivery_scan_cursor: Schema.String,
3551
+ recovery_scan_cursor: Schema.Natural
3552
+ });
3553
+ const JsonRow = Schema.Struct({ record_json: StoredJson });
3554
+ const RegistrationRow = Schema.Struct({
3555
+ owner_id: Schema.String,
3556
+ subscription_id: Schema.String,
3557
+ ordinal: Schema.Natural,
3558
+ source_name: Schema.String,
3559
+ source_version: Schema.String,
3560
+ matching_key: Schema.String,
3561
+ state: SubscriptionRecord.fields.state,
3562
+ expires_at_millis: Schema.Number,
3563
+ recovery_at_millis: Schema.NullOr(Schema.Number),
3564
+ record_json: StoredJson
3565
+ });
3566
+ const EventRow = Schema.Struct({
3567
+ event_id: Schema.String,
3568
+ source_name: Schema.String,
3569
+ source_version: Schema.String,
3570
+ matching_key: Schema.String,
3571
+ payload_digest: Digest,
3572
+ cutoff: Schema.Natural,
3573
+ cursor: Schema.Natural,
3574
+ routing_complete: Schema.Number,
3575
+ next_attempt_at_millis: Schema.Number,
3576
+ record_json: StoredJson
3577
+ });
3578
+ const DeliveryRow = Schema.Struct({
3579
+ owner_id: Schema.String,
3580
+ subscription_id: Schema.String,
3581
+ event_id: Schema.String,
3582
+ delivery_key: Schema.String,
3583
+ state: SubscriptionDelivery.fields.state,
3584
+ next_attempt_at_millis: Schema.Number,
3585
+ record_json: StoredJson
3586
+ });
3587
+ const StoreStateRow = Schema.Struct({
3588
+ storage_version: Schema.Int,
3589
+ alarm_generation: Schema.Natural
3590
+ });
3591
+ /** One Durable Object transaction combining subscription SQL and its single native alarm. */
3592
+ var DoSubscriptionTransaction = class extends Context.Service()("@effect-agent/storage-cloudflare/DoSubscriptionTransaction") {};
3593
+ var DoSubscriptionAlarmControl = class extends Context.Service()("@effect-agent/storage-cloudflare/DoSubscriptionAlarmControl") {};
3594
+ const error = (reason, code) => SubscriptionError.make({
3595
+ reason,
3596
+ code
3597
+ });
3598
+ const unavailable = (operation) => error("storage", operation);
3599
+ const corrupt = (operation) => error("corrupt", operation);
3600
+ const bytes = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
3601
+ const validate = (schema, value, code) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => error("validation", code)));
3602
+ const encode = (schema, value, code) => Schema.encodeEffect(Schema.fromJsonString(schema))(value).pipe(Effect.mapError(() => corrupt(code)));
3603
+ const decode = (schema, value, code) => Schema.decodeEffect(Schema.fromJsonString(schema))(value).pipe(Effect.mapError(() => corrupt(code)));
3604
+ const decodeRows = (schema, rows, code) => Schema.decodeUnknownEffect(Schema.Array(schema))(rows).pipe(Effect.mapError(() => corrupt(code)));
3605
+ const sameDeliveryIdentity = (left, right) => subscriptionDeliveryKeyString(left.key) === subscriptionDeliveryKeyString(right.key) && left.deliveryId === right.deliveryId && left.source.name === right.source.name && left.source.version === right.source.version && left.threadId === right.threadId && left.admissionKey === right.admissionKey && left.subscriptionFingerprint === right.subscriptionFingerprint && left.eventDigest === right.eventDigest;
3606
+ const initializeDoSubscriptionStore = Effect.fn("DoSubscriptionStore.initialize")(function* () {
3607
+ const sql = yield* SqlClient.SqlClient;
3608
+ const names = yield* sql`
3609
+ SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'effect_agent_subscription_%'
3610
+ `.pipe(Effect.mapError(() => unavailable("inspect subscription storage")));
3611
+ const decodedNames = yield* decodeRows(Schema.Struct({ name: Schema.String }), names, "inspect subscription storage");
3612
+ const expected = /* @__PURE__ */ new Set([
3613
+ "effect_agent_subscription_store_state",
3614
+ "effect_agent_subscription_sequences",
3615
+ "effect_agent_subscriptions",
3616
+ "effect_agent_subscription_events",
3617
+ "effect_agent_subscription_deliveries"
3618
+ ]);
3619
+ const hasState = decodedNames.some(({ name }) => name === "effect_agent_subscription_store_state");
3620
+ if (!hasState && decodedNames.length > 0) return yield* corrupt("partial subscription storage");
3621
+ if (!hasState) {
3622
+ yield* sql.withTransaction(Effect.gen(function* () {
3623
+ yield* sql`CREATE TABLE effect_agent_subscription_store_state (
3624
+ singleton INTEGER PRIMARY KEY NOT NULL CHECK(singleton=1), storage_version INTEGER NOT NULL, alarm_generation INTEGER NOT NULL
3625
+ )`.withoutTransform;
3626
+ yield* sql`CREATE TABLE effect_agent_subscription_sequences (
3627
+ tenant_id TEXT NOT NULL, source_address TEXT NOT NULL, sequence INTEGER NOT NULL,
3628
+ event_scan_cursor TEXT NOT NULL, delivery_scan_cursor TEXT NOT NULL, recovery_scan_cursor INTEGER NOT NULL,
3629
+ PRIMARY KEY (tenant_id, source_address)
3630
+ )`.withoutTransform;
3631
+ yield* sql`CREATE TABLE effect_agent_subscriptions (
3632
+ tenant_id TEXT NOT NULL, source_address TEXT NOT NULL, owner_id TEXT NOT NULL, subscription_id TEXT NOT NULL,
3633
+ ordinal INTEGER NOT NULL, source_name TEXT NOT NULL, source_version TEXT NOT NULL, matching_key TEXT NOT NULL,
3634
+ state TEXT NOT NULL, expires_at_millis INTEGER NOT NULL, recovery_at_millis INTEGER, record_json TEXT NOT NULL,
3635
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id), UNIQUE (tenant_id, source_address, ordinal)
3636
+ )`.withoutTransform;
3637
+ yield* sql`CREATE INDEX effect_agent_subscriptions_owner ON effect_agent_subscriptions (tenant_id, source_address, owner_id, ordinal)`.withoutTransform;
3638
+ yield* sql`CREATE INDEX effect_agent_subscriptions_candidates ON effect_agent_subscriptions (tenant_id, source_address, source_name, source_version, matching_key, ordinal)`.withoutTransform;
3639
+ yield* sql`CREATE INDEX effect_agent_subscriptions_recovery ON effect_agent_subscriptions (tenant_id, source_address, recovery_at_millis, ordinal) WHERE recovery_at_millis IS NOT NULL`.withoutTransform;
3640
+ yield* sql`CREATE TABLE effect_agent_subscription_events (
3641
+ tenant_id TEXT NOT NULL, source_address TEXT NOT NULL, event_id TEXT NOT NULL, source_name TEXT NOT NULL,
3642
+ source_version TEXT NOT NULL, matching_key TEXT NOT NULL, payload_digest TEXT NOT NULL, cutoff INTEGER NOT NULL,
3643
+ cursor INTEGER NOT NULL, routing_complete INTEGER NOT NULL, next_attempt_at_millis INTEGER NOT NULL, record_json TEXT NOT NULL,
3644
+ PRIMARY KEY (tenant_id, source_address, event_id)
3645
+ )`.withoutTransform;
3646
+ yield* sql`CREATE INDEX effect_agent_subscription_events_pending ON effect_agent_subscription_events (tenant_id, source_address, routing_complete, next_attempt_at_millis, event_id)`.withoutTransform;
3647
+ yield* sql`CREATE TABLE effect_agent_subscription_deliveries (
3648
+ tenant_id TEXT NOT NULL, source_address TEXT NOT NULL, owner_id TEXT NOT NULL, subscription_id TEXT NOT NULL,
3649
+ event_id TEXT NOT NULL, delivery_key TEXT NOT NULL, state TEXT NOT NULL, next_attempt_at_millis INTEGER NOT NULL,
3650
+ record_json TEXT NOT NULL, PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id, event_id),
3651
+ UNIQUE (tenant_id, source_address, delivery_key)
3652
+ )`.withoutTransform;
3653
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_pending ON effect_agent_subscription_deliveries (tenant_id, source_address, state, next_attempt_at_millis, delivery_key)`.withoutTransform;
3654
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_registration ON effect_agent_subscription_deliveries (tenant_id, source_address, owner_id, subscription_id, delivery_key)`.withoutTransform;
3655
+ yield* sql`INSERT INTO effect_agent_subscription_store_state (singleton, storage_version, alarm_generation)
3656
+ VALUES (1, ${CURRENT_SUBSCRIPTION_STORE_VERSION}, 0)`.withoutTransform;
3657
+ })).pipe(Effect.mapError(() => unavailable("initialize subscription storage")));
3658
+ return;
3659
+ }
3660
+ if (decodedNames.length !== expected.size || decodedNames.some(({ name }) => !expected.has(name))) return yield* corrupt("subscription storage tables");
3661
+ const rows = yield* sql`SELECT storage_version, alarm_generation FROM effect_agent_subscription_store_state WHERE singleton=1`.pipe(Effect.mapError(() => unavailable("read subscription storage version")));
3662
+ const state = yield* decodeRows(StoreStateRow, rows, "read subscription storage version");
3663
+ if (state.length !== 1 || state[0].storage_version !== CURRENT_SUBSCRIPTION_STORE_VERSION) return yield* corrupt(state.length === 1 ? `incompatible subscription storage version ${state[0].storage_version}; expected ${CURRENT_SUBSCRIPTION_STORE_VERSION}` : "invalid subscription storage version row");
3664
+ });
3665
+ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function* (owned) {
3666
+ const partition = yield* validate(SourcePartition, owned, "partition");
3667
+ const sql = yield* SqlClient.SqlClient;
3668
+ const failpoint = yield* SubscriptionFailpoint;
3669
+ const transactions = yield* DoSubscriptionTransaction;
3670
+ yield* initializeDoSubscriptionStore();
3671
+ yield* sql`
3672
+ INSERT INTO effect_agent_subscription_sequences (
3673
+ tenant_id, source_address, sequence, event_scan_cursor, delivery_scan_cursor, recovery_scan_cursor
3674
+ ) VALUES (${partition.tenantId}, ${partition.address}, 0, '', '', 0) ON CONFLICT DO NOTHING
3675
+ `.pipe(Effect.mapError(() => unavailable("initialize subscription partition")));
3676
+ const query = (effect, code) => effect.pipe(Effect.mapError(() => unavailable(code)));
3677
+ const readIndexedDeadline = Effect.fn("DoSubscriptionStore.readIndexedDeadline")(function* () {
3678
+ const scanRows = yield* query(sql`
3679
+ SELECT event_scan_cursor, delivery_scan_cursor, recovery_scan_cursor FROM effect_agent_subscription_sequences
3680
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3681
+ `, "read scan cursor deadline");
3682
+ const scans = yield* decodeRows(ScanRow, scanRows, "read scan cursor deadline");
3683
+ if (scans.length !== 1) return yield* corrupt("scan cursor deadline");
3684
+ if (scans[0].event_scan_cursor !== "" || scans[0].delivery_scan_cursor !== "" || scans[0].recovery_scan_cursor !== 0) return 0;
3685
+ const rows = yield* query(sql`
3686
+ SELECT MIN(deadline) AS deadline FROM (
3687
+ SELECT next_attempt_at_millis AS deadline FROM effect_agent_subscription_events
3688
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND routing_complete=0
3689
+ UNION ALL SELECT next_attempt_at_millis FROM effect_agent_subscription_deliveries
3690
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND state NOT IN ('delivered','refused')
3691
+ UNION ALL SELECT recovery_at_millis FROM effect_agent_subscriptions
3692
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND state='active' AND recovery_at_millis IS NOT NULL
3693
+ )
3694
+ `, "read subscription deadline");
3695
+ const decoded = yield* decodeRows(Schema.Struct({ deadline: Schema.NullOr(Schema.Number) }), rows, "read subscription deadline");
3696
+ if (decoded.length !== 1) return yield* corrupt("subscription deadline");
3697
+ return decoded[0].deadline;
3698
+ });
3699
+ const replaceAlarm = Effect.fn("DoSubscriptionStore.replaceAlarm")(function* (replace, deadlineAtMillis) {
3700
+ const rows = yield* query(sql`
3701
+ UPDATE effect_agent_subscription_store_state SET alarm_generation=alarm_generation+1
3702
+ WHERE singleton=1 RETURNING storage_version, alarm_generation
3703
+ `, "advance subscription alarm generation");
3704
+ const state = yield* decodeRows(StoreStateRow, rows, "advance subscription alarm generation");
3705
+ if (state.length !== 1 || state[0].storage_version !== CURRENT_SUBSCRIPTION_STORE_VERSION) return yield* corrupt("subscription alarm state");
3706
+ yield* failpoint.hit("subscription:alarm:before");
3707
+ yield* replace({
3708
+ deadlineAtMillis,
3709
+ generation: state[0].alarm_generation
3710
+ });
3711
+ yield* failpoint.hit("subscription:alarm:after");
3712
+ });
3713
+ const transact = (effect) => transactions.run((replace) => Effect.gen(function* () {
3714
+ const value = yield* effect;
3715
+ yield* replaceAlarm(replace, yield* readIndexedDeadline());
3716
+ return value;
3717
+ }));
3718
+ const requirePartition = (candidate, code) => sameSourcePartition(candidate, partition) ? Effect.void : Effect.fail(error("validation", code));
3719
+ const requireKey = Effect.fn("SqliteSubscriptionStore.requireKey")(function* (input, code) {
3720
+ const key = yield* validate(SubscriptionKey, input, code);
3721
+ yield* requirePartition(key.partition, code);
3722
+ return key;
3723
+ });
3724
+ const readRegistration = Effect.fn("SqliteSubscriptionStore.readRegistration")(function* (key, code) {
3725
+ const rows = yield* query(sql`
3726
+ SELECT owner_id, subscription_id, ordinal, source_name, source_version, matching_key, state,
3727
+ expires_at_millis, recovery_at_millis, record_json FROM effect_agent_subscriptions
3728
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3729
+ AND owner_id=${key.ownerId} AND subscription_id=${key.subscriptionId}
3730
+ `, code);
3731
+ const decodedRows = yield* decodeRows(RegistrationRow, rows, code);
3732
+ if (decodedRows.length > 1) return yield* corrupt(code);
3733
+ const row = decodedRows[0];
3734
+ if (row === void 0) return null;
3735
+ const record = yield* decode(SubscriptionRecord, row.record_json, code);
3736
+ if (!sameSourcePartition(record.key.partition, partition) || record.key.ownerId !== row.owner_id || record.key.subscriptionId !== row.subscription_id || record.ordinal !== row.ordinal || record.configuration.source.name !== row.source_name || record.configuration.source.version !== row.source_version || record.configuration.matchingKey !== row.matching_key || record.state !== row.state || record.configuration.expiresAtMillis !== row.expires_at_millis || (record.recovery?.nextAttemptAtMillis ?? null) !== row.recovery_at_millis) return yield* corrupt(`${code}-projection`);
3737
+ return record;
3738
+ });
3739
+ const readEvent = Effect.fn("SqliteSubscriptionStore.readEvent")(function* (eventId, code) {
3740
+ const rows = yield* query(sql`
3741
+ SELECT event_id, source_name, source_version, matching_key, payload_digest, cutoff, cursor,
3742
+ routing_complete, next_attempt_at_millis, record_json FROM effect_agent_subscription_events
3743
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND event_id=${eventId}
3744
+ `, code);
3745
+ const decodedRows = yield* decodeRows(EventRow, rows, code);
3746
+ if (decodedRows.length > 1) return yield* corrupt(code);
3747
+ const row = decodedRows[0];
3748
+ if (row === void 0) return null;
3749
+ const event = yield* decode(AcceptedEvent, row.record_json, code);
3750
+ if (!sameSourcePartition(event.partition, partition) || event.eventId !== row.event_id || event.source.name !== row.source_name || event.source.version !== row.source_version || event.matchingKey !== row.matching_key || event.payloadDigest !== row.payload_digest || event.cutoff !== row.cutoff || event.cursor !== row.cursor || (event.routingComplete ? 1 : 0) !== row.routing_complete || event.nextAttemptAtMillis !== row.next_attempt_at_millis) return yield* corrupt(`${code}-projection`);
3751
+ return event;
3752
+ });
3753
+ const readDelivery = Effect.fn("SqliteSubscriptionStore.readDelivery")(function* (key, code) {
3754
+ const rows = yield* query(sql`
3755
+ SELECT owner_id, subscription_id, event_id, delivery_key, state, next_attempt_at_millis, record_json
3756
+ FROM effect_agent_subscription_deliveries
3757
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3758
+ AND owner_id=${key.subscription.ownerId} AND subscription_id=${key.subscription.subscriptionId}
3759
+ AND event_id=${key.eventId}
3760
+ `, code);
3761
+ const decodedRows = yield* decodeRows(DeliveryRow, rows, code);
3762
+ if (decodedRows.length > 1) return yield* corrupt(code);
3763
+ const row = decodedRows[0];
3764
+ if (row === void 0) return null;
3765
+ const delivery = yield* decode(SubscriptionDelivery, row.record_json, code);
3766
+ if (!sameSourcePartition(delivery.key.subscription.partition, partition) || delivery.key.subscription.ownerId !== row.owner_id || delivery.key.subscription.subscriptionId !== row.subscription_id || delivery.key.eventId !== row.event_id || subscriptionDeliveryKeyString(delivery.key) !== row.delivery_key || delivery.state !== row.state || delivery.retry.nextAttemptAtMillis !== row.next_attempt_at_millis) return yield* corrupt(`${code}-projection`);
3767
+ return delivery;
3768
+ });
3769
+ const count = Effect.fn("SqliteSubscriptionStore.count")(function* (statement, code) {
3770
+ const rows = yield* query(statement, code);
3771
+ const decoded = yield* decodeRows(CountRow, rows, code);
3772
+ if (decoded.length !== 1) return yield* corrupt(code);
3773
+ return decoded[0].count;
3774
+ });
3775
+ const nextSequence = Effect.fn("SqliteSubscriptionStore.nextSequence")(function* () {
3776
+ const rows = yield* query(sql`
3777
+ UPDATE effect_agent_subscription_sequences SET sequence=sequence+1
3778
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3779
+ RETURNING sequence
3780
+ `, "advance subscription sequence");
3781
+ const decoded = yield* decodeRows(SequenceRow, rows, "advance subscription sequence");
3782
+ if (decoded.length !== 1) return yield* corrupt("subscription sequence");
3783
+ return decoded[0].sequence;
3784
+ });
3785
+ const writeRegistration = Effect.fn("SqliteSubscriptionStore.writeRegistration")(function* (record) {
3786
+ const json = yield* encode(SubscriptionRecord, record, "encode subscription");
3787
+ yield* query(sql`
3788
+ UPDATE effect_agent_subscriptions SET state=${record.state}, expires_at_millis=${record.configuration.expiresAtMillis},
3789
+ recovery_at_millis=${record.recovery?.nextAttemptAtMillis ?? null}, record_json=${json}
3790
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3791
+ AND owner_id=${record.key.ownerId} AND subscription_id=${record.key.subscriptionId}
3792
+ `, "write subscription");
3793
+ });
3794
+ const writeEvent = Effect.fn("SqliteSubscriptionStore.writeEvent")(function* (event) {
3795
+ const json = yield* encode(AcceptedEvent, event, "encode event");
3796
+ yield* query(sql`
3797
+ UPDATE effect_agent_subscription_events SET cursor=${event.cursor}, routing_complete=${event.routingComplete ? 1 : 0},
3798
+ next_attempt_at_millis=${event.nextAttemptAtMillis}, record_json=${json}
3799
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND event_id=${event.eventId}
3800
+ `, "write event");
3801
+ });
3802
+ const writeDelivery = Effect.fn("SqliteSubscriptionStore.writeDelivery")(function* (delivery) {
3803
+ const json = yield* encode(SubscriptionDelivery, delivery, "encode delivery");
3804
+ yield* query(sql`
3805
+ UPDATE effect_agent_subscription_deliveries SET state=${delivery.state},
3806
+ next_attempt_at_millis=${delivery.retry.nextAttemptAtMillis}, record_json=${json}
3807
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3808
+ AND owner_id=${delivery.key.subscription.ownerId} AND subscription_id=${delivery.key.subscription.subscriptionId}
3809
+ AND event_id=${delivery.key.eventId}
3810
+ `, "write delivery");
3811
+ });
3812
+ const register = Effect.fn("SqliteSubscriptionStore.register")(function* (input, inputLimits) {
3813
+ const record = yield* validate(SubscriptionRecord, input, "register-record");
3814
+ const limits = yield* validate(SubscriptionLimits, inputLimits, "register-limits");
3815
+ yield* requirePartition(record.key.partition, "register-partition");
3816
+ const result = yield* transact(Effect.gen(function* () {
3817
+ const existing = yield* readRegistration(record.key, "register-existing");
3818
+ if (existing !== null) {
3819
+ if (existing.creationFingerprint !== record.creationFingerprint) return yield* error("conflict", "registration-identity");
3820
+ return {
3821
+ value: existing,
3822
+ changed: false
3823
+ };
3824
+ }
3825
+ if (bytes(record.configuration.context) > limits.maxContextBytes) return yield* error("capacity", "context-bytes");
3826
+ if (bytes(record.configuration.parameters) > limits.maxPayloadBytes) return yield* error("capacity", "parameters-bytes");
3827
+ if (record.configuration.expiresAtMillis - record.createdAtMillis > limits.maxLifetimeMillis) return yield* error("capacity", "lifetime");
3828
+ if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscriptions WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}`, "count registrations")) >= limits.maxRegistrations) return yield* error("capacity", "registrations");
3829
+ if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscriptions WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND owner_id=${record.key.ownerId}`, "count owner registrations")) >= limits.maxRegistrationsPerOwner) return yield* error("capacity", "owner-registrations");
3830
+ const assigned = {
3831
+ ...record,
3832
+ ordinal: yield* nextSequence()
3833
+ };
3834
+ const json = yield* encode(SubscriptionRecord, assigned, "encode registration");
3835
+ yield* failpoint.hit("subscription:register:before");
3836
+ yield* query(sql`
3837
+ INSERT INTO effect_agent_subscriptions (tenant_id, source_address, owner_id, subscription_id, ordinal,
3838
+ source_name, source_version, matching_key, state, expires_at_millis, recovery_at_millis, record_json)
3839
+ VALUES (${partition.tenantId}, ${partition.address}, ${assigned.key.ownerId}, ${assigned.key.subscriptionId}, ${assigned.ordinal},
3840
+ ${assigned.configuration.source.name}, ${assigned.configuration.source.version}, ${assigned.configuration.matchingKey}, ${assigned.state},
3841
+ ${assigned.configuration.expiresAtMillis}, ${assigned.recovery?.nextAttemptAtMillis ?? null}, ${json})
3842
+ `, "insert registration");
3843
+ return {
3844
+ value: assigned,
3845
+ changed: true
3846
+ };
3847
+ }));
3848
+ if (result.changed) yield* failpoint.hit("subscription:register:after");
3849
+ return result.value;
3850
+ });
3851
+ const get = Effect.fn("SqliteSubscriptionStore.get")(function* (input) {
3852
+ return yield* readRegistration(yield* requireKey(input, "get-key"), "get subscription");
3853
+ });
3854
+ const list = Effect.fn("SqliteSubscriptionStore.list")(function* (ownerId, after, limit) {
3855
+ const rows = yield* query(sql`
3856
+ SELECT owner_id, subscription_id, ordinal FROM effect_agent_subscriptions WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3857
+ AND owner_id=${ownerId} AND ordinal>${after} ORDER BY ordinal LIMIT ${limit}
3858
+ `, "list subscriptions");
3859
+ const decoded = yield* decodeRows(Schema.Struct({
3860
+ owner_id: Schema.String,
3861
+ subscription_id: Schema.String,
3862
+ ordinal: Schema.Natural
3863
+ }), rows, "list subscriptions");
3864
+ return yield* Effect.forEach(decoded, Effect.fn("SqliteSubscriptionStore.listRecord")(function* (row) {
3865
+ const record = yield* readRegistration({
3866
+ partition,
3867
+ ownerId: row.owner_id,
3868
+ subscriptionId: row.subscription_id
3869
+ }, "list subscription");
3870
+ if (record === null || record.ordinal !== row.ordinal) return yield* corrupt("list subscription projection");
3871
+ return record;
3872
+ }));
3873
+ });
3874
+ const cancel = Effect.fn("SqliteSubscriptionStore.cancel")(function* (input) {
3875
+ const key = yield* requireKey(input, "cancel-key");
3876
+ const result = yield* transact(Effect.gen(function* () {
3877
+ const current = yield* readRegistration(key, "cancel subscription");
3878
+ if (current === null) return yield* error("not-found", "subscription");
3879
+ if (current.state === "cancelled") return {
3880
+ value: current,
3881
+ changed: false
3882
+ };
3883
+ const updated = {
3884
+ ...current,
3885
+ state: "cancelled",
3886
+ recovery: null
3887
+ };
3888
+ yield* failpoint.hit("subscription:cancel:before");
3889
+ yield* writeRegistration(updated);
3890
+ return {
3891
+ value: updated,
3892
+ changed: true
3893
+ };
3894
+ }));
3895
+ if (result.changed) yield* failpoint.hit("subscription:cancel:after");
3896
+ return result.value;
3897
+ });
3898
+ const accept = Effect.fn("SqliteSubscriptionStore.accept")(function* (input, inputLimits) {
3899
+ const event = yield* validate(AcceptedEvent, input, "accept-event");
3900
+ const limits = yield* validate(SubscriptionLimits, inputLimits, "accept-limits");
3901
+ yield* requirePartition(event.partition, "accept-partition");
3902
+ const result = yield* transact(Effect.gen(function* () {
3903
+ const existing = yield* readEvent(event.eventId, "accept event");
3904
+ if (existing !== null) {
3905
+ if (!sameAcceptedEventIdentity(existing, event)) return yield* error("conflict", "event-identity");
3906
+ return {
3907
+ value: existing,
3908
+ changed: false
3909
+ };
3910
+ }
3911
+ if (bytes(event.payload) > limits.maxPayloadBytes) return yield* error("capacity", "payload-bytes");
3912
+ if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscription_events WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}`, "count events")) >= limits.maxEvents) return yield* error("capacity", "events");
3913
+ const accepted = {
3914
+ ...event,
3915
+ cutoff: yield* nextSequence(),
3916
+ cursor: 0,
3917
+ routingComplete: false,
3918
+ routingFailure: null
3919
+ };
3920
+ const json = yield* encode(AcceptedEvent, accepted, "encode accepted event");
3921
+ yield* failpoint.hit("subscription:accept:before");
3922
+ yield* query(sql`
3923
+ INSERT INTO effect_agent_subscription_events (tenant_id, source_address, event_id, source_name, source_version,
3924
+ matching_key, payload_digest, cutoff, cursor, routing_complete, next_attempt_at_millis, record_json)
3925
+ VALUES (${partition.tenantId}, ${partition.address}, ${accepted.eventId}, ${accepted.source.name}, ${accepted.source.version},
3926
+ ${accepted.matchingKey}, ${accepted.payloadDigest}, ${accepted.cutoff}, ${accepted.cursor}, 0, ${accepted.nextAttemptAtMillis}, ${json})
3927
+ `, "insert event");
3928
+ return {
3929
+ value: accepted,
3930
+ changed: true
3931
+ };
3932
+ }));
3933
+ if (result.changed) yield* failpoint.hit("subscription:accept:after");
3934
+ return result.value;
3935
+ });
3936
+ const event = Effect.fn("SqliteSubscriptionStore.event")((eventId) => readEvent(eventId, "get event"));
3937
+ const pendingEvents = Effect.fn("SqliteSubscriptionStore.pendingEvents")(function* (nowMillis, after, limit) {
3938
+ const rows = yield* query(sql`
3939
+ SELECT event_id FROM effect_agent_subscription_events WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3940
+ AND routing_complete=0 AND next_attempt_at_millis<=${nowMillis} AND event_id>${after} ORDER BY event_id LIMIT ${limit}
3941
+ `, "pending events");
3942
+ return yield* decodeRows(Schema.Struct({ event_id: Schema.String }), rows, "pending event keys").pipe(Effect.map((items) => items.map((item) => item.event_id)));
3943
+ });
3944
+ const candidates = Effect.fn("SqliteSubscriptionStore.candidates")(function* (input, limit) {
3945
+ const supplied = yield* validate(AcceptedEvent, input, "candidates-event");
3946
+ yield* requirePartition(supplied.partition, "candidates-partition");
3947
+ const stored = yield* readEvent(supplied.eventId, "candidates event");
3948
+ if (stored === null) return yield* error("not-found", "event");
3949
+ if (!sameAcceptedEventIdentity(stored, supplied)) return yield* error("conflict", "event");
3950
+ const rows = yield* query(sql`
3951
+ SELECT owner_id, subscription_id, ordinal FROM effect_agent_subscriptions WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
3952
+ AND source_name=${stored.source.name} AND source_version=${stored.source.version} AND matching_key=${stored.matchingKey}
3953
+ AND ordinal>${stored.cursor} AND ordinal<=${stored.cutoff} ORDER BY ordinal LIMIT ${limit}
3954
+ `, "subscription candidates");
3955
+ const decoded = yield* decodeRows(Schema.Struct({
3956
+ owner_id: Schema.String,
3957
+ subscription_id: Schema.String,
3958
+ ordinal: Schema.Natural
3959
+ }), rows, "subscription candidates");
3960
+ return yield* Effect.forEach(decoded, Effect.fn("SqliteSubscriptionStore.candidateRecord")(function* (row) {
3961
+ const record = yield* readRegistration({
3962
+ partition,
3963
+ ownerId: row.owner_id,
3964
+ subscriptionId: row.subscription_id
3965
+ }, "subscription candidate");
3966
+ if (record === null || record.ordinal !== row.ordinal) return yield* corrupt("subscription candidate projection");
3967
+ return record;
3968
+ }));
3969
+ });
3970
+ const insertDelivery = Effect.fn("SqliteSubscriptionStore.insertDelivery")(function* (delivery) {
3971
+ const json = yield* encode(SubscriptionDelivery, delivery, "encode selected delivery");
3972
+ yield* query(sql`
3973
+ INSERT INTO effect_agent_subscription_deliveries (tenant_id, source_address, owner_id, subscription_id, event_id,
3974
+ delivery_key, state, next_attempt_at_millis, record_json)
3975
+ VALUES (${partition.tenantId}, ${partition.address}, ${delivery.key.subscription.ownerId}, ${delivery.key.subscription.subscriptionId},
3976
+ ${delivery.key.eventId}, ${subscriptionDeliveryKeyString(delivery.key)}, ${delivery.state}, ${delivery.retry.nextAttemptAtMillis}, ${json})
3977
+ `, "insert delivery");
3978
+ });
3979
+ const select = Effect.fn("SqliteSubscriptionStore.select")(function* (inputEvent, inputDeliveries, cursor, complete, nowMillis, inputLimits) {
3980
+ const supplied = yield* validate(AcceptedEvent, inputEvent, "select-event");
3981
+ const deliveries = yield* validate(Schema.Array(SubscriptionDelivery), inputDeliveries, "select-deliveries");
3982
+ const limits = yield* validate(SubscriptionLimits, inputLimits, "select-limits");
3983
+ yield* requirePartition(supplied.partition, "select-partition");
3984
+ for (const candidate of deliveries) yield* requirePartition(candidate.key.subscription.partition, "select-delivery-partition");
3985
+ if (yield* transact(Effect.gen(function* () {
3986
+ const accepted = yield* readEvent(supplied.eventId, "select event");
3987
+ if (accepted === null) return yield* error("not-found", "event");
3988
+ if (!sameAcceptedEventIdentity(accepted, supplied) || accepted.cursor !== supplied.cursor) return yield* error("conflict", "event-cursor");
3989
+ if (accepted.routingComplete) return false;
3990
+ if (!Number.isSafeInteger(cursor) || cursor < accepted.cursor || cursor > accepted.cutoff) return yield* error("validation", "cursor");
3991
+ yield* failpoint.hit("subscription:select:before");
3992
+ const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
3993
+ const additions = [];
3994
+ for (const delivery of deliveries) {
3995
+ const record = yield* readRegistration(delivery.key.subscription, "select registration");
3996
+ if (record === null) return yield* error("not-found", "subscription");
3997
+ if (!subscriptionDeliveryCanSelect(delivery, record, accepted) || delivery.key.eventId !== accepted.eventId || delivery.source.name !== accepted.source.name || delivery.source.version !== accepted.source.version || record.ordinal <= accepted.cursor || record.ordinal > cursor) return yield* error("conflict", "selection");
3998
+ const existing = yield* readDelivery(delivery.key, "select existing delivery");
3999
+ if (existing !== null) {
4000
+ if (!sameDeliveryIdentity(existing, delivery)) return yield* error("conflict", "delivery-identity");
4001
+ continue;
4002
+ }
4003
+ if (!subscriptionCanSelect(record, accepted, effectiveNowMillis, false)) continue;
4004
+ additions.push({
4005
+ delivery,
4006
+ record
4007
+ });
4008
+ }
4009
+ if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}`, "count deliveries")) + additions.length > limits.maxDeliveries) return yield* error("capacity", "deliveries");
4010
+ for (const ownerId of new Set(additions.map(({ record }) => record.key.ownerId))) if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND owner_id=${ownerId}`, "count owner deliveries")) + additions.filter(({ record }) => record.key.ownerId === ownerId).length > limits.maxDeliveriesPerOwner) return yield* error("capacity", "owner-deliveries");
4011
+ for (const addition of additions) {
4012
+ yield* insertDelivery(addition.delivery);
4013
+ if (addition.record.configuration.mode === "once") yield* writeRegistration({
4014
+ ...addition.record,
4015
+ state: "consumed",
4016
+ recovery: null
4017
+ });
4018
+ }
4019
+ yield* writeEvent({
4020
+ ...accepted,
4021
+ cursor,
4022
+ routingComplete: complete,
4023
+ routingFailure: null
4024
+ });
4025
+ return true;
4026
+ }))) yield* failpoint.hit("subscription:select:after");
4027
+ });
4028
+ const catchUp = Effect.fn("SqliteSubscriptionStore.catchUp")(function* (inputEvent, inputDelivery, nowMillis, inputLimits) {
4029
+ const supplied = yield* validate(AcceptedEvent, inputEvent, "catch-up-event");
4030
+ const delivery = yield* validate(SubscriptionDelivery, inputDelivery, "catch-up-delivery");
4031
+ const limits = yield* validate(SubscriptionLimits, inputLimits, "catch-up-limits");
4032
+ yield* requirePartition(supplied.partition, "catch-up-partition");
4033
+ yield* requirePartition(delivery.key.subscription.partition, "catch-up-delivery-partition");
4034
+ if (yield* transact(Effect.gen(function* () {
4035
+ const accepted = yield* readEvent(supplied.eventId, "catch-up event");
4036
+ const record = yield* readRegistration(delivery.key.subscription, "catch-up subscription");
4037
+ if (accepted === null || record === null) return yield* error("not-found", accepted === null ? "event" : "subscription");
4038
+ if (!sameAcceptedEventIdentity(accepted, supplied) || !subscriptionDeliveryCanSelect(delivery, record, accepted) || delivery.key.eventId !== accepted.eventId || delivery.source.name !== accepted.source.name || delivery.source.version !== accepted.source.version || record.configuration.mode !== "once") return yield* error("conflict", "catch-up-identity");
4039
+ const existing = yield* readDelivery(delivery.key, "catch-up existing delivery");
4040
+ if (existing !== null) {
4041
+ if (!sameDeliveryIdentity(existing, delivery)) return yield* error("conflict", "delivery-identity");
4042
+ return false;
4043
+ }
4044
+ yield* failpoint.hit("subscription:catch-up:before");
4045
+ if (!subscriptionCanSelect(record, accepted, Math.max(nowMillis, yield* Clock.currentTimeMillis), true)) return yield* error("conflict", "catch-up-eligibility");
4046
+ if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}`, "count deliveries")) >= limits.maxDeliveries) return yield* error("capacity", "deliveries");
4047
+ if ((yield* count(sql`SELECT COUNT(*) AS count FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND owner_id=${record.key.ownerId}`, "count owner deliveries")) >= limits.maxDeliveriesPerOwner) return yield* error("capacity", "owner-deliveries");
4048
+ yield* insertDelivery(delivery);
4049
+ yield* writeRegistration({
4050
+ ...record,
4051
+ state: "consumed",
4052
+ recovery: null
4053
+ });
4054
+ return true;
4055
+ }))) yield* failpoint.hit("subscription:catch-up:after");
4056
+ });
4057
+ const deferEvent = Effect.fn("SqliteSubscriptionStore.deferEvent")(function* (eventId, nextAttemptAtMillis, code) {
4058
+ const routingFailure = code === void 0 ? "routing-failed" : yield* validate(SubscriptionName, code, "routing-failure");
4059
+ yield* transact(Effect.gen(function* () {
4060
+ const accepted = yield* readEvent(eventId, "defer event");
4061
+ if (accepted === null) return yield* error("not-found", "event");
4062
+ yield* failpoint.hit("subscription:defer-event:before");
4063
+ yield* writeEvent({
4064
+ ...accepted,
4065
+ nextAttemptAtMillis,
4066
+ routingFailure
4067
+ });
4068
+ }));
4069
+ yield* failpoint.hit("subscription:defer-event:after");
4070
+ });
4071
+ const delivery = Effect.fn("SqliteSubscriptionStore.delivery")(function* (input) {
4072
+ const key = yield* validate(SubscriptionDeliveryKey, input, "delivery-key");
4073
+ yield* requirePartition(key.subscription.partition, "delivery-partition");
4074
+ return yield* readDelivery(key, "get delivery");
4075
+ });
4076
+ const pendingDeliveries = Effect.fn("SqliteSubscriptionStore.pendingDeliveries")(function* (nowMillis, after, limit) {
4077
+ const rows = yield* query(sql`
4078
+ SELECT owner_id, subscription_id, event_id FROM effect_agent_subscription_deliveries
4079
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND state NOT IN ('delivered','refused')
4080
+ AND next_attempt_at_millis<=${nowMillis} AND delivery_key>${after} ORDER BY delivery_key LIMIT ${limit}
4081
+ `, "pending deliveries");
4082
+ const rowSchema = Schema.Struct({
4083
+ owner_id: Schema.String,
4084
+ subscription_id: Schema.String,
4085
+ event_id: Schema.String
4086
+ });
4087
+ return yield* decodeRows(rowSchema, rows, "pending delivery keys").pipe(Effect.map((items) => items.map((item) => ({
4088
+ subscription: {
4089
+ partition,
4090
+ ownerId: item.owner_id,
4091
+ subscriptionId: item.subscription_id
4092
+ },
4093
+ eventId: item.event_id
4094
+ }))));
4095
+ });
4096
+ const listDeliveries = Effect.fn("SqliteSubscriptionStore.listDeliveries")(function* (input, after, limit) {
4097
+ const key = yield* requireKey(input, "list-deliveries-key");
4098
+ const rows = yield* query(sql`
4099
+ SELECT record_json FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
4100
+ AND owner_id=${key.ownerId} AND subscription_id=${key.subscriptionId} AND delivery_key>${after} ORDER BY delivery_key LIMIT ${limit}
4101
+ `, "list deliveries");
4102
+ const decoded = yield* decodeRows(JsonRow, rows, "list deliveries");
4103
+ return yield* Effect.forEach(decoded, (row) => decode(SubscriptionDelivery, row.record_json, "list delivery"));
4104
+ });
4105
+ const changeDelivery = Effect.fn("SqliteSubscriptionStore.changeDelivery")(function* (inputKey, inputDeliveryId, inputChange) {
4106
+ const key = yield* validate(SubscriptionDeliveryKey, inputKey, "change-delivery-key");
4107
+ const deliveryId = yield* validate(Digest, inputDeliveryId, "change-delivery-id");
4108
+ const change = yield* validate(DeliveryChange, inputChange, "change-delivery-change");
4109
+ yield* requirePartition(key.subscription.partition, "change-delivery-partition");
4110
+ const result = yield* transact(Effect.gen(function* () {
4111
+ const existing = yield* readDelivery(key, "change delivery");
4112
+ const record = yield* readRegistration(key.subscription, "change delivery subscription");
4113
+ if (existing === null || record === null) return yield* error("not-found", existing === null ? "delivery" : "subscription");
4114
+ yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:before`);
4115
+ const effectiveChange = change._tag === "Prepare" ? {
4116
+ ...change,
4117
+ nowMillis: Math.max(change.nowMillis, yield* Clock.currentTimeMillis)
4118
+ } : change;
4119
+ const transition = applySubscriptionDeliveryChange(existing, record, deliveryId, effectiveChange);
4120
+ if (Result.isFailure(transition)) return yield* transition.failure;
4121
+ if (transition.success === existing) return {
4122
+ value: existing,
4123
+ changed: false
4124
+ };
4125
+ yield* writeDelivery(transition.success);
4126
+ return {
4127
+ value: transition.success,
4128
+ changed: true
4129
+ };
4130
+ }));
4131
+ if (result.changed) yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:after`);
4132
+ return result.value;
4133
+ });
4134
+ const recovering = Effect.fn("SqliteSubscriptionStore.recovering")(function* (nowMillis, after, limit) {
4135
+ const rows = yield* query(sql`
4136
+ SELECT owner_id, subscription_id, ordinal FROM effect_agent_subscriptions
4137
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND state='active'
4138
+ AND recovery_at_millis IS NOT NULL AND recovery_at_millis<=${nowMillis} AND ordinal>${after}
4139
+ ORDER BY ordinal LIMIT ${limit}
4140
+ `, "recovering subscriptions");
4141
+ const rowSchema = Schema.Struct({
4142
+ owner_id: Schema.String,
4143
+ subscription_id: Schema.String,
4144
+ ordinal: Schema.Natural
4145
+ });
4146
+ return yield* decodeRows(rowSchema, rows, "recovering subscription keys").pipe(Effect.map((items) => items.map((item) => ({
4147
+ key: {
4148
+ partition,
4149
+ ownerId: item.owner_id,
4150
+ subscriptionId: item.subscription_id
4151
+ },
4152
+ ordinal: item.ordinal
4153
+ }))));
4154
+ });
4155
+ const deferRecovery = Effect.fn("SqliteSubscriptionStore.deferRecovery")(function* (input, recovery) {
4156
+ const key = yield* requireKey(input, "defer-recovery-key");
4157
+ yield* transact(Effect.gen(function* () {
4158
+ const record = yield* readRegistration(key, "defer recovery");
4159
+ if (record === null) return yield* error("not-found", "subscription");
4160
+ yield* failpoint.hit("subscription:defer-recovery:before");
4161
+ yield* writeRegistration({
4162
+ ...record,
4163
+ recovery: record.state === "active" ? recovery : null
4164
+ });
4165
+ }));
4166
+ yield* failpoint.hit("subscription:defer-recovery:after");
4167
+ });
4168
+ const readScanCursors = Effect.gen(function* () {
4169
+ const rows = yield* query(sql`
4170
+ SELECT event_scan_cursor, delivery_scan_cursor, recovery_scan_cursor
4171
+ FROM effect_agent_subscription_sequences
4172
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
4173
+ `, "read subscription scan cursors");
4174
+ const decoded = yield* decodeRows(ScanRow, rows, "read subscription scan cursors");
4175
+ if (decoded.length !== 1) return yield* corrupt("subscription scan cursors");
4176
+ return {
4177
+ events: decoded[0].event_scan_cursor,
4178
+ deliveries: decoded[0].delivery_scan_cursor,
4179
+ recovery: decoded[0].recovery_scan_cursor
4180
+ };
4181
+ });
4182
+ const advanceScanCursors = Effect.fn("SqliteSubscriptionStore.advanceScanCursors")(function* (input) {
4183
+ const cursors = yield* validate(SubscriptionScanCursors, input, "scan-cursors");
4184
+ yield* transact(Effect.gen(function* () {
4185
+ yield* failpoint.hit("subscription:advance-scan-cursors:before");
4186
+ yield* query(sql`
4187
+ UPDATE effect_agent_subscription_sequences
4188
+ SET event_scan_cursor=${cursors.events}, delivery_scan_cursor=${cursors.deliveries}, recovery_scan_cursor=${cursors.recovery}
4189
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
4190
+ `, "advance subscription scan cursors");
4191
+ }));
4192
+ yield* failpoint.hit("subscription:advance-scan-cursors:after");
4193
+ });
4194
+ const indexedDeadline = query(sql`
4195
+ SELECT MIN(deadline) AS deadline FROM (
4196
+ SELECT next_attempt_at_millis AS deadline FROM effect_agent_subscription_events
4197
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND routing_complete=0
4198
+ UNION ALL SELECT next_attempt_at_millis FROM effect_agent_subscription_deliveries
4199
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND state NOT IN ('delivered','refused')
4200
+ UNION ALL SELECT recovery_at_millis FROM effect_agent_subscriptions
4201
+ WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND state='active' AND recovery_at_millis IS NOT NULL
4202
+ )
4203
+ `, "next subscription deadline").pipe(Effect.flatMap((rows) => decodeRows(Schema.Struct({ deadline: Schema.NullOr(Schema.Number) }), rows, "next subscription deadline")), Effect.flatMap((rows) => rows.length === 1 ? Effect.succeed(rows[0].deadline) : Effect.fail(corrupt("next subscription deadline"))));
4204
+ const nextDeadline = Effect.gen(function* () {
4205
+ const cursors = yield* readScanCursors;
4206
+ if (cursors.events !== "" || cursors.deliveries !== "" || cursors.recovery !== 0) return 0;
4207
+ return yield* indexedDeadline;
4208
+ });
4209
+ const prearm = Effect.fn("DoSubscriptionStore.prearm")(function* (deadlineAtMillis) {
4210
+ yield* transactions.run((replace) => replaceAlarm(replace, deadlineAtMillis));
4211
+ yield* failpoint.hit("subscription:prearm:after");
4212
+ });
4213
+ const reconcile = Effect.gen(function* () {
4214
+ yield* transactions.run((replace) => Effect.gen(function* () {
4215
+ yield* replaceAlarm(replace, yield* readIndexedDeadline());
4216
+ }));
4217
+ yield* failpoint.hit("subscription:reconcile:after");
4218
+ });
4219
+ return Context.make(SubscriptionStore, {
4220
+ partition,
4221
+ register,
4222
+ get,
4223
+ list,
4224
+ cancel,
4225
+ accept,
4226
+ event,
4227
+ pendingEvents,
4228
+ candidates,
4229
+ select,
4230
+ catchUp,
4231
+ deferEvent,
4232
+ delivery,
4233
+ pendingDeliveries,
4234
+ listDeliveries,
4235
+ changeDelivery,
4236
+ recovering,
4237
+ deferRecovery,
4238
+ readScanCursors,
4239
+ advanceScanCursors,
4240
+ nextDeadline
4241
+ }).pipe(Context.add(DoSubscriptionAlarmControl, {
4242
+ prearm,
4243
+ reconcile
4244
+ }));
4245
+ });
4246
+ const doSubscriptionStoreLayer = (partition) => Layer.effectContext(makeSubscriptionStore(partition));
4247
+ //#endregion
3574
4248
  //#region src/port-protocol.ts
3575
4249
  /**
3576
4250
  * The cross-Durable-Object port protocol (plan §1.3, D-P6-3): Schema request/response/error
3577
- * envelopes for the CLOSED route-capable subset of the session ports. One Conversation's
3578
- * Durable Object executes another Conversation's request against its OWN local facets; the
4251
+ * envelopes for the CLOSED route-capable subset of the thread ports. One Thread's
4252
+ * Durable Object executes another Thread's request against its OWN local facets; the
3579
4253
  * envelopes here are the only values that cross the Object boundary, and they are
3580
4254
  * transport-agnostic — native Durable Object JS RPC is the shipped carrier, fetch-with-JSON
3581
4255
  * the documented fallback, and both move the same Schema-encoded JSON.
3582
4256
  *
3583
4257
  * The closed subset is exactly the set of operations the durable coordinator performs against
3584
- * a FOREIGN Conversation (parent/child establishment, status checks, abort propagation,
3585
- * child-settlement notification, and the child-conversation store operations used by
4258
+ * a FOREIGN Thread (parent/child establishment, status checks, abort propagation,
4259
+ * child-settlement notification, and the child-thread store operations used by
3586
4260
  * establishment, `verifySettledChild`, and result projection):
3587
4261
  *
3588
4262
  * - ledger: `admit`, `markReady`, `lookup`, `resolveAdmission`, `requestAbort`,
@@ -3595,7 +4269,7 @@ const scheduleStoreLayer = Layer.effectContext(makeServices);
3595
4269
  *
3596
4270
  * Failures cross the boundary as the `PortFailure` union and re-decode on the caller side to
3597
4271
  * the SAME tagged error types the local facet would have produced, so routed calls keep
3598
- * error-tag fidelity. `cause` chains inside `LedgerError`/`ConversationStoreError` travel as
4272
+ * error-tag fidelity. `cause` chains inside `LedgerError`/`ThreadStoreError` travel as
3599
4273
  * Schema defects and do not claim instance fidelity across Objects (plan §2.8).
3600
4274
  */
3601
4275
  /** Ceiling for protocol diagnostic strings; matches `AdmissionIndeterminate.reason`. */
@@ -3606,7 +4280,7 @@ const boundPortDiagnostic = (value) => value.length > 4096 ? `${value.slice(0, M
3606
4280
  /**
3607
4281
  * The envelope itself could not be honored: the receiving Object could not decode the
3608
4282
  * request, or a response could not be encoded/decoded. It never carries port semantics —
3609
- * callers fold it into the operation's base error (`LedgerError`/`ConversationStoreError`),
4283
+ * callers fold it into the operation's base error (`LedgerError`/`ThreadStoreError`),
3610
4284
  * except `resolveAdmission`, which folds it into `AdmissionIndeterminate` because a
3611
4285
  * non-answer is never proof of absence (SUB-031).
3612
4286
  */
@@ -3623,16 +4297,16 @@ var LedgerResolveAdmissionCall = class extends Schema.TaggedClass("@effect-agent
3623
4297
  var LedgerRequestAbortCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/LedgerRequestAbortCall")("LedgerRequestAbort", { request: AbortCommand }) {};
3624
4298
  /** Routed `SubmissionLedger.recordChildSettled` — the child→parent durable notification. */
3625
4299
  var LedgerRecordChildSettledCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/LedgerRecordChildSettledCall")("LedgerRecordChildSettled", { request: ChildSettledNotification }) {};
3626
- /** Routed `ConversationStore.materialize` against the owning Object. */
3627
- var StoreMaterializeCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreMaterializeCall")("StoreMaterialize", { request: ConversationMaterialization }) {};
3628
- /** Routed `ConversationStore.append` against the owning Object. */
4300
+ /** Routed `ThreadStore.materialize` against the owning Object. */
4301
+ var StoreMaterializeCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreMaterializeCall")("StoreMaterialize", { request: ThreadMaterialization }) {};
4302
+ /** Routed `ThreadStore.append` against the owning Object. */
3629
4303
  var StoreAppendCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreAppendCall")("StoreAppend", { request: FencedAppendRequest }) {};
3630
- /** Routed one-page `ConversationStore.read`; the page bound is the request's own `limit`. */
3631
- var StoreReadPageCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreReadPageCall")("StoreReadPage", { request: ConversationRead }) {};
3632
- /** Routed `ConversationStore.inspectTail` against the owning Object. */
3633
- var StoreInspectTailCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreInspectTailCall")("StoreInspectTail", { request: ConversationTailRequest }) {};
3634
- /** Routed `ConversationStore.export` against the owning Object. */
3635
- var StoreExportCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreExportCall")("StoreExport", { request: ConversationExportRequest }) {};
4304
+ /** Routed one-page `ThreadStore.read`; the page bound is the request's own `limit`. */
4305
+ var StoreReadPageCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreReadPageCall")("StoreReadPage", { request: ThreadRead }) {};
4306
+ /** Routed `ThreadStore.inspectTail` against the owning Object. */
4307
+ var StoreInspectTailCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreInspectTailCall")("StoreInspectTail", { request: ThreadTailRequest }) {};
4308
+ /** Routed `ThreadStore.export` against the owning Object. */
4309
+ var StoreExportCall = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreExportCall")("StoreExport", { request: ThreadExportRequest }) {};
3636
4310
  /** Every request that may cross a Durable Object boundary — the CLOSED route-capable subset. */
3637
4311
  const PortRequest = Schema.Union([
3638
4312
  LedgerAdmitCall,
@@ -3658,8 +4332,8 @@ var StoreMaterializeResult = class extends Schema.TaggedClass("@effect-agent/sto
3658
4332
  var StoreAppendResult = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreAppendResult")("StoreAppendResult", { result: AppendResult }) {};
3659
4333
  /** One page of canonical records, bounded by the request's `limit` (≤ 1,024). */
3660
4334
  var StoreReadPageResult = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreReadPageResult")("StoreReadPageResult", { records: Schema.Array(CanonicalRecordEnvelope).check(Schema.isMaxLength(1024)) }) {};
3661
- var StoreInspectTailResult = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreInspectTailResult")("StoreInspectTailResult", { tail: ConversationTail }) {};
3662
- var StoreExportResult = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreExportResult")("StoreExportResult", { export: ConversationExport }) {};
4335
+ var StoreInspectTailResult = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreInspectTailResult")("StoreInspectTailResult", { tail: ThreadTail }) {};
4336
+ var StoreExportResult = class extends Schema.TaggedClass("@effect-agent/storage-cloudflare/StoreExportResult")("StoreExportResult", { export: ThreadExport }) {};
3663
4337
  /** Every successful routed result. Callers narrow by the tag their request implies. */
3664
4338
  const PortResult = Schema.Union([
3665
4339
  LedgerAdmitResult,
@@ -3677,15 +4351,15 @@ const PortResult = Schema.Union([
3677
4351
  /**
3678
4352
  * Every typed failure a route-capable operation can produce on its owning Object, plus the
3679
4353
  * protocol's own `PortProtocolError`. Members re-decode to the SAME tagged classes the
3680
- * session ports declare, so a routed caller observes identical error tags and fields.
4354
+ * thread ports declare, so a routed caller observes identical error tags and fields.
3681
4355
  */
3682
4356
  const PortFailure = Schema.Union([
3683
4357
  AdmissionConflict,
3684
4358
  SettlementConflict,
3685
4359
  JoinedToHost,
3686
4360
  LedgerError,
3687
- ConversationStoreError,
3688
- ConversationNotMaterialized,
4361
+ ThreadStoreError,
4362
+ ThreadNotMaterialized,
3689
4363
  AppendConflict,
3690
4364
  FenceRejected,
3691
4365
  PortProtocolError
@@ -3702,8 +4376,8 @@ const encodePortResponse = Schema.encodeEffect(PortResponse);
3702
4376
  const decodePortResponse = Schema.decodeUnknownEffect(PortResponse);
3703
4377
  //#endregion
3704
4378
  //#region src/routing.ts
3705
- const ConversationIdSchema = ConversationMaterialization.fields.conversationId;
3706
- const decodeConversationId = Schema.decodeUnknownEffect(ConversationIdSchema);
4379
+ const ThreadIdSchema = ThreadMaterialization.fields.threadId;
4380
+ const decodeThreadId = Schema.decodeUnknownEffect(ThreadIdSchema);
3707
4381
  /**
3708
4382
  * The ledger row bound routable Submission identities must respect (mirrors the local
3709
4383
  * facet's `MAX_IDENTIFIER_LENGTH`; the minting side already refuses longer identities typed
@@ -3714,7 +4388,7 @@ const MAX_ROUTABLE_SUBMISSION_ID_LENGTH = 1024;
3714
4388
  const UUID_HEAD_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
3715
4389
  /**
3716
4390
  * A transport could not deliver a port request to (or an answer from) the owning
3717
- * Conversation's Durable Object. `retryable` carries the platform's own stub signal when one
4391
+ * Thread's Durable Object. `retryable` carries the platform's own stub signal when one
3718
4392
  * exists. This error never crosses the wire — it is the CALLER-side evidence that the
3719
4393
  * authority was unreachable, which is exactly the case `AdmissionIndeterminate` was
3720
4394
  * specified for (SUB-031).
@@ -3757,24 +4431,24 @@ const portTransportFailure = (target, cause) => {
3757
4431
  };
3758
4432
  /**
3759
4433
  * Delivery of Schema-encoded port envelopes to the Durable Object that owns a FOREIGN
3760
- * Conversation (plan §1.3, D-P6-3). The shipped implementation (platform-cloudflare, WP3)
4434
+ * Thread (plan §1.3, D-P6-3). The shipped implementation (platform-cloudflare, WP3)
3761
4435
  * calls the owner's `portCall` over native Durable Object JS RPC via
3762
- * `namespace.idFromName(conversationId)`; the protocol is transport-agnostic and any carrier
4436
+ * `namespace.idFromName(threadId)`; the protocol is transport-agnostic and any carrier
3763
4437
  * that moves the encoded envelopes verbatim satisfies this service. Implementations MUST
3764
4438
  * surface every delivery problem as `PortTransportError` and must never fabricate an answer.
3765
4439
  */
3766
- var ConversationPortTransport = class extends Context.Service()("@effect-agent/storage-cloudflare/ConversationPortTransport") {};
4440
+ var ThreadPortTransport = class extends Context.Service()("@effect-agent/storage-cloudflare/ThreadPortTransport") {};
3767
4441
  const LOCAL = { _tag: "local" };
3768
4442
  /**
3769
- * Parse a DC-minted routable Submission identity — `{uuidv7}:{conversationId}`, split at the
3770
- * FIRST `:` because the Conversation tail may itself contain colons (D-P6-5). This adapter
4443
+ * Parse a DC-minted routable Submission identity — `{uuidv7}:{threadId}`, split at the
4444
+ * FIRST `:` because the Thread tail may itself contain colons (D-P6-5). This adapter
3771
4445
  * minted the format at admission and is the ONLY component that parses it; identities that do
3772
4446
  * not carry the minted shape (no separator, non-UUID head, empty tail) fall back to the local
3773
4447
  * facet, which is the only authority this Object can consult without inventing an owner.
3774
4448
  * Identities beyond the ledger's 1,024-character row bound fail typed: the minting side
3775
4449
  * refused them at admission, so they cannot name any stored row anywhere.
3776
4450
  */
3777
- const routableSubmissionTarget = (localConversationId) => Effect.fn("DoPortRouting.routableSubmissionTarget")(function* (operation, submissionId) {
4451
+ const routableSubmissionTarget = (localThreadId) => Effect.fn("DoPortRouting.routableSubmissionTarget")(function* (operation, submissionId) {
3778
4452
  if (submissionId.length > MAX_ROUTABLE_SUBMISSION_ID_LENGTH) return yield* LedgerError.make({
3779
4453
  operation,
3780
4454
  message: `A Submission identity of ${submissionId.length} characters exceeds the ${MAX_ROUTABLE_SUBMISSION_ID_LENGTH}-character routable identity bound; admission refuses such identities, so it cannot name any stored row.`
@@ -3783,16 +4457,16 @@ const routableSubmissionTarget = (localConversationId) => Effect.fn("DoPortRouti
3783
4457
  if (separator === -1) return LOCAL;
3784
4458
  if (!UUID_HEAD_PATTERN.test(submissionId.slice(0, separator))) return LOCAL;
3785
4459
  const tail = submissionId.slice(separator + 1);
3786
- if (tail === localConversationId) return LOCAL;
3787
- return yield* decodeConversationId(tail).pipe(Effect.map((conversationId) => ({
4460
+ if (tail === localThreadId) return LOCAL;
4461
+ return yield* decodeThreadId(tail).pipe(Effect.map((threadId) => ({
3788
4462
  _tag: "foreign",
3789
- conversationId
4463
+ threadId
3790
4464
  })), Effect.orElseSucceed(() => LOCAL));
3791
4465
  });
3792
4466
  const NoAdditionalPortFailure = Schema.Never;
3793
4467
  const AbortPortFailure = Schema.Union([SettlementConflict, JoinedToHost]);
3794
4468
  const AppendPortFailure = Schema.Union([
3795
- ConversationNotMaterialized,
4469
+ ThreadNotMaterialized,
3796
4470
  AppendConflict,
3797
4471
  FenceRejected
3798
4472
  ]);
@@ -3800,13 +4474,13 @@ const AppendPortFailure = Schema.Union([
3800
4474
  * The fail-fast refusal for any foreign operation OUTSIDE the closed route-capable subset
3801
4475
  * (plan §1.3): honesty over accidental distribution.
3802
4476
  */
3803
- const crossConversationLedgerError = (operation, target) => LedgerError.make({
4477
+ const crossThreadLedgerError = (operation, target) => LedgerError.make({
3804
4478
  operation,
3805
- message: `${operation} addressed to foreign Conversation ${target} is not route-capable; the closed cross-Object subset is admit, markReady, lookup, resolveAdmission, requestAbort, and recordChildSettled. Every other ledger operation is lane-local by construction and must execute inside the owning Conversation's Durable Object.`
4479
+ message: `${operation} addressed to foreign Thread ${target} is not route-capable; the closed cross-Object subset is admit, markReady, lookup, resolveAdmission, requestAbort, and recordChildSettled. Every other ledger operation is lane-local by construction and must execute inside the owning Thread's Durable Object.`
3806
4480
  });
3807
- const crossConversationStoreError = (operation, target) => ConversationStoreError.make({
4481
+ const crossThreadStoreError = (operation, target) => ThreadStoreError.make({
3808
4482
  operation,
3809
- message: `${operation} addressed to foreign Conversation ${target} is not route-capable; the closed cross-Object subset is materialize, append, read (paged), inspectTail, and export. Observation and checkpoints are lane-local by construction and must execute inside the owning Conversation's Durable Object.`
4483
+ message: `${operation} addressed to foreign Thread ${target} is not route-capable; the closed cross-Object subset is materialize, append, read (paged), inspectTail, and export. Observation and checkpoints are lane-local by construction and must execute inside the owning Thread's Durable Object.`
3810
4484
  });
3811
4485
  const makeTransportCall = (transport) => Effect.fn("DoPortRouting.transportCall")(function* (target, call) {
3812
4486
  const encoded = yield* encodePortRequest(call).pipe(Effect.mapError((error) => PortProtocolError.make({ message: boundPortDiagnostic(`The port request could not be encoded: ${error.message}`) })));
@@ -3814,12 +4488,12 @@ const makeTransportCall = (transport) => Effect.fn("DoPortRouting.transportCall"
3814
4488
  });
3815
4489
  const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServices")(function* (options) {
3816
4490
  const local = yield* SubmissionLedger;
3817
- const transport = yield* ConversationPortTransport;
4491
+ const transport = yield* ThreadPortTransport;
3818
4492
  const transportCall = makeTransportCall(transport);
3819
- const submissionTarget = routableSubmissionTarget(options.localConversationId);
4493
+ const submissionTarget = routableSubmissionTarget(options.localThreadId);
3820
4494
  const routeFailure = (operation, target) => (error) => LedgerError.make({
3821
4495
  operation,
3822
- message: boundPortDiagnostic(`Routed ${operation} to the Conversation Object owning ${target} failed: ${error.message}`),
4496
+ message: boundPortDiagnostic(`Routed ${operation} to the Thread Object owning ${target} failed: ${error.message}`),
3823
4497
  cause: error
3824
4498
  });
3825
4499
  /**
@@ -3838,14 +4512,14 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3838
4512
  if (failure._tag === "LedgerError") return Effect.fail(failure);
3839
4513
  return Effect.fail(LedgerError.make({
3840
4514
  operation,
3841
- message: boundPortDiagnostic(`The Conversation Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
4515
+ message: boundPortDiagnostic(`The Thread Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
3842
4516
  cause: failure
3843
4517
  }));
3844
4518
  }
3845
4519
  const result = response.result;
3846
4520
  if (!isExpectedResult(result)) return Effect.fail(LedgerError.make({
3847
4521
  operation,
3848
- message: `The Conversation Object owning ${target} answered ${operation} with the mismatched result ${result._tag}.`
4522
+ message: `The Thread Object owning ${target} answered ${operation} with the mismatched result ${result._tag}.`
3849
4523
  }));
3850
4524
  return Effect.succeed(result);
3851
4525
  }), Effect.withSpan("DoPortRouting.foreignLedgerCall", { attributes: {
@@ -3864,13 +4538,13 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3864
4538
  const resolveForeignAdmission = (target, request) => transportCall(target, LedgerResolveAdmissionCall.make({ request })).pipe(Effect.flatMap((response) => {
3865
4539
  if (response._tag === "PortFailed") {
3866
4540
  if (response.failure._tag === "LedgerError") return Effect.fail(response.failure);
3867
- return Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Conversation Object owning ${target} answered resolveAdmission with the out-of-contract failure ${response.failure._tag}: ${response.failure.message}`) }));
4541
+ return Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Thread Object owning ${target} answered resolveAdmission with the out-of-contract failure ${response.failure._tag}: ${response.failure.message}`) }));
3868
4542
  }
3869
- if (response.result._tag !== "LedgerResolveAdmissionResult") return Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Conversation Object owning ${target} answered resolveAdmission with the mismatched result ${response.result._tag}.`) }));
4543
+ if (response.result._tag !== "LedgerResolveAdmissionResult") return Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Thread Object owning ${target} answered resolveAdmission with the mismatched result ${response.result._tag}.`) }));
3870
4544
  return Effect.succeed(response.result.resolution);
3871
4545
  }), Effect.catchTags({
3872
- PortTransportError: (error) => Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Conversation Object owning ${target} is unreachable: ${error.message}`) })),
3873
- PortProtocolError: (error) => Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The answer of the Conversation Object owning ${target} could not be understood: ${error.message}`) }))
4546
+ PortTransportError: (error) => Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The Thread Object owning ${target} is unreachable: ${error.message}`) })),
4547
+ PortProtocolError: (error) => Effect.succeed(AdmissionIndeterminate.make({ reason: boundPortDiagnostic(`The answer of the Thread Object owning ${target} could not be understood: ${error.message}`) }))
3874
4548
  }), Effect.withSpan("DoPortRouting.resolveForeignAdmission", { attributes: { target } }));
3875
4549
  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)));
3876
4550
  /**
@@ -3889,7 +4563,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3889
4563
  if (childSubmissionId === void 0 || attachments.has(childSubmissionId)) continue;
3890
4564
  const target = yield* submissionTarget(operation, childSubmissionId);
3891
4565
  if (target._tag !== "foreign") continue;
3892
- const child = yield* foreignLookupById(operation, target.conversationId, childSubmissionId);
4566
+ const child = yield* foreignLookupById(operation, target.threadId, childSubmissionId);
3893
4567
  if (Option.isNone(child)) continue;
3894
4568
  attachments.set(childSubmissionId, ChildAttachmentSnapshot.make({
3895
4569
  toolCallId: reservation.parentToolCallId,
@@ -3913,44 +4587,45 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
3913
4587
  });
3914
4588
  const routed = SubmissionLedger.of({
3915
4589
  capabilities: local.capabilities,
3916
- 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)),
3917
- 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))),
3918
- 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))),
3919
- resolveAdmission: (request) => request.conversationId === options.localConversationId ? local.resolveAdmission(request) : resolveForeignAdmission(request.conversationId, request),
3920
- 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)))),
3921
- 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)))),
3922
- claim: (request) => request.conversationId === options.localConversationId ? local.claim(request) : Effect.fail(crossConversationLedgerError("ledger claim", request.conversationId)),
3923
- claimJoining: (request) => request.conversationId === options.localConversationId ? local.claimJoining(request) : Effect.fail(crossConversationLedgerError("ledger claim joining", request.conversationId)),
3924
- 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)))),
3925
- releaseOwnership: (request) => submissionTarget("ledger release ownership", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.releaseOwnership(request) : Effect.fail(crossConversationLedgerError("ledger release ownership", target.conversationId)))),
3926
- markInputApplied: (request) => submissionTarget("ledger mark input applied", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markInputApplied(request) : Effect.fail(crossConversationLedgerError("ledger mark input applied", target.conversationId)))),
3927
- reserveSettlement: (request) => submissionTarget("ledger reserve settlement", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.reserveSettlement(request) : Effect.fail(crossConversationLedgerError("ledger reserve settlement", target.conversationId)))),
3928
- finalizeSettlement: (request) => submissionTarget("ledger finalize settlement", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.finalizeSettlement(request) : Effect.fail(crossConversationLedgerError("ledger finalize settlement", target.conversationId)))),
3929
- markJoined: (request) => submissionTarget("ledger mark joined", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markJoined(request) : Effect.fail(crossConversationLedgerError("ledger mark joined", target.conversationId)))),
3930
- revertJoining: (request) => submissionTarget("ledger revert joining", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.revertJoining(request) : Effect.fail(crossConversationLedgerError("ledger revert joining", target.conversationId)))),
3931
- suspend: (request) => submissionTarget("ledger suspend", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.suspend(request) : Effect.fail(crossConversationLedgerError("ledger suspend", target.conversationId)))),
3932
- recordApprovalDecision: (command) => submissionTarget("ledger record approval decision", command.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.recordApprovalDecision(command) : Effect.fail(crossConversationLedgerError("ledger record approval decision", target.conversationId)))),
3933
- markUnknown: (request) => submissionTarget("ledger mark unknown", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markUnknown(request) : Effect.fail(crossConversationLedgerError("ledger mark unknown", target.conversationId)))),
3934
- recordUnknownResolution: (command) => submissionTarget("ledger record unknown resolution", command.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.recordUnknownResolution(command) : Effect.fail(crossConversationLedgerError("ledger record unknown resolution", target.conversationId)))),
3935
- reserveChildBudget: (request) => submissionTarget("ledger reserve child budget", request.parentSubmissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.reserveChildBudget(request) : Effect.fail(crossConversationLedgerError("ledger reserve child budget", target.conversationId)))),
4590
+ admit: (request) => request.threadId === options.localThreadId ? local.admit(request) : foreignLedgerCall("ledger admit", request.threadId, LedgerAdmitCall.make({ request }), LedgerAdmitResult, AdmissionConflict).pipe(Effect.map((reply) => reply.result)),
4591
+ markReady: (request) => submissionTarget("ledger mark ready", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markReady(request) : foreignLedgerCall("ledger mark ready", target.threadId, LedgerMarkReadyCall.make({ request }), LedgerMarkReadyResult, NoAdditionalPortFailure).pipe(Effect.asVoid))),
4592
+ lookup: (request) => request._tag === "SubmissionLookupById" ? submissionTarget("ledger lookup", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.lookup(request) : foreignLookupById("ledger lookup", target.threadId, request.submissionId))) : request.threadId === options.localThreadId ? local.lookup(request) : foreignLedgerCall("ledger lookup", request.threadId, LedgerLookupCall.make({ request }), LedgerLookupResult, NoAdditionalPortFailure).pipe(Effect.map((result) => result.submission === void 0 ? Option.none() : Option.some(result.submission))),
4593
+ resolveAdmission: (request) => request.threadId === options.localThreadId ? local.resolveAdmission(request) : resolveForeignAdmission(request.threadId, request),
4594
+ requestAbort: (request) => submissionTarget("ledger request abort", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.requestAbort(request) : foreignLedgerCall("ledger request abort", target.threadId, LedgerRequestAbortCall.make({ request }), LedgerRequestAbortResult, AbortPortFailure).pipe(Effect.map((reply) => reply.intent)))),
4595
+ 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.threadId, LedgerRecordChildSettledCall.make({ request }), LedgerRecordChildSettledResult, NoAdditionalPortFailure).pipe(Effect.map((reply) => reply.outcome)))),
4596
+ claim: (request) => request.threadId === options.localThreadId ? local.claim(request) : Effect.fail(crossThreadLedgerError("ledger claim", request.threadId)),
4597
+ claimJoining: (request) => request.threadId === options.localThreadId ? local.claimJoining(request) : Effect.fail(crossThreadLedgerError("ledger claim joining", request.threadId)),
4598
+ renewOwnership: (request) => submissionTarget("ledger renew ownership", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.renewOwnership(request) : Effect.fail(crossThreadLedgerError("ledger renew ownership", target.threadId)))),
4599
+ releaseOwnership: (request) => submissionTarget("ledger release ownership", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.releaseOwnership(request) : Effect.fail(crossThreadLedgerError("ledger release ownership", target.threadId)))),
4600
+ markInputApplied: (request) => submissionTarget("ledger mark input applied", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markInputApplied(request) : Effect.fail(crossThreadLedgerError("ledger mark input applied", target.threadId)))),
4601
+ reserveSettlement: (request) => submissionTarget("ledger reserve settlement", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.reserveSettlement(request) : Effect.fail(crossThreadLedgerError("ledger reserve settlement", target.threadId)))),
4602
+ finalizeSettlement: (request) => submissionTarget("ledger finalize settlement", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.finalizeSettlement(request) : Effect.fail(crossThreadLedgerError("ledger finalize settlement", target.threadId)))),
4603
+ markJoined: (request) => submissionTarget("ledger mark joined", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markJoined(request) : Effect.fail(crossThreadLedgerError("ledger mark joined", target.threadId)))),
4604
+ revertJoining: (request) => submissionTarget("ledger revert joining", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.revertJoining(request) : Effect.fail(crossThreadLedgerError("ledger revert joining", target.threadId)))),
4605
+ suspend: (request) => submissionTarget("ledger suspend", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.suspend(request) : Effect.fail(crossThreadLedgerError("ledger suspend", target.threadId)))),
4606
+ recordApprovalDecision: (command) => submissionTarget("ledger record approval decision", command.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.recordApprovalDecision(command) : Effect.fail(crossThreadLedgerError("ledger record approval decision", target.threadId)))),
4607
+ markUnknown: (request) => submissionTarget("ledger mark unknown", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.markUnknown(request) : Effect.fail(crossThreadLedgerError("ledger mark unknown", target.threadId)))),
4608
+ recordUnknownResolution: (command) => submissionTarget("ledger record unknown resolution", command.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.recordUnknownResolution(command) : Effect.fail(crossThreadLedgerError("ledger record unknown resolution", target.threadId)))),
4609
+ reserveChildBudget: (request) => submissionTarget("ledger reserve child budget", request.parentSubmissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.reserveChildBudget(request) : Effect.fail(crossThreadLedgerError("ledger reserve child budget", target.threadId)))),
3936
4610
  attachChildToReservation: local.attachChildToReservation,
3937
4611
  beginChildBudgetRelease: local.beginChildBudgetRelease,
3938
4612
  releaseChildBudget: local.releaseChildBudget,
3939
4613
  scanNonterminal: local.scanNonterminal,
3940
- loadRecoverySnapshot: (request) => submissionTarget("ledger load recovery snapshot", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.loadRecoverySnapshot(request).pipe(Effect.flatMap(enrichChildAttachments)) : Effect.fail(crossConversationLedgerError("ledger load recovery snapshot", target.conversationId))))
4614
+ loadRecoverySnapshot: (request) => submissionTarget("ledger load recovery snapshot", request.submissionId).pipe(Effect.flatMap((target) => target._tag === "local" ? local.loadRecoverySnapshot(request).pipe(Effect.flatMap(enrichChildAttachments)) : Effect.fail(crossThreadLedgerError("ledger load recovery snapshot", target.threadId))))
3941
4615
  });
3942
4616
  return Context.make(SubmissionLedger, routed);
3943
4617
  });
3944
4618
  const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices")(function* (options) {
3945
- const local = yield* ConversationStore;
3946
- const transport = yield* ConversationPortTransport;
4619
+ const local = yield* ThreadStore;
4620
+ const checkpoints = local.checkpoints;
4621
+ const transport = yield* ThreadPortTransport;
3947
4622
  const transportCall = makeTransportCall(transport);
3948
- const routeFailure = (operation, target) => (error) => ConversationStoreError.make({
4623
+ const routeFailure = (operation, target) => (error) => ThreadStoreError.make({
3949
4624
  operation,
3950
- message: boundPortDiagnostic(`Routed ${operation} to the Conversation Object owning ${target} failed: ${error.message}`),
4625
+ message: boundPortDiagnostic(`Routed ${operation} to the Thread Object owning ${target} failed: ${error.message}`),
3951
4626
  cause: error
3952
4627
  });
3953
- /** The store twin of `foreignLedgerCall` with `ConversationStoreError` as the base error. */
4628
+ /** The store twin of `foreignLedgerCall` with `ThreadStoreError` as the base error. */
3954
4629
  const foreignStoreCall = (operation, target, call, resultSchema, failureSchema) => {
3955
4630
  const isExpectedResult = Schema.is(resultSchema);
3956
4631
  const isExpectedFailure = Schema.is(failureSchema);
@@ -3958,17 +4633,17 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
3958
4633
  if (response._tag === "PortFailed") {
3959
4634
  const failure = response.failure;
3960
4635
  if (isExpectedFailure(failure)) return Effect.fail(failure);
3961
- if (failure._tag === "ConversationStoreError") return Effect.fail(failure);
3962
- return Effect.fail(ConversationStoreError.make({
4636
+ if (failure._tag === "ThreadStoreError") return Effect.fail(failure);
4637
+ return Effect.fail(ThreadStoreError.make({
3963
4638
  operation,
3964
- message: boundPortDiagnostic(`The Conversation Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
4639
+ message: boundPortDiagnostic(`The Thread Object owning ${target} answered ${operation} with the out-of-contract failure ${failure._tag}: ${failure.message}`),
3965
4640
  cause: failure
3966
4641
  }));
3967
4642
  }
3968
4643
  const result = response.result;
3969
- if (!isExpectedResult(result)) return Effect.fail(ConversationStoreError.make({
4644
+ if (!isExpectedResult(result)) return Effect.fail(ThreadStoreError.make({
3970
4645
  operation,
3971
- message: `The Conversation Object owning ${target} answered ${operation} with the mismatched result ${result._tag}.`
4646
+ message: `The Thread Object owning ${target} answered ${operation} with the mismatched result ${result._tag}.`
3972
4647
  }));
3973
4648
  return Effect.succeed(result);
3974
4649
  }), Effect.withSpan("DoPortRouting.foreignStoreCall", { attributes: {
@@ -3976,39 +4651,41 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
3976
4651
  target
3977
4652
  } }));
3978
4653
  };
3979
- const routed = ConversationStore.of({
3980
- materialize: (request) => request.conversationId === options.localConversationId ? local.materialize(request) : foreignStoreCall("conversation materialize", request.conversationId, StoreMaterializeCall.make({ request }), StoreMaterializeResult, FenceRejected).pipe(Effect.asVoid),
3981
- 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)),
3982
- 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)))),
3983
- 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)),
3984
- 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)),
3985
- observe: (request) => request.conversationId === options.localConversationId ? local.observe(request) : Stream.unwrap(Effect.fail(crossConversationStoreError("conversation observe", request.conversationId))),
3986
- saveCheckpoint: (request) => request.checkpoint.conversationId === options.localConversationId ? local.saveCheckpoint(request) : Effect.fail(crossConversationStoreError("conversation save checkpoint", request.checkpoint.conversationId)),
3987
- loadCheckpoint: (request) => request.conversationId === options.localConversationId ? local.loadCheckpoint(request) : Effect.fail(crossConversationStoreError("conversation load checkpoint", request.conversationId))
3988
- });
3989
- return Context.make(ConversationStore, routed);
4654
+ const routed = ThreadStore.of({
4655
+ materialize: (request) => request.threadId === options.localThreadId ? local.materialize(request) : foreignStoreCall("thread materialize", request.threadId, StoreMaterializeCall.make({ request }), StoreMaterializeResult, FenceRejected).pipe(Effect.asVoid),
4656
+ append: (request) => request.threadId === options.localThreadId ? local.append(request) : foreignStoreCall("thread append", request.threadId, StoreAppendCall.make({ request }), StoreAppendResult, AppendPortFailure).pipe(Effect.map((reply) => reply.result)),
4657
+ read: (request) => request.threadId === options.localThreadId ? local.read(request) : Stream.unwrap(foreignStoreCall("thread read", request.threadId, StoreReadPageCall.make({ request }), StoreReadPageResult, ThreadNotMaterialized).pipe(Effect.map((reply) => Stream.fromIterable(reply.records)))),
4658
+ inspectTail: (request) => request.threadId === options.localThreadId ? local.inspectTail(request) : foreignStoreCall("thread inspect tail", request.threadId, StoreInspectTailCall.make({ request }), StoreInspectTailResult, ThreadNotMaterialized).pipe(Effect.map((reply) => reply.tail)),
4659
+ export: (request) => request.threadId === options.localThreadId ? local.export(request) : foreignStoreCall("thread export", request.threadId, StoreExportCall.make({ request }), StoreExportResult, ThreadNotMaterialized).pipe(Effect.map((reply) => reply.export)),
4660
+ observe: (request) => request.threadId === options.localThreadId ? local.observe(request) : Stream.unwrap(Effect.fail(crossThreadStoreError("thread observe", request.threadId))),
4661
+ ...checkpoints === void 0 ? {} : { checkpoints: {
4662
+ save: (request) => request.checkpoint.threadId === options.localThreadId ? checkpoints.save(request) : Effect.fail(crossThreadStoreError("thread save checkpoint", request.checkpoint.threadId)),
4663
+ load: (request) => request.threadId === options.localThreadId ? checkpoints.load(request) : Effect.fail(crossThreadStoreError("thread load checkpoint", request.threadId))
4664
+ } }
4665
+ });
4666
+ return Context.make(ThreadStore, routed);
3990
4667
  });
3991
4668
  /**
3992
4669
  * Routing decorator over the LOCAL `SubmissionLedger` facet (plan §1.3): a request addressing
3993
- * this Object's Conversation executes locally; a route-capable request addressing another
3994
- * Conversation is Schema-encoded onto the `ConversationPortTransport` and executed by the
4670
+ * this Object's Thread executes locally; a route-capable request addressing another
4671
+ * Thread is Schema-encoded onto the `ThreadPortTransport` and executed by the
3995
4672
  * owning Object's local facet; any other foreign request fails fast typed. Provide the WP1
3996
4673
  * local facet (`submissionLedgerLayer`/`ledgerLayer`) and a transport to close it.
3997
4674
  */
3998
4675
  const routedSubmissionLedgerLayer = (options) => Layer.effectContext(makeRoutedLedgerServices(options));
3999
4676
  /**
4000
- * Routing decorator over the LOCAL `ConversationStore` facet (plan §1.3): this-conversation
4677
+ * Routing decorator over the LOCAL `ThreadStore` facet (plan §1.3): this-thread
4001
4678
  * requests execute locally; foreign materialize/append/read/inspectTail/export travel the
4002
4679
  * transport; foreign observation and checkpoints fail fast typed.
4003
4680
  */
4004
- const routedConversationStoreLayer = (options) => Layer.effectContext(makeRoutedStoreServices(options));
4681
+ const routedThreadStoreLayer = (options) => Layer.effectContext(makeRoutedStoreServices(options));
4005
4682
  /** Fold one port operation's typed failures into the uniform response envelope. */
4006
4683
  const capture = (effect) => effect.pipe(Effect.map((result) => PortSucceeded.make({ result })), Effect.catch((failure) => Effect.succeed(PortFailed.make({ failure }))));
4007
4684
  /**
4008
4685
  * Execute one decoded port request against THIS Object's LOCAL facets — the owner-side half
4009
4686
  * of the routed ports (plan §1.3). Callers must provide the WP1 local facets, never the
4010
4687
  * routed decorators: the routing layer already established that this Object owns the
4011
- * addressed Conversation, and re-routing here could bounce a request between Objects.
4688
+ * addressed Thread, and re-routing here could bounce a request between Objects.
4012
4689
  * Failures never escape — every typed port failure becomes a `PortFailed` envelope that
4013
4690
  * re-decodes on the caller side.
4014
4691
  */
@@ -4039,24 +4716,24 @@ const executePortRequest = Effect.fn("DoPortRouting.executePortRequest")(functio
4039
4716
  return yield* capture(ledger.recordChildSettled(request.request).pipe(Effect.map((outcome) => LedgerRecordChildSettledResult.make({ outcome }))));
4040
4717
  }
4041
4718
  case "StoreMaterialize": {
4042
- const store = yield* ConversationStore;
4719
+ const store = yield* ThreadStore;
4043
4720
  return yield* capture(store.materialize(request.request).pipe(Effect.map(() => StoreMaterializeResult.make({}))));
4044
4721
  }
4045
4722
  case "StoreAppend": {
4046
- const store = yield* ConversationStore;
4723
+ const store = yield* ThreadStore;
4047
4724
  return yield* capture(store.append(request.request).pipe(Effect.map((result) => StoreAppendResult.make({ result }))));
4048
4725
  }
4049
4726
  case "StoreReadPage": {
4050
- const store = yield* ConversationStore;
4727
+ const store = yield* ThreadStore;
4051
4728
  return yield* capture(store.read(request.request).pipe(Stream.runCollect, Effect.map((records) => StoreReadPageResult.make({ records: [...records] }))));
4052
4729
  }
4053
4730
  case "StoreInspectTail": {
4054
- const store = yield* ConversationStore;
4731
+ const store = yield* ThreadStore;
4055
4732
  return yield* capture(store.inspectTail(request.request).pipe(Effect.map((tail) => StoreInspectTailResult.make({ tail }))));
4056
4733
  }
4057
4734
  case "StoreExport": {
4058
- const store = yield* ConversationStore;
4059
- return yield* capture(store.export(request.request).pipe(Effect.map((conversationExport) => StoreExportResult.make({ export: conversationExport }))));
4735
+ const store = yield* ThreadStore;
4736
+ return yield* capture(store.export(request.request).pipe(Effect.map((threadExport) => StoreExportResult.make({ export: threadExport }))));
4060
4737
  }
4061
4738
  }
4062
4739
  });
@@ -4083,6 +4760,6 @@ const handleEncodedPortRequest = Effect.fn("DoPortRouting.handleEncodedPortReque
4083
4760
  return yield* encodePortResponse(yield* decodePortRequest(encoded).pipe(Effect.flatMap(executePortRequest), Effect.catch((error) => Effect.succeed(PortFailed.make({ failure: PortProtocolError.make({ message: boundPortDiagnostic(`The port request could not be decoded: ${error.message}`) }) }))))).pipe(Effect.catch((error) => Effect.succeed(encodedProtocolFailure(`The port response could not be encoded: ${error.message}`))));
4084
4761
  });
4085
4762
  //#endregion
4086
- export { ConversationPortTransport, CurrentDoStorageVersion, DEFAULT_MAX_STORED_VALUE_BYTES, DoAppendConflict, DoCheckpointConflict, DoFenceRejected, DoLedgerError, DoScheduleAlarmControl, DoScheduleTransaction, DoStorageCompatibilityError, DoStorageConfig, DoStorageConfigValue, DoStorageCorruptionError, DoStorageError, DoStorageFailpoint, DoStorageFailpointError, DoStorageFailpointLocation, DoStorageFailpointTestControl, DoValueBoundExceeded, LedgerAdmitCall, LedgerAdmitResult, LedgerLookupCall, LedgerLookupResult, LedgerMarkReadyCall, LedgerMarkReadyResult, LedgerRecordChildSettledCall, LedgerRecordChildSettledResult, LedgerRequestAbortCall, LedgerRequestAbortResult, LedgerResolveAdmissionCall, LedgerResolveAdmissionResult, MAX_PORT_DIAGNOSTIC_LENGTH, PortFailed, PortFailure, PortProtocolError, PortRequest, PortResponse, PortResult, PortSucceeded, PortTransportError, StoreAppendCall, StoreAppendResult, StoreExportCall, StoreExportResult, StoreInspectTailCall, StoreInspectTailResult, StoreMaterializeCall, StoreMaterializeResult, StoreReadPageCall, StoreReadPageResult, boundPortDiagnostic, conversationStoreLayer, decodePortRequest, decodePortResponse, doMigrations, encodePortRequest, encodePortResponse, evictionFailpointHandler, executePortRequest, handleEncodedPortRequest, layer, ledgerLayer, observationOffsetAt, portTransportFailure, routedConversationStoreLayer, routedSubmissionLedgerLayer, scheduleStoreLayer, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer };
4763
+ export { CurrentDoStorageVersion, DEFAULT_MAX_STORED_VALUE_BYTES, DoAppendConflict, DoCheckpointConflict, DoFenceRejected, DoLedgerError, DoScheduleAlarmControl, DoScheduleTransaction, DoStorageCompatibilityError, DoStorageConfig, DoStorageConfigValue, DoStorageCorruptionError, DoStorageError, DoStorageFailpoint, DoStorageFailpointError, DoStorageFailpointLocation, DoSubscriptionAlarmControl, DoSubscriptionTransaction, DoValueBoundExceeded, LedgerAdmitCall, LedgerAdmitResult, LedgerLookupCall, LedgerLookupResult, LedgerMarkReadyCall, LedgerMarkReadyResult, LedgerRecordChildSettledCall, LedgerRecordChildSettledResult, LedgerRequestAbortCall, LedgerRequestAbortResult, LedgerResolveAdmissionCall, LedgerResolveAdmissionResult, MAX_PORT_DIAGNOSTIC_LENGTH, PortFailed, PortFailure, PortProtocolError, PortRequest, PortResponse, PortResult, PortSucceeded, PortTransportError, StoreAppendCall, StoreAppendResult, StoreExportCall, StoreExportResult, StoreInspectTailCall, StoreInspectTailResult, StoreMaterializeCall, StoreMaterializeResult, StoreReadPageCall, StoreReadPageResult, ThreadPortTransport, boundPortDiagnostic, decodePortRequest, decodePortResponse, doSubscriptionStoreLayer, encodePortRequest, encodePortResponse, executePortRequest, handleEncodedPortRequest, layer, ledgerLayer, observationOffsetAt, portTransportFailure, routedSubmissionLedgerLayer, routedThreadStoreLayer, scheduleStoreLayer, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer, threadStoreLayer };
4087
4764
 
4088
4765
  //# sourceMappingURL=index.mjs.map