@effect-agent/storage-cloudflare 0.1.0-beta.42 → 0.1.0-beta.45
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.map +1 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -52
- package/src/do-journal.ts +42 -0
- package/src/do-ledger.ts +309 -0
- package/src/do-schedule-store.ts +49 -0
- package/src/do-storage-failpoint-testing.ts +1 -0
- package/src/do-subscription-store.ts +153 -0
- package/src/do-thread-store.ts +74 -0
- package/src/errors.ts +1 -0
- package/src/memory-protocol.ts +24 -0
- package/src/port-protocol.ts +3 -0
- package/src/routing.ts +41 -0
package/src/do-thread-store.ts
CHANGED
|
@@ -140,10 +140,13 @@ const parseOffset = Effect.fn(function* (
|
|
|
140
140
|
offset: ObservationOffset | undefined,
|
|
141
141
|
): Effect.fn.Return<CanonicalSequence, ThreadStoreError> {
|
|
142
142
|
if (offset === undefined) return ZERO_CANONICAL_SEQUENCE;
|
|
143
|
+
|
|
143
144
|
const text = yield* Schema.decodeUnknownEffect(OffsetText)(offset).pipe(
|
|
144
145
|
Effect.mapError((error) => schemaStoreError("decode observation offset", error)),
|
|
145
146
|
);
|
|
147
|
+
|
|
146
148
|
const threadPrefix = `${DO_OFFSET_PREFIX}${encodeURIComponent(threadId)}:`;
|
|
149
|
+
|
|
147
150
|
if (!text.startsWith(threadPrefix)) {
|
|
148
151
|
return yield* ThreadStoreError.make({
|
|
149
152
|
operation: "decode observation offset",
|
|
@@ -151,12 +154,14 @@ const parseOffset = Effect.fn(function* (
|
|
|
151
154
|
});
|
|
152
155
|
}
|
|
153
156
|
const sequenceText = text.slice(threadPrefix.length);
|
|
157
|
+
|
|
154
158
|
if (!/^(0|[1-9][0-9]*)$/.test(sequenceText)) {
|
|
155
159
|
return yield* ThreadStoreError.make({
|
|
156
160
|
operation: "decode observation offset",
|
|
157
161
|
message: "The observation offset is malformed.",
|
|
158
162
|
});
|
|
159
163
|
}
|
|
164
|
+
|
|
160
165
|
return yield* Schema.decodeUnknownEffect(CanonicalSequence)(Number(sequenceText)).pipe(
|
|
161
166
|
Effect.mapError((error) => schemaStoreError("decode observation offset", error)),
|
|
162
167
|
);
|
|
@@ -209,13 +214,17 @@ const decodeEnvelope = Effect.fn(function* (row: {
|
|
|
209
214
|
}),
|
|
210
215
|
),
|
|
211
216
|
);
|
|
217
|
+
|
|
212
218
|
const threadId = yield* Schema.decodeUnknownEffect(CanonicalRecordEnvelope.fields.threadId)(
|
|
213
219
|
row.thread_id,
|
|
214
220
|
).pipe(Effect.mapError((error) => schemaStoreError("decode thread identity", error)));
|
|
221
|
+
|
|
215
222
|
const offset = yield* makeOffset(threadId, row.sequence);
|
|
223
|
+
|
|
216
224
|
const batchId = yield* Schema.decodeUnknownEffect(CanonicalRecordEnvelope.fields.batchId)(
|
|
217
225
|
row.batch_id,
|
|
218
226
|
).pipe(Effect.mapError((error) => schemaStoreError("decode batch identity", error)));
|
|
227
|
+
|
|
219
228
|
return CanonicalRecordEnvelope.make({
|
|
220
229
|
threadId,
|
|
221
230
|
batchId,
|
|
@@ -240,9 +249,11 @@ const requireThread = Effect.fn("DoThreadStore.requireThread")(function* (
|
|
|
240
249
|
const rows = yield* journal
|
|
241
250
|
.getThread(threadId)
|
|
242
251
|
.pipe(Effect.mapError((error) => storeError("read thread", error)));
|
|
252
|
+
|
|
243
253
|
if (rows.length === 0) {
|
|
244
254
|
return yield* ThreadNotMaterialized.make({ threadId });
|
|
245
255
|
}
|
|
256
|
+
|
|
246
257
|
return rows[0];
|
|
247
258
|
});
|
|
248
259
|
|
|
@@ -252,15 +263,18 @@ const tailDigestAt = Effect.fn("DoThreadStore.tailDigestAt")(function* (
|
|
|
252
263
|
sequence: CanonicalSequence,
|
|
253
264
|
) {
|
|
254
265
|
if (sequence === 0) return EMPTY_TAIL_DIGEST;
|
|
266
|
+
|
|
255
267
|
const digests = yield* journal
|
|
256
268
|
.getTailDigestAt(threadId, sequence)
|
|
257
269
|
.pipe(Effect.mapError((error) => storeError("read checkpoint digest", error)));
|
|
270
|
+
|
|
258
271
|
if (digests.length !== 1) {
|
|
259
272
|
return yield* CheckpointRejected.make({
|
|
260
273
|
threadId,
|
|
261
274
|
reason: "digest-mismatch",
|
|
262
275
|
});
|
|
263
276
|
}
|
|
277
|
+
|
|
264
278
|
return yield* Schema.decodeUnknownEffect(Digest)(digests[0]).pipe(
|
|
265
279
|
Effect.mapError((error) => schemaStoreError("decode checkpoint digest", error)),
|
|
266
280
|
);
|
|
@@ -271,14 +285,17 @@ const groupByKey = <A>(
|
|
|
271
285
|
key: (row: A) => string,
|
|
272
286
|
): ReadonlyMap<string, ReadonlyArray<A>> => {
|
|
273
287
|
const grouped = new Map<string, Array<A>>();
|
|
288
|
+
|
|
274
289
|
for (const row of rows) {
|
|
275
290
|
const existing = grouped.get(key(row));
|
|
291
|
+
|
|
276
292
|
if (existing === undefined) {
|
|
277
293
|
grouped.set(key(row), [row]);
|
|
278
294
|
} else {
|
|
279
295
|
existing.push(row);
|
|
280
296
|
}
|
|
281
297
|
}
|
|
298
|
+
|
|
282
299
|
return grouped;
|
|
283
300
|
};
|
|
284
301
|
|
|
@@ -292,6 +309,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
292
309
|
crypto: Crypto.Crypto,
|
|
293
310
|
) {
|
|
294
311
|
const stored = yield* journal.scanStoredPayloads();
|
|
312
|
+
|
|
295
313
|
const batches = yield* Effect.forEach(stored.batches, (batch) =>
|
|
296
314
|
Schema.decodeEffect(Schema.fromJsonString(CanonicalBatch))(batch.batch_json).pipe(
|
|
297
315
|
Effect.map((decoded) => ({ decoded, row: batch })),
|
|
@@ -304,6 +322,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
304
322
|
),
|
|
305
323
|
),
|
|
306
324
|
);
|
|
325
|
+
|
|
307
326
|
const records = yield* Effect.forEach(stored.records, (record) =>
|
|
308
327
|
Schema.decodeEffect(Schema.fromJsonString(CanonicalRecord))(record.record_json).pipe(
|
|
309
328
|
Effect.map((decoded) => ({ decoded, row: record })),
|
|
@@ -316,6 +335,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
316
335
|
),
|
|
317
336
|
),
|
|
318
337
|
);
|
|
338
|
+
|
|
319
339
|
const checkpoints = yield* Effect.forEach(stored.checkpoints, (checkpoint) =>
|
|
320
340
|
Schema.decodeEffect(Schema.fromJsonString(ThreadCheckpoint))(checkpoint.checkpoint_json).pipe(
|
|
321
341
|
Effect.map((decoded) => ({ decoded, row: checkpoint })),
|
|
@@ -345,6 +365,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
345
365
|
|
|
346
366
|
for (const { decoded: canonicalBatch, row: batchRow } of threadBatches) {
|
|
347
367
|
const key = `${batchRow.thread_id}/${batchRow.batch_id}`;
|
|
368
|
+
|
|
348
369
|
if (
|
|
349
370
|
canonicalBatch.batchId !== batchRow.batch_id ||
|
|
350
371
|
batchRow.first_sequence !== expectedSequence ||
|
|
@@ -367,6 +388,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
367
388
|
}),
|
|
368
389
|
),
|
|
369
390
|
);
|
|
391
|
+
|
|
370
392
|
if (batchRow.batch_digest !== digest || batchRow.tail_digest !== digest) {
|
|
371
393
|
return yield* DoStorageCorruptionError.make({
|
|
372
394
|
table: "effect_agent_canonical_batches",
|
|
@@ -376,6 +398,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
376
398
|
}
|
|
377
399
|
|
|
378
400
|
const batchRecords = recordsByBatch.get(batchRow.batch_id) ?? [];
|
|
401
|
+
|
|
379
402
|
if (batchRecords.length !== canonicalBatch.records.length) {
|
|
380
403
|
return yield* DoStorageCorruptionError.make({
|
|
381
404
|
table: "effect_agent_canonical_records",
|
|
@@ -386,6 +409,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
386
409
|
for (let index = 0; index < canonicalBatch.records.length; index++) {
|
|
387
410
|
const expectedRecord = canonicalBatch.records[index];
|
|
388
411
|
const storedRecord = batchRecords[index];
|
|
412
|
+
|
|
389
413
|
const expectedJson = yield* Schema.encodeEffect(Schema.fromJsonString(CanonicalRecord))(
|
|
390
414
|
expectedRecord,
|
|
391
415
|
).pipe(
|
|
@@ -397,6 +421,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
397
421
|
}),
|
|
398
422
|
),
|
|
399
423
|
);
|
|
424
|
+
|
|
400
425
|
const storedJson = yield* Schema.encodeEffect(Schema.fromJsonString(CanonicalRecord))(
|
|
401
426
|
storedRecord.decoded,
|
|
402
427
|
).pipe(
|
|
@@ -408,6 +433,7 @@ const decodeStartupPayloads = Effect.fn("DoThreadStore.decodeStartupPayloads")(f
|
|
|
408
433
|
}),
|
|
409
434
|
),
|
|
410
435
|
);
|
|
436
|
+
|
|
411
437
|
if (
|
|
412
438
|
storedRecord.row.sequence !== batchRow.first_sequence + index ||
|
|
413
439
|
storedRecord.row.record_id !== expectedRecord.recordId ||
|
|
@@ -473,12 +499,14 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
473
499
|
const sql = yield* SqlClientService.SqlClient;
|
|
474
500
|
const crypto = yield* Crypto.Crypto;
|
|
475
501
|
const journal = yield* initializeDoJournal(sql, failpoint.hit, config.maxStoredValueBytes);
|
|
502
|
+
|
|
476
503
|
if (config.verifyOnOpen) {
|
|
477
504
|
yield* decodeStartupPayloads(journal, crypto);
|
|
478
505
|
}
|
|
479
506
|
|
|
480
507
|
const provideCrypto = <A, E>(effect: Effect.Effect<A, E, Crypto.Crypto>) =>
|
|
481
508
|
Effect.provideService(effect, Crypto.Crypto, crypto);
|
|
509
|
+
|
|
482
510
|
const hitFailpoint = Effect.fn(
|
|
483
511
|
(location: DoStorageFailpointLocation): Effect.Effect<void, ThreadStoreError> =>
|
|
484
512
|
failpoint
|
|
@@ -491,7 +519,9 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
491
519
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadMaterialization))(
|
|
492
520
|
request,
|
|
493
521
|
).pipe(Effect.mapError((error) => schemaStoreError("validate materialization", error)));
|
|
522
|
+
|
|
494
523
|
const now = yield* Clock.currentTimeMillis;
|
|
524
|
+
|
|
495
525
|
yield* hitFailpoint("materialize:before");
|
|
496
526
|
yield* journal
|
|
497
527
|
.materialize(
|
|
@@ -517,11 +547,15 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
517
547
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(FencedAppendRequest))(
|
|
518
548
|
request,
|
|
519
549
|
).pipe(Effect.mapError((error) => schemaStoreError("validate canonical append", error)));
|
|
550
|
+
|
|
520
551
|
yield* requireThread(journal, validated.threadId);
|
|
552
|
+
|
|
521
553
|
const tailDigest = yield* provideCrypto(
|
|
522
554
|
digestCanonicalBatch(validated.expectedTailDigest, validated.batch),
|
|
523
555
|
).pipe(Effect.mapError((error) => storeError("digest canonical append", error)));
|
|
556
|
+
|
|
524
557
|
const batchJson = yield* encodeCanonicalBatch(validated.batch);
|
|
558
|
+
|
|
525
559
|
const rawRecords = yield* Effect.forEach(validated.batch.records, (record) =>
|
|
526
560
|
encodeCanonicalRecord(record).pipe(
|
|
527
561
|
Effect.map((recordJson) => ({
|
|
@@ -530,6 +564,7 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
530
564
|
})),
|
|
531
565
|
),
|
|
532
566
|
);
|
|
567
|
+
|
|
533
568
|
const rawRequest = yield* Schema.decodeUnknownEffect(RawAppendRequest)({
|
|
534
569
|
threadId: validated.threadId,
|
|
535
570
|
batchId: validated.batch.batchId,
|
|
@@ -541,7 +576,9 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
541
576
|
records: rawRecords,
|
|
542
577
|
tailDigest,
|
|
543
578
|
}).pipe(Effect.mapError((error) => schemaStoreError("encode canonical append", error)));
|
|
579
|
+
|
|
544
580
|
yield* hitFailpoint("append:before");
|
|
581
|
+
|
|
545
582
|
const result = yield* journal.append(rawRequest).pipe(
|
|
546
583
|
Effect.mapError((error) => {
|
|
547
584
|
if (isDoFenceRejected(error)) {
|
|
@@ -562,6 +599,7 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
562
599
|
reason: error.reason,
|
|
563
600
|
});
|
|
564
601
|
}
|
|
602
|
+
|
|
565
603
|
return storeError("append canonical batch", error);
|
|
566
604
|
}),
|
|
567
605
|
Effect.flatMap((result) =>
|
|
@@ -570,7 +608,9 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
570
608
|
),
|
|
571
609
|
),
|
|
572
610
|
);
|
|
611
|
+
|
|
573
612
|
yield* hitFailpoint("append:after");
|
|
613
|
+
|
|
574
614
|
return result;
|
|
575
615
|
});
|
|
576
616
|
|
|
@@ -578,6 +618,7 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
578
618
|
const rows = yield* journal
|
|
579
619
|
.read(request)
|
|
580
620
|
.pipe(Effect.mapError((error) => storeError("read canonical records", error)));
|
|
621
|
+
|
|
581
622
|
return yield* Effect.forEach(rows, decodeEnvelope);
|
|
582
623
|
});
|
|
583
624
|
|
|
@@ -585,7 +626,9 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
585
626
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadRead))(request).pipe(
|
|
586
627
|
Effect.mapError((error) => schemaStoreError("validate thread read", error)),
|
|
587
628
|
);
|
|
629
|
+
|
|
588
630
|
yield* requireThread(journal, validated.threadId);
|
|
631
|
+
|
|
589
632
|
const records = yield* loadRecords(
|
|
590
633
|
RawReadRequest.make({
|
|
591
634
|
threadId: validated.threadId,
|
|
@@ -593,19 +636,24 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
593
636
|
limit: validated.limit,
|
|
594
637
|
}),
|
|
595
638
|
);
|
|
639
|
+
|
|
596
640
|
return Stream.fromIterable(records);
|
|
597
641
|
});
|
|
642
|
+
|
|
598
643
|
const read: ThreadStore["Service"]["read"] = (request) => Stream.unwrap(readEffect(request));
|
|
599
644
|
|
|
600
645
|
const observeEffect = Effect.fn("DoThreadStore.observe")(function* (request: ThreadObservation) {
|
|
601
646
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadObservation))(
|
|
602
647
|
request,
|
|
603
648
|
).pipe(Effect.mapError((error) => schemaStoreError("validate thread observation", error)));
|
|
649
|
+
|
|
604
650
|
yield* requireThread(journal, validated.threadId);
|
|
605
651
|
const initialSequence = yield* parseOffset(validated.threadId, validated.afterOffset);
|
|
606
652
|
const cursor = yield* Ref.make(initialSequence);
|
|
653
|
+
|
|
607
654
|
const poll = Effect.fn("DoThreadStore.observePoll")(function* () {
|
|
608
655
|
const fromSequenceExclusive = yield* Ref.get(cursor);
|
|
656
|
+
|
|
609
657
|
const records = yield* loadRecords(
|
|
610
658
|
RawReadRequest.make({
|
|
611
659
|
threadId: validated.threadId,
|
|
@@ -613,15 +661,20 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
613
661
|
limit: 1_024,
|
|
614
662
|
}),
|
|
615
663
|
);
|
|
664
|
+
|
|
616
665
|
if (records.length === 0) {
|
|
617
666
|
yield* Effect.sleep(config.observationPollInterval);
|
|
667
|
+
|
|
618
668
|
return [];
|
|
619
669
|
}
|
|
620
670
|
yield* Ref.set(cursor, records[records.length - 1].sequence);
|
|
671
|
+
|
|
621
672
|
return records;
|
|
622
673
|
});
|
|
674
|
+
|
|
623
675
|
return Stream.fromIterableEffectRepeat(poll());
|
|
624
676
|
});
|
|
677
|
+
|
|
625
678
|
const observe: ThreadStore["Service"]["observe"] = (request) =>
|
|
626
679
|
Stream.unwrap(observeEffect(request));
|
|
627
680
|
|
|
@@ -630,20 +683,26 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
630
683
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadExportRequest))(
|
|
631
684
|
request,
|
|
632
685
|
).pipe(Effect.mapError((error) => schemaStoreError("validate thread export", error)));
|
|
686
|
+
|
|
633
687
|
yield* requireThread(journal, validated.threadId);
|
|
688
|
+
|
|
634
689
|
const exported = yield* journal
|
|
635
690
|
.exportThread(validated.threadId)
|
|
636
691
|
.pipe(Effect.mapError((error) => storeError("export thread", error)));
|
|
692
|
+
|
|
637
693
|
const records = yield* Effect.forEach(exported.records, decodeEnvelope);
|
|
694
|
+
|
|
638
695
|
if (records.length > 65_536) {
|
|
639
696
|
return yield* ThreadStoreError.make({
|
|
640
697
|
operation: "decode thread export",
|
|
641
698
|
message: "The thread exceeds the current export record limit.",
|
|
642
699
|
});
|
|
643
700
|
}
|
|
701
|
+
|
|
644
702
|
const tailDigest = yield* Schema.decodeUnknownEffect(Digest)(
|
|
645
703
|
exported.thread.tail_digest,
|
|
646
704
|
).pipe(Effect.mapError((error) => schemaStoreError("decode export tail digest", error)));
|
|
705
|
+
|
|
647
706
|
return ThreadExport.make({
|
|
648
707
|
format: "effect-agent/thread@1",
|
|
649
708
|
threadId: validated.threadId,
|
|
@@ -659,10 +718,13 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
659
718
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ThreadTailRequest))(
|
|
660
719
|
request,
|
|
661
720
|
).pipe(Effect.mapError((error) => schemaStoreError("validate tail inspection", error)));
|
|
721
|
+
|
|
662
722
|
const thread = yield* requireThread(journal, validated.threadId);
|
|
723
|
+
|
|
663
724
|
const tailDigest = yield* Schema.decodeUnknownEffect(Digest)(thread.tail_digest).pipe(
|
|
664
725
|
Effect.mapError((error) => schemaStoreError("decode tail digest", error)),
|
|
665
726
|
);
|
|
727
|
+
|
|
666
728
|
return ThreadTail.make({
|
|
667
729
|
threadId: validated.threadId,
|
|
668
730
|
tailSequence: thread.tail_sequence,
|
|
@@ -677,18 +739,22 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
677
739
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SaveCheckpointRequest))(
|
|
678
740
|
request,
|
|
679
741
|
).pipe(Effect.mapError((error) => schemaStoreError("validate checkpoint", error)));
|
|
742
|
+
|
|
680
743
|
const thread = yield* requireThread(journal, validated.checkpoint.threadId);
|
|
744
|
+
|
|
681
745
|
if (validated.checkpoint.throughSequence > thread.tail_sequence) {
|
|
682
746
|
return yield* CheckpointRejected.make({
|
|
683
747
|
threadId: validated.checkpoint.threadId,
|
|
684
748
|
reason: "ahead-of-tail",
|
|
685
749
|
});
|
|
686
750
|
}
|
|
751
|
+
|
|
687
752
|
const canonicalDigest = yield* tailDigestAt(
|
|
688
753
|
journal,
|
|
689
754
|
validated.checkpoint.threadId,
|
|
690
755
|
validated.checkpoint.throughSequence,
|
|
691
756
|
);
|
|
757
|
+
|
|
692
758
|
if (canonicalDigest !== validated.checkpoint.tailDigest) {
|
|
693
759
|
return yield* CheckpointRejected.make({
|
|
694
760
|
threadId: validated.checkpoint.threadId,
|
|
@@ -696,12 +762,14 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
696
762
|
});
|
|
697
763
|
}
|
|
698
764
|
const checkpointJson = yield* encodeCheckpoint(validated.checkpoint);
|
|
765
|
+
|
|
699
766
|
const raw = RawCheckpoint.make({
|
|
700
767
|
threadId: validated.checkpoint.threadId,
|
|
701
768
|
throughSequence: validated.checkpoint.throughSequence,
|
|
702
769
|
tailDigest: validated.checkpoint.tailDigest,
|
|
703
770
|
checkpointJson,
|
|
704
771
|
});
|
|
772
|
+
|
|
705
773
|
yield* hitFailpoint("save-checkpoint:before");
|
|
706
774
|
yield* journal.saveCheckpoint(raw).pipe(
|
|
707
775
|
Effect.mapError((error) =>
|
|
@@ -722,10 +790,13 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
722
790
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(LoadCheckpointRequest))(
|
|
723
791
|
request,
|
|
724
792
|
).pipe(Effect.mapError((error) => schemaStoreError("validate checkpoint lookup", error)));
|
|
793
|
+
|
|
725
794
|
const thread = yield* requireThread(journal, validated.threadId);
|
|
795
|
+
|
|
726
796
|
const rows = yield* journal
|
|
727
797
|
.loadCheckpoint(validated.threadId, validated.atOrBeforeSequence ?? thread.tail_sequence)
|
|
728
798
|
.pipe(Effect.mapError((error) => storeError("load checkpoint", error)));
|
|
799
|
+
|
|
729
800
|
if (rows.length === 0) return Option.none();
|
|
730
801
|
if (rows.length !== 1) {
|
|
731
802
|
return yield* ThreadStoreError.make({
|
|
@@ -734,17 +805,20 @@ const makeServices = Effect.fn("DoThreadStore.makeServices")(function* () {
|
|
|
734
805
|
});
|
|
735
806
|
}
|
|
736
807
|
const checkpoint = yield* decodeCheckpoint(rows[0].checkpoint_json);
|
|
808
|
+
|
|
737
809
|
const canonicalDigest = yield* tailDigestAt(
|
|
738
810
|
journal,
|
|
739
811
|
checkpoint.threadId,
|
|
740
812
|
checkpoint.throughSequence,
|
|
741
813
|
);
|
|
814
|
+
|
|
742
815
|
if (canonicalDigest !== checkpoint.tailDigest) {
|
|
743
816
|
return yield* CheckpointRejected.make({
|
|
744
817
|
threadId: checkpoint.threadId,
|
|
745
818
|
reason: "digest-mismatch",
|
|
746
819
|
});
|
|
747
820
|
}
|
|
821
|
+
|
|
748
822
|
return Option.some(checkpoint);
|
|
749
823
|
},
|
|
750
824
|
);
|
package/src/errors.ts
CHANGED
|
@@ -155,6 +155,7 @@ export const DoStorageFailpointLocation = Schema.Literals([
|
|
|
155
155
|
"ledger:child-settled:before",
|
|
156
156
|
"ledger:child-settled:after",
|
|
157
157
|
]);
|
|
158
|
+
|
|
158
159
|
export type DoStorageFailpointLocation = typeof DoStorageFailpointLocation.Type;
|
|
159
160
|
|
|
160
161
|
/** Deterministic test-only fault or pause injected at a Durable Object storage boundary. */
|
package/src/memory-protocol.ts
CHANGED
|
@@ -60,7 +60,9 @@ const RevalidateRequest = Schema.TaggedStruct("Revalidate", {
|
|
|
60
60
|
lookup: MemoryLookup,
|
|
61
61
|
limits: MemoryRecallLimits,
|
|
62
62
|
});
|
|
63
|
+
|
|
63
64
|
const ChangeRequest = Schema.TaggedStruct("Change", { ...RequestFields, write: MemoryWrite.Wire });
|
|
65
|
+
|
|
64
66
|
const SemanticRequest: Schema.TaggedStruct<
|
|
65
67
|
"RevalidateSemantic",
|
|
66
68
|
typeof RequestFields & {
|
|
@@ -74,9 +76,11 @@ const SemanticRequest: Schema.TaggedStruct<
|
|
|
74
76
|
profile: SemanticMemoryProfile,
|
|
75
77
|
limits: SemanticCandidateLimits,
|
|
76
78
|
});
|
|
79
|
+
|
|
77
80
|
export const MemoryOwnerRequest: Schema.Union<
|
|
78
81
|
[typeof RevalidateRequest, typeof ChangeRequest, typeof SemanticRequest]
|
|
79
82
|
> = Schema.Union([RevalidateRequest, ChangeRequest, SemanticRequest]);
|
|
83
|
+
|
|
80
84
|
export type MemoryOwnerRequest = typeof MemoryOwnerRequest.Type;
|
|
81
85
|
|
|
82
86
|
export const MemoryOwnerFailure = Schema.Union([
|
|
@@ -90,6 +94,7 @@ export const MemoryOwnerFailure = Schema.Union([
|
|
|
90
94
|
MemoryIndexError,
|
|
91
95
|
SemanticMemoryError,
|
|
92
96
|
]);
|
|
97
|
+
|
|
93
98
|
export type MemoryOwnerFailure = typeof MemoryOwnerFailure.Type;
|
|
94
99
|
|
|
95
100
|
export const MemoryOwnerResponse = Schema.Union([
|
|
@@ -98,6 +103,7 @@ export const MemoryOwnerResponse = Schema.Union([
|
|
|
98
103
|
Schema.TaggedStruct("Semantic", { access: MemoryAccess.Wire, result: SemanticCandidateResult }),
|
|
99
104
|
Schema.TaggedStruct("Failed", { failure: MemoryOwnerFailure }),
|
|
100
105
|
]);
|
|
106
|
+
|
|
101
107
|
export type MemoryOwnerResponse = typeof MemoryOwnerResponse.Type;
|
|
102
108
|
|
|
103
109
|
/** Fail-closed application policy. Authorize the namespace, principal, scope, and full command. */
|
|
@@ -126,8 +132,10 @@ export const decodeMemoryWire = Effect.fn("decodeMemoryWire")(function* <A, I>(
|
|
|
126
132
|
const text = yield* Schema.decodeUnknownEffect(Schema.String)(raw).pipe(
|
|
127
133
|
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
128
134
|
);
|
|
135
|
+
|
|
129
136
|
if (text.length > maxBytes || memoryWireBytes(text) > maxBytes)
|
|
130
137
|
return yield* MemoryRpcError.make({ reason: "budget" });
|
|
138
|
+
|
|
131
139
|
return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(text).pipe(
|
|
132
140
|
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
133
141
|
);
|
|
@@ -141,8 +149,10 @@ export const encodeMemoryWire = Effect.fn("encodeMemoryWire")(function* <A, I>(
|
|
|
141
149
|
const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(schema))(value).pipe(
|
|
142
150
|
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
143
151
|
);
|
|
152
|
+
|
|
144
153
|
if (encoded.length > maxBytes || memoryWireBytes(encoded) > maxBytes)
|
|
145
154
|
return yield* MemoryRpcError.make({ reason: "budget" });
|
|
155
|
+
|
|
146
156
|
return encoded;
|
|
147
157
|
});
|
|
148
158
|
|
|
@@ -165,6 +175,7 @@ export const handleMemoryOwnerRequest = Effect.fn("MemoryOwner.handleRequest")(f
|
|
|
165
175
|
);
|
|
166
176
|
const request = yield* decodeMemoryWire(MemoryOwnerRequest, raw, limits.maxRequestBytes);
|
|
167
177
|
const { namespace } = yield* MemoryOwnerIdentity;
|
|
178
|
+
|
|
168
179
|
if (
|
|
169
180
|
!MemoryNamespace.equals(namespace, request.access.namespace) ||
|
|
170
181
|
(request._tag === "Change" &&
|
|
@@ -175,20 +186,25 @@ export const handleMemoryOwnerRequest = Effect.fn("MemoryOwner.handleRequest")(f
|
|
|
175
186
|
))
|
|
176
187
|
)
|
|
177
188
|
return yield* MemoryRpcError.make({ reason: "denied" });
|
|
189
|
+
|
|
178
190
|
const remaining = Math.min(
|
|
179
191
|
limits.timeoutMillis,
|
|
180
192
|
request.deadlineMillis - (yield* Clock.currentTimeMillis),
|
|
181
193
|
);
|
|
194
|
+
|
|
182
195
|
if (remaining <= 0) return yield* MemoryRpcError.make({ reason: "timeout" });
|
|
196
|
+
|
|
183
197
|
return yield* Effect.gen(function* (): Effect.fn.Return<
|
|
184
198
|
MemoryOwnerResponse,
|
|
185
199
|
MemoryOwnerFailure,
|
|
186
200
|
MemoryOwnerAuthorizer | MemoryReader | MemoryWriter
|
|
187
201
|
> {
|
|
188
202
|
const authorizer = yield* MemoryOwnerAuthorizer;
|
|
203
|
+
|
|
189
204
|
yield* authorizer.authorize(request);
|
|
190
205
|
if (request._tag === "Change") {
|
|
191
206
|
const writer = yield* MemoryWriter;
|
|
207
|
+
|
|
192
208
|
return {
|
|
193
209
|
_tag: "Changed",
|
|
194
210
|
access: request.access,
|
|
@@ -201,6 +217,7 @@ export const handleMemoryOwnerRequest = Effect.fn("MemoryOwner.handleRequest")(f
|
|
|
201
217
|
limits.maxSources
|
|
202
218
|
)
|
|
203
219
|
return yield* MemoryRpcError.make({ reason: "budget" });
|
|
220
|
+
|
|
204
221
|
const result = yield* revalidateSemanticMemoryCandidates(
|
|
205
222
|
request.found,
|
|
206
223
|
request.access,
|
|
@@ -217,18 +234,23 @@ export const handleMemoryOwnerRequest = Effect.fn("MemoryOwner.handleRequest")(f
|
|
|
217
234
|
),
|
|
218
235
|
},
|
|
219
236
|
);
|
|
237
|
+
|
|
220
238
|
return { _tag: "Semantic", access: request.access, result };
|
|
221
239
|
}
|
|
240
|
+
|
|
222
241
|
const count =
|
|
223
242
|
request.lookup._tag === "Found"
|
|
224
243
|
? new Set(request.lookup.passages.map((passage) => passage.source.id)).size
|
|
225
244
|
: 0;
|
|
245
|
+
|
|
226
246
|
if (count > Math.min(limits.maxSources, request.limits.maxSources))
|
|
227
247
|
return yield* MemoryRpcError.make({ reason: "budget" });
|
|
248
|
+
|
|
228
249
|
const lookup = yield* revalidateMemoryLookup(request.lookup, request.access, {
|
|
229
250
|
maxSourceBytes: limits.maxSourceBytes,
|
|
230
251
|
maxInputBytes: Math.min(limits.maxSourceBytes, request.limits.maxInputBytes ?? 16_777_216),
|
|
231
252
|
});
|
|
253
|
+
|
|
232
254
|
return { _tag: "Lookup", access: request.access, lookup };
|
|
233
255
|
}).pipe(
|
|
234
256
|
Effect.scoped,
|
|
@@ -243,7 +265,9 @@ export const handleMemoryOwnerRequest = Effect.fn("MemoryOwner.handleRequest")(f
|
|
|
243
265
|
),
|
|
244
266
|
Effect.result,
|
|
245
267
|
);
|
|
268
|
+
|
|
246
269
|
if (result._tag === "Success") return result.success;
|
|
270
|
+
|
|
247
271
|
return yield* encodeMemoryWire(
|
|
248
272
|
MemoryOwnerResponse,
|
|
249
273
|
{
|
package/src/port-protocol.ts
CHANGED
|
@@ -177,6 +177,7 @@ export const PortRequest = Schema.Union([
|
|
|
177
177
|
StoreInspectTailCall,
|
|
178
178
|
StoreExportCall,
|
|
179
179
|
]);
|
|
180
|
+
|
|
180
181
|
export type PortRequest = typeof PortRequest.Type;
|
|
181
182
|
|
|
182
183
|
/** The wire form of one port request (what a transport actually carries). */
|
|
@@ -264,6 +265,7 @@ export const PortResult = Schema.Union([
|
|
|
264
265
|
StoreInspectTailResult,
|
|
265
266
|
StoreExportResult,
|
|
266
267
|
]);
|
|
268
|
+
|
|
267
269
|
export type PortResult = typeof PortResult.Type;
|
|
268
270
|
|
|
269
271
|
// ---------------------------------------------------------------------------
|
|
@@ -286,6 +288,7 @@ export const PortFailure = Schema.Union([
|
|
|
286
288
|
FenceRejected,
|
|
287
289
|
PortProtocolError,
|
|
288
290
|
]);
|
|
291
|
+
|
|
289
292
|
export type PortFailure = typeof PortFailure.Type;
|
|
290
293
|
|
|
291
294
|
/** The routed operation succeeded on its owning Object. */
|