@effect-agent/storage-cloudflare 0.1.0-beta.36 → 0.1.0-beta.37
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.d.mts +31 -2
- package/dist/index.mjs +393 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/do-schedule-store.ts +530 -0
- package/src/index.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -1,5 +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, Settlement, SettlementConflict, SettlementFinalization, SettlementOutcome, SettlementReservation, SettlementReservationSnapshot, SubmissionLedger, SubmissionLookup, SubmissionLookupById, SubmissionLookupByKey, SubmissionSnapshot, SubmissionState, SuspendRequest, SuspensionReason, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, digestCanonicalBatch, settlementFailureFromRecord, submissionAbortRecordId } from "@effect-agent/session";
|
|
2
|
-
import { Clock, Context, Crypto, DateTime, Duration, Effect, Layer, Option, Predicate, Ref, Schema, Stream } from "effect";
|
|
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";
|
|
2
|
+
import { Clock, Context, Crypto, DateTime, Duration, Effect, Layer, Option, Predicate, Ref, Result, Schema, Stream } from "effect";
|
|
3
3
|
import { SqliteClient, SqliteMigrator } from "@effect/sql-sqlite-do";
|
|
4
4
|
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
5
5
|
import { BrowserCrypto } from "@effect/platform-browser";
|
|
@@ -477,13 +477,13 @@ const storageError = (operation) => (error) => DoStorageError.make({
|
|
|
477
477
|
message: error.message
|
|
478
478
|
});
|
|
479
479
|
/** Decode raw Durable Object SQLite rows against a Schema, reporting failures as typed corruption. */
|
|
480
|
-
const decodeRows = Effect.fn((schema, table, rowKey, rows) => Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError((error) => DoStorageCorruptionError.make({
|
|
480
|
+
const decodeRows$1 = Effect.fn((schema, table, rowKey, rows) => Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError((error) => DoStorageCorruptionError.make({
|
|
481
481
|
table,
|
|
482
482
|
rowKey,
|
|
483
483
|
message: String(error)
|
|
484
484
|
}))));
|
|
485
485
|
/** Decode exactly one raw row against a Schema, reporting failures as typed corruption. */
|
|
486
|
-
const decodeSingleRow = Effect.fn((schema, table, rowKey, rows) => decodeRows(schema, table, rowKey, rows).pipe(Effect.flatMap((decoded) => decoded.length === 1 ? Effect.succeed(decoded[0]) : Effect.fail(DoStorageCorruptionError.make({
|
|
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
487
|
table,
|
|
488
488
|
rowKey,
|
|
489
489
|
message: `Expected exactly one row but found ${decoded.length}.`
|
|
@@ -518,7 +518,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
518
518
|
WHERE type = 'table'
|
|
519
519
|
AND name = 'effect_agent_meta'
|
|
520
520
|
`.pipe(Effect.mapError(storageError("read storage version table")));
|
|
521
|
-
if ((yield* decodeRows(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_meta", metaTableRows)).length === 0) {
|
|
521
|
+
if ((yield* decodeRows$1(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_meta", metaTableRows)).length === 0) {
|
|
522
522
|
const existingRows = yield* sql`
|
|
523
523
|
SELECT name
|
|
524
524
|
FROM sqlite_master
|
|
@@ -526,7 +526,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
526
526
|
AND name LIKE 'effect_agent_%'
|
|
527
527
|
ORDER BY name
|
|
528
528
|
`.pipe(Effect.mapError(storageError("inspect unversioned storage")));
|
|
529
|
-
if ((yield* decodeRows(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_%", existingRows)).length > 0) return yield* DoStorageCompatibilityError.make({
|
|
529
|
+
if ((yield* decodeRows$1(Schema.Array(DoNameRow), "sqlite_master", "effect_agent_%", existingRows)).length > 0) return yield* DoStorageCompatibilityError.make({
|
|
530
530
|
actualVersion: 0,
|
|
531
531
|
supportedVersion: 1,
|
|
532
532
|
message: "The Durable Object contains unversioned Effect Agent tables. Reset the development namespace explicitly; refusing to mutate ambiguous stored data."
|
|
@@ -559,7 +559,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
559
559
|
AND name IN ${sql.in([...REQUIRED_TABLES])}
|
|
560
560
|
ORDER BY name
|
|
561
561
|
`.pipe(Effect.mapError(storageError("verify storage tables")));
|
|
562
|
-
if ((yield* decodeRows(Schema.Array(DoNameRow), "sqlite_master", "required_tables", requiredRows)).length !== REQUIRED_TABLES.length) return yield* DoStorageCompatibilityError.make({
|
|
562
|
+
if ((yield* decodeRows$1(Schema.Array(DoNameRow), "sqlite_master", "required_tables", requiredRows)).length !== REQUIRED_TABLES.length) return yield* DoStorageCompatibilityError.make({
|
|
563
563
|
actualVersion: 1,
|
|
564
564
|
supportedVersion: 1,
|
|
565
565
|
message: "The Durable Object claims the current format but is missing required tables. Reset the corrupt private-development data."
|
|
@@ -606,7 +606,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
606
606
|
FROM effect_agent_conversations
|
|
607
607
|
WHERE conversation_id = ${conversationId}
|
|
608
608
|
`.pipe(Effect.mapError(storageError("read materialized conversation")));
|
|
609
|
-
const existing = yield* decodeRows(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, existingRows);
|
|
609
|
+
const existing = yield* decodeRows$1(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, existingRows);
|
|
610
610
|
if (existing.length > 1) return yield* DoStorageCorruptionError.make({
|
|
611
611
|
table: "effect_agent_conversations",
|
|
612
612
|
rowKey: conversationId,
|
|
@@ -653,7 +653,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
653
653
|
FROM effect_agent_conversations
|
|
654
654
|
WHERE conversation_id = ${conversationId}
|
|
655
655
|
`.pipe(Effect.mapError(storageError("read conversation")));
|
|
656
|
-
return yield* decodeRows(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, rows);
|
|
656
|
+
return yield* decodeRows$1(Schema.Array(ConversationRow), "effect_agent_conversations", conversationId, rows);
|
|
657
657
|
});
|
|
658
658
|
const append = Effect.fn("DoJournal.append")(function* (request) {
|
|
659
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({
|
|
@@ -699,7 +699,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
699
699
|
WHERE conversation_id = ${request.conversationId}
|
|
700
700
|
AND batch_id = ${request.batchId}
|
|
701
701
|
`.pipe(Effect.mapError(storageError("read idempotent batch")));
|
|
702
|
-
const batches = yield* decodeRows(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${request.conversationId}/${request.batchId}`, batchRows);
|
|
702
|
+
const batches = yield* decodeRows$1(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${request.conversationId}/${request.batchId}`, batchRows);
|
|
703
703
|
if (batches.length > 1) return yield* DoStorageCorruptionError.make({
|
|
704
704
|
table: "effect_agent_canonical_batches",
|
|
705
705
|
rowKey: `${request.conversationId}/${request.batchId}`,
|
|
@@ -742,7 +742,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
742
742
|
AND record_id IN ${sql.in([...chunk])}
|
|
743
743
|
ORDER BY sequence
|
|
744
744
|
`.pipe(Effect.mapError(storageError("check canonical record identities")));
|
|
745
|
-
existingRecords.push(...yield* decodeRows(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.conversationId}/record_ids`, existingRecordRows));
|
|
745
|
+
existingRecords.push(...yield* decodeRows$1(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.conversationId}/record_ids`, existingRecordRows));
|
|
746
746
|
}
|
|
747
747
|
if (existingRecords.length > 0) return yield* DoAppendConflict.make({
|
|
748
748
|
message: `Canonical record ID ${existingRecords[0].record_id} already exists.`,
|
|
@@ -827,7 +827,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
827
827
|
ORDER BY sequence
|
|
828
828
|
LIMIT ${request.limit}
|
|
829
829
|
`.pipe(Effect.mapError(storageError("read canonical records")));
|
|
830
|
-
return yield* decodeRows(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.conversationId}>${request.fromSequenceExclusive}`, rows);
|
|
830
|
+
return yield* decodeRows$1(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.conversationId}>${request.fromSequenceExclusive}`, rows);
|
|
831
831
|
});
|
|
832
832
|
const exportConversation = Effect.fn("DoJournal.exportConversation")(function* (conversationId) {
|
|
833
833
|
return yield* sql.withTransaction(Effect.gen(function* () {
|
|
@@ -879,9 +879,9 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
879
879
|
`.pipe(Effect.mapError(storageError("export checkpoints")));
|
|
880
880
|
return RawConversationExport.make({
|
|
881
881
|
conversation,
|
|
882
|
-
batches: yield* decodeRows(Schema.Array(BatchRow), "effect_agent_canonical_batches", conversationId, batchRows),
|
|
883
|
-
records: yield* decodeRows(Schema.Array(RecordRow), "effect_agent_canonical_records", conversationId, recordRows),
|
|
884
|
-
checkpoints: yield* decodeRows(Schema.Array(CheckpointRow), "effect_agent_checkpoints", conversationId, checkpointRows)
|
|
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)
|
|
885
885
|
});
|
|
886
886
|
})).pipe(Effect.catchTag("SqlError", (error) => Effect.fail(storageError("export transaction")(error))));
|
|
887
887
|
});
|
|
@@ -914,7 +914,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
914
914
|
WHERE conversation_id = ${checkpoint.conversationId}
|
|
915
915
|
AND through_sequence = ${checkpoint.throughSequence}
|
|
916
916
|
`.pipe(Effect.mapError(storageError("read idempotent checkpoint")));
|
|
917
|
-
const existing = yield* decodeRows(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${checkpoint.conversationId}/${checkpoint.throughSequence}`, checkpointRows);
|
|
917
|
+
const existing = yield* decodeRows$1(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${checkpoint.conversationId}/${checkpoint.throughSequence}`, checkpointRows);
|
|
918
918
|
if (existing.length > 1) return yield* DoStorageCorruptionError.make({
|
|
919
919
|
table: "effect_agent_checkpoints",
|
|
920
920
|
rowKey: `${checkpoint.conversationId}/${checkpoint.throughSequence}`,
|
|
@@ -952,7 +952,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
952
952
|
ORDER BY through_sequence DESC
|
|
953
953
|
LIMIT 1
|
|
954
954
|
`.pipe(Effect.mapError(storageError("load checkpoint")));
|
|
955
|
-
return yield* decodeRows(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${conversationId}<=${atOrBeforeSequence}`, rows);
|
|
955
|
+
return yield* decodeRows$1(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${conversationId}<=${atOrBeforeSequence}`, rows);
|
|
956
956
|
});
|
|
957
957
|
return {
|
|
958
958
|
append,
|
|
@@ -977,7 +977,7 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
977
977
|
WHERE conversation_id = ${conversationId}
|
|
978
978
|
AND last_sequence = ${sequence}
|
|
979
979
|
`.pipe(Effect.mapError(storageError("read canonical digest at sequence")));
|
|
980
|
-
return (yield* decodeRows(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${conversationId}/${sequence}`, rows)).map((batch) => batch.tail_digest);
|
|
980
|
+
return (yield* decodeRows$1(Schema.Array(BatchRow), "effect_agent_canonical_batches", `${conversationId}/${sequence}`, rows)).map((batch) => batch.tail_digest);
|
|
981
981
|
}),
|
|
982
982
|
loadCheckpoint,
|
|
983
983
|
materialize,
|
|
@@ -1027,10 +1027,10 @@ const makeJournal = (sql, failpoint, maxStoredValueBytes) => {
|
|
|
1027
1027
|
ORDER BY conversation_id, through_sequence
|
|
1028
1028
|
`.pipe(Effect.mapError(storageError("scan checkpoints")));
|
|
1029
1029
|
return {
|
|
1030
|
-
conversations: yield* decodeRows(Schema.Array(ConversationRow), "effect_agent_conversations", "startup_scan", conversations),
|
|
1031
|
-
batches: yield* decodeRows(Schema.Array(BatchRow), "effect_agent_canonical_batches", "startup_scan", batches),
|
|
1032
|
-
records: yield* decodeRows(Schema.Array(RecordRow), "effect_agent_canonical_records", "startup_scan", records),
|
|
1033
|
-
checkpoints: yield* decodeRows(Schema.Array(CheckpointRow), "effect_agent_checkpoints", "startup_scan", checkpoints)
|
|
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)
|
|
1034
1034
|
};
|
|
1035
1035
|
})).pipe(Effect.catchTag("SqlError", (error) => Effect.fail(storageError("startup scan transaction")(error))));
|
|
1036
1036
|
}),
|
|
@@ -1313,7 +1313,7 @@ const decodeStartupPayloads = Effect.fn("DoConversationStore.decodeStartupPayloa
|
|
|
1313
1313
|
message: "Canonical rows exist without a materialized Conversation."
|
|
1314
1314
|
});
|
|
1315
1315
|
});
|
|
1316
|
-
const makeServices$
|
|
1316
|
+
const makeServices$2 = Effect.fn("DoConversationStore.makeServices")(function* () {
|
|
1317
1317
|
const config = yield* DoStorageConfig;
|
|
1318
1318
|
const failpoint = yield* DoStorageFailpoint;
|
|
1319
1319
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -1491,7 +1491,7 @@ const makeServices$1 = Effect.fn("DoConversationStore.makeServices")(function* (
|
|
|
1491
1491
|
* Durable Object Conversation Store implementation with configuration, failpoint, SQL, and
|
|
1492
1492
|
* Crypto authority kept visible in its input channel.
|
|
1493
1493
|
*/
|
|
1494
|
-
const conversationStoreLayer = Layer.effectContext(makeServices$
|
|
1494
|
+
const conversationStoreLayer = Layer.effectContext(makeServices$2());
|
|
1495
1495
|
/**
|
|
1496
1496
|
* Validated Durable Object storage configuration Layer with the documented defaults applied.
|
|
1497
1497
|
* Shared by the ConversationStore and SubmissionLedger convenience layers so their defaults
|
|
@@ -1717,7 +1717,7 @@ const corruptionFailure = (operation, table, rowKey, message) => internalFailure
|
|
|
1717
1717
|
rowKey,
|
|
1718
1718
|
message
|
|
1719
1719
|
}));
|
|
1720
|
-
const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
1720
|
+
const makeServices$1 = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
1721
1721
|
const config = yield* DoStorageConfig;
|
|
1722
1722
|
const failpoint = yield* DoStorageFailpoint;
|
|
1723
1723
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -1736,7 +1736,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1736
1736
|
iso: new Date(millis).toISOString()
|
|
1737
1737
|
}));
|
|
1738
1738
|
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(Schema.Array(SubmissionRow), "effect_agent_submissions", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1739
|
+
const decodeSubmissionRows = (operation, rowKey, rows) => decodeRows$1(Schema.Array(SubmissionRow), "effect_agent_submissions", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1740
1740
|
const readSubmission = Effect.fn("DoSubmissionLedger.readSubmission")(function* (operation, submissionId) {
|
|
1741
1741
|
const rows = yield* sql`
|
|
1742
1742
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
@@ -1767,7 +1767,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1767
1767
|
FROM effect_agent_submission_ownership
|
|
1768
1768
|
WHERE submission_id = ${submissionId}
|
|
1769
1769
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1770
|
-
const decoded = yield* decodeRows(Schema.Array(OwnershipRow), "effect_agent_submission_ownership", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1770
|
+
const decoded = yield* decodeRows$1(Schema.Array(OwnershipRow), "effect_agent_submission_ownership", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1771
1771
|
if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submission_ownership", submissionId, "An ownership primary key returned more than one row.");
|
|
1772
1772
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
1773
1773
|
});
|
|
@@ -1832,7 +1832,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1832
1832
|
FROM effect_agent_settlement_reservations
|
|
1833
1833
|
WHERE submission_id = ${submissionId}
|
|
1834
1834
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1835
|
-
const decoded = yield* decodeRows(Schema.Array(ReservationRow), "effect_agent_settlement_reservations", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1835
|
+
const decoded = yield* decodeRows$1(Schema.Array(ReservationRow), "effect_agent_settlement_reservations", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1836
1836
|
if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_settlement_reservations", submissionId, "A settlement reservation primary key returned more than one row.");
|
|
1837
1837
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
1838
1838
|
});
|
|
@@ -1847,7 +1847,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1847
1847
|
FROM effect_agent_abort_intents
|
|
1848
1848
|
WHERE submission_id = ${submissionId}
|
|
1849
1849
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1850
|
-
const decoded = yield* decodeRows(Schema.Array(AbortIntentRow), "effect_agent_abort_intents", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1850
|
+
const decoded = yield* decodeRows$1(Schema.Array(AbortIntentRow), "effect_agent_abort_intents", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1851
1851
|
if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_abort_intents", submissionId, "An abort intent primary key returned more than one row.");
|
|
1852
1852
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
1853
1853
|
});
|
|
@@ -1867,7 +1867,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1867
1867
|
WHERE parent_submission_id = ${parentSubmissionId}
|
|
1868
1868
|
ORDER BY child_submission_id ASC
|
|
1869
1869
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1870
|
-
return yield* decodeRows(Schema.Array(ChildSettlementMarkerRow), "effect_agent_child_settlements", parentSubmissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1870
|
+
return yield* decodeRows$1(Schema.Array(ChildSettlementMarkerRow), "effect_agent_child_settlements", parentSubmissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1871
1871
|
});
|
|
1872
1872
|
/**
|
|
1873
1873
|
* Whether one listed child is provably settled from THIS store: either its own row lives
|
|
@@ -1880,7 +1880,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1880
1880
|
const childRow = yield* readSubmission(operation, childSubmissionId);
|
|
1881
1881
|
return Option.isSome(childRow) && childRow.value.state === "settled";
|
|
1882
1882
|
});
|
|
1883
|
-
const decodeChildReservationRows = (operation, rowKey, rows) => decodeRows(Schema.Array(ChildReservationRow), "effect_agent_child_reservations", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1883
|
+
const decodeChildReservationRows = (operation, rowKey, rows) => decodeRows$1(Schema.Array(ChildReservationRow), "effect_agent_child_reservations", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1884
1884
|
const readChildReservation = Effect.fn("DoSubmissionLedger.readChildReservation")(function* (operation, reservationId) {
|
|
1885
1885
|
const rows = yield* sql`
|
|
1886
1886
|
SELECT ${sql.literal(CHILD_RESERVATION_COLUMNS)}
|
|
@@ -1933,7 +1933,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1933
1933
|
WHERE submission_id = ${submissionId}
|
|
1934
1934
|
ORDER BY tool_call_id ASC
|
|
1935
1935
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1936
|
-
return yield* decodeRows(Schema.Array(ApprovalDecisionRow), "effect_agent_approval_decisions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1936
|
+
return yield* decodeRows$1(Schema.Array(ApprovalDecisionRow), "effect_agent_approval_decisions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1937
1937
|
});
|
|
1938
1938
|
const approvalIntentFromRow = Effect.fn("DoSubmissionLedger.approvalIntentFromRow")(function* (operation, row) {
|
|
1939
1939
|
return yield* decodeApprovalDecisionIntent({
|
|
@@ -1958,7 +1958,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1958
1958
|
WHERE submission_id = ${submissionId}
|
|
1959
1959
|
ORDER BY tool_call_id ASC
|
|
1960
1960
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1961
|
-
return yield* decodeRows(Schema.Array(UnknownResolutionRow), "effect_agent_unknown_resolutions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1961
|
+
return yield* decodeRows$1(Schema.Array(UnknownResolutionRow), "effect_agent_unknown_resolutions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1962
1962
|
});
|
|
1963
1963
|
const unknownResolutionIntentFromRow = Effect.fn("DoSubmissionLedger.unknownResolutionIntentFromRow")(function* (operation, row) {
|
|
1964
1964
|
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)));
|
|
@@ -1989,7 +1989,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1989
1989
|
WHERE conversation_id = ${conversationId}
|
|
1990
1990
|
AND record_id = ${recordId}
|
|
1991
1991
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1992
|
-
return (yield* decodeRows(Schema.Array(CanonicalRecordIdRow), "effect_agent_canonical_records", `${conversationId}/${recordId}`, rows).pipe(Effect.mapError(internalFailure(operation)))).length === 0 ? void 0 : recordId;
|
|
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;
|
|
1993
1993
|
});
|
|
1994
1994
|
const abortIntentFromRow = Effect.fn("DoSubmissionLedger.abortIntentFromRow")(function* (operation, submission, submissionId, row) {
|
|
1995
1995
|
const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.conversation_id, submissionId);
|
|
@@ -2048,7 +2048,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2048
2048
|
FROM effect_agent_submissions
|
|
2049
2049
|
WHERE conversation_id = ${validated.conversationId}
|
|
2050
2050
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2051
|
-
const decodedMax = yield* decodeRows(Schema.Array(MaxQueueSequenceRow), "effect_agent_submissions", validated.conversationId, maxRows).pipe(Effect.mapError(internalFailure(operation)));
|
|
2051
|
+
const decodedMax = yield* decodeRows$1(Schema.Array(MaxQueueSequenceRow), "effect_agent_submissions", validated.conversationId, maxRows).pipe(Effect.mapError(internalFailure(operation)));
|
|
2052
2052
|
const queueSequence = yield* decodeQueueSequence((decodedMax[0]?.max_queue_sequence ?? 0) + 1).pipe(Effect.mapError(internalFailure(operation)));
|
|
2053
2053
|
const now = yield* currentInstant;
|
|
2054
2054
|
yield* sql`
|
|
@@ -3208,13 +3208,369 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
3208
3208
|
* database, storage-backed transaction discipline, and producer-epoch fencing substrate.
|
|
3209
3209
|
* Configuration, failpoint, SQL, and Crypto authority stay visible in the input channel.
|
|
3210
3210
|
*/
|
|
3211
|
-
const submissionLedgerLayer = Layer.effectContext(makeServices());
|
|
3211
|
+
const submissionLedgerLayer = Layer.effectContext(makeServices$1());
|
|
3212
3212
|
/**
|
|
3213
3213
|
* A composition-root convenience Layer for the durable Submission Ledger. Point it at the
|
|
3214
3214
|
* same `ctx.storage` as the ConversationStore so claims fence the same producer epochs.
|
|
3215
3215
|
*/
|
|
3216
3216
|
const ledgerLayer = (options) => Layer.unwrap(Effect.map(DoStorageConfig, (config) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(DoStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ storage: options.storage }), BrowserCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
|
|
3217
3217
|
//#endregion
|
|
3218
|
+
//#region src/do-schedule-store.ts
|
|
3219
|
+
const CURRENT_SCHEDULE_STORE_VERSION = 1;
|
|
3220
|
+
const StoredScheduleJson = Schema.String.check(Schema.isMaxLength(19e5));
|
|
3221
|
+
const StoredDeadline = Schema.NullOr(ScheduleInstant);
|
|
3222
|
+
var ScheduleRow = class extends Schema.Class("@effect-agent/storage-cloudflare/ScheduleRow")({
|
|
3223
|
+
tenant_id: ScheduleOwner.fields.tenantId,
|
|
3224
|
+
owner_id: ScheduleOwner.fields.ownerId,
|
|
3225
|
+
schedule_id: ScheduleId,
|
|
3226
|
+
deadline_at_millis: StoredDeadline,
|
|
3227
|
+
record_json: StoredScheduleJson
|
|
3228
|
+
}) {};
|
|
3229
|
+
const ScheduleDueRow = Schema.Struct({
|
|
3230
|
+
tenant_id: ScheduleOwner.fields.tenantId,
|
|
3231
|
+
owner_id: ScheduleOwner.fields.ownerId,
|
|
3232
|
+
schedule_id: ScheduleId,
|
|
3233
|
+
deadline_at_millis: ScheduleInstant
|
|
3234
|
+
});
|
|
3235
|
+
var ScheduleCountRow = class extends Schema.Class("@effect-agent/storage-cloudflare/ScheduleCountRow")({ schedule_count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) }) {};
|
|
3236
|
+
var ScheduleDeadlineRow = class extends Schema.Class("@effect-agent/storage-cloudflare/ScheduleDeadlineRow")({ deadline_at_millis: StoredDeadline }) {};
|
|
3237
|
+
var ScheduleStoreStateRow = class extends Schema.Class("@effect-agent/storage-cloudflare/ScheduleStoreStateRow")({
|
|
3238
|
+
storage_version: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
3239
|
+
alarm_generation: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
3240
|
+
}) {};
|
|
3241
|
+
var ScheduleTableNameRow = class extends Schema.Class("@effect-agent/storage-cloudflare/ScheduleTableNameRow")({ name: Schema.String }) {};
|
|
3242
|
+
/**
|
|
3243
|
+
* Platform-owned transaction boundary for schedule SQL and logical alarm mutation. The callback
|
|
3244
|
+
* and its `replaceAlarm` capability belong to one fiber and must not escape or fork.
|
|
3245
|
+
*/
|
|
3246
|
+
var DoScheduleTransaction = class extends Context.Service()("@effect-agent/storage-cloudflare/DoScheduleTransaction") {};
|
|
3247
|
+
/** Platform driver operations that use the same schedule-state and alarm transaction. */
|
|
3248
|
+
var DoScheduleAlarmControl = class extends Context.Service()("@effect-agent/storage-cloudflare/DoScheduleAlarmControl") {};
|
|
3249
|
+
const unavailable = (operation) => ScheduleStorageError.make({
|
|
3250
|
+
operation,
|
|
3251
|
+
reason: "unavailable"
|
|
3252
|
+
});
|
|
3253
|
+
const corrupt = (operation) => ScheduleStorageError.make({
|
|
3254
|
+
operation,
|
|
3255
|
+
reason: "corrupt"
|
|
3256
|
+
});
|
|
3257
|
+
const decodeRows = Effect.fn("DoScheduleStore.decodeRows")(function* (schema, rows, operation) {
|
|
3258
|
+
return yield* Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError(() => corrupt(operation)));
|
|
3259
|
+
});
|
|
3260
|
+
const decodeBoundary = (schema, value, operation) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => corrupt(operation)));
|
|
3261
|
+
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");
|
|
3264
|
+
return record;
|
|
3265
|
+
});
|
|
3266
|
+
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")));
|
|
3269
|
+
});
|
|
3270
|
+
const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function* () {
|
|
3271
|
+
const sql = yield* SqlClient.SqlClient;
|
|
3272
|
+
const operation = "initialize schedule store";
|
|
3273
|
+
const rawTables = yield* sql`
|
|
3274
|
+
SELECT name
|
|
3275
|
+
FROM sqlite_master
|
|
3276
|
+
WHERE type = 'table'
|
|
3277
|
+
AND name IN (
|
|
3278
|
+
'effect_agent_schedule_store_state',
|
|
3279
|
+
'effect_agent_schedules'
|
|
3280
|
+
)
|
|
3281
|
+
ORDER BY name
|
|
3282
|
+
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
3283
|
+
const tables = yield* decodeRows(Schema.Array(ScheduleTableNameRow), rawTables, operation);
|
|
3284
|
+
const hasState = tables.some((row) => row.name === "effect_agent_schedule_store_state");
|
|
3285
|
+
const hasSchedules = tables.some((row) => row.name === "effect_agent_schedules");
|
|
3286
|
+
if (!hasState) {
|
|
3287
|
+
if (hasSchedules) return yield* corrupt(operation);
|
|
3288
|
+
yield* sql.withTransaction(Effect.gen(function* () {
|
|
3289
|
+
yield* sql`
|
|
3290
|
+
CREATE TABLE effect_agent_schedule_store_state (
|
|
3291
|
+
singleton INTEGER PRIMARY KEY NOT NULL CHECK (singleton = 1),
|
|
3292
|
+
storage_version INTEGER NOT NULL,
|
|
3293
|
+
alarm_generation INTEGER NOT NULL
|
|
3294
|
+
)
|
|
3295
|
+
`.withoutTransform;
|
|
3296
|
+
yield* sql`
|
|
3297
|
+
CREATE TABLE effect_agent_schedules (
|
|
3298
|
+
tenant_id TEXT NOT NULL,
|
|
3299
|
+
owner_id TEXT NOT NULL,
|
|
3300
|
+
schedule_id TEXT NOT NULL,
|
|
3301
|
+
deadline_at_millis INTEGER,
|
|
3302
|
+
record_json TEXT NOT NULL,
|
|
3303
|
+
PRIMARY KEY (tenant_id, owner_id, schedule_id)
|
|
3304
|
+
)
|
|
3305
|
+
`.withoutTransform;
|
|
3306
|
+
yield* sql`
|
|
3307
|
+
CREATE INDEX effect_agent_schedules_deadline
|
|
3308
|
+
ON effect_agent_schedules (deadline_at_millis, tenant_id, owner_id, schedule_id)
|
|
3309
|
+
WHERE deadline_at_millis IS NOT NULL
|
|
3310
|
+
`.withoutTransform;
|
|
3311
|
+
yield* sql`
|
|
3312
|
+
CREATE INDEX effect_agent_schedules_owner_deadline
|
|
3313
|
+
ON effect_agent_schedules (tenant_id, owner_id, deadline_at_millis, schedule_id)
|
|
3314
|
+
WHERE deadline_at_millis IS NOT NULL
|
|
3315
|
+
`.withoutTransform;
|
|
3316
|
+
yield* sql`
|
|
3317
|
+
INSERT INTO effect_agent_schedule_store_state (
|
|
3318
|
+
singleton, storage_version, alarm_generation
|
|
3319
|
+
) VALUES (1, ${CURRENT_SCHEDULE_STORE_VERSION}, 0)
|
|
3320
|
+
`.withoutTransform;
|
|
3321
|
+
})).pipe(Effect.mapError(() => unavailable(operation)));
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
if (!hasSchedules) return yield* corrupt(operation);
|
|
3325
|
+
const rawState = yield* sql`
|
|
3326
|
+
SELECT storage_version, alarm_generation
|
|
3327
|
+
FROM effect_agent_schedule_store_state
|
|
3328
|
+
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);
|
|
3332
|
+
});
|
|
3333
|
+
const makeServices = Effect.gen(function* () {
|
|
3334
|
+
const sql = yield* SqlClient.SqlClient;
|
|
3335
|
+
const transactions = yield* DoScheduleTransaction;
|
|
3336
|
+
const scheduleFailpoint = yield* ScheduleFailpoint;
|
|
3337
|
+
yield* initializeScheduleStore();
|
|
3338
|
+
const readRows = Effect.fn("DoScheduleStore.readRows")(function* (key, operation) {
|
|
3339
|
+
const rows = yield* sql`
|
|
3340
|
+
SELECT tenant_id, owner_id, schedule_id, deadline_at_millis, record_json
|
|
3341
|
+
FROM effect_agent_schedules
|
|
3342
|
+
WHERE tenant_id = ${key.owner.tenantId}
|
|
3343
|
+
AND owner_id = ${key.owner.ownerId}
|
|
3344
|
+
AND schedule_id = ${key.scheduleId}
|
|
3345
|
+
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
3346
|
+
return yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
|
|
3347
|
+
});
|
|
3348
|
+
const readOne = Effect.fn("DoScheduleStore.readOne")(function* (key, operation) {
|
|
3349
|
+
const rows = yield* readRows(key, operation);
|
|
3350
|
+
if (rows.length === 0) return null;
|
|
3351
|
+
if (rows.length !== 1) return yield* corrupt(operation);
|
|
3352
|
+
return yield* decodeRecord(rows[0]);
|
|
3353
|
+
});
|
|
3354
|
+
const readNextDeadline = Effect.fn("DoScheduleStore.readNextDeadline")(function* (owner, operation) {
|
|
3355
|
+
const rows = owner === void 0 ? yield* sql`
|
|
3356
|
+
SELECT MIN(deadline_at_millis) AS deadline_at_millis
|
|
3357
|
+
FROM effect_agent_schedules
|
|
3358
|
+
WHERE deadline_at_millis IS NOT NULL
|
|
3359
|
+
`.pipe(Effect.mapError(() => unavailable(operation))) : yield* sql`
|
|
3360
|
+
SELECT MIN(deadline_at_millis) AS deadline_at_millis
|
|
3361
|
+
FROM effect_agent_schedules
|
|
3362
|
+
WHERE tenant_id = ${owner.tenantId}
|
|
3363
|
+
AND owner_id = ${owner.ownerId}
|
|
3364
|
+
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);
|
|
3368
|
+
return decoded[0].deadline_at_millis;
|
|
3369
|
+
});
|
|
3370
|
+
const replaceAlarm = Effect.fn("DoScheduleStore.replaceAlarm")(function* (replace, deadlineAtMillis, operation) {
|
|
3371
|
+
const rawState = yield* sql`
|
|
3372
|
+
UPDATE effect_agent_schedule_store_state
|
|
3373
|
+
SET alarm_generation = alarm_generation + 1
|
|
3374
|
+
WHERE singleton = 1
|
|
3375
|
+
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);
|
|
3379
|
+
yield* scheduleFailpoint.hit("schedule:alarm:before");
|
|
3380
|
+
yield* replace({
|
|
3381
|
+
deadlineAtMillis,
|
|
3382
|
+
generation: state[0].alarm_generation
|
|
3383
|
+
});
|
|
3384
|
+
yield* scheduleFailpoint.hit("schedule:alarm:after");
|
|
3385
|
+
});
|
|
3386
|
+
const insert = Effect.fn("DoScheduleStore.insert")(function* (record, ownerLimit) {
|
|
3387
|
+
const operation = "insert schedule";
|
|
3388
|
+
const canonical = yield* decodeBoundary(ScheduleRecord, record, operation);
|
|
3389
|
+
const recordJson = yield* encodeRecord(canonical);
|
|
3390
|
+
const result = yield* transactions.run((replace) => Effect.gen(function* () {
|
|
3391
|
+
const existing = yield* readOne(canonical, operation);
|
|
3392
|
+
if (existing !== null) {
|
|
3393
|
+
if (existing.creationFingerprint === canonical.creationFingerprint) return {
|
|
3394
|
+
record: existing,
|
|
3395
|
+
inserted: false
|
|
3396
|
+
};
|
|
3397
|
+
return yield* ScheduleConflict.make({
|
|
3398
|
+
reason: "creation",
|
|
3399
|
+
key: canonical
|
|
3400
|
+
});
|
|
3401
|
+
}
|
|
3402
|
+
const rawCounts = yield* sql`
|
|
3403
|
+
SELECT COUNT(*) AS schedule_count
|
|
3404
|
+
FROM effect_agent_schedules
|
|
3405
|
+
WHERE tenant_id = ${canonical.owner.tenantId}
|
|
3406
|
+
AND owner_id = ${canonical.owner.ownerId}
|
|
3407
|
+
AND (json_extract(record_json, '$.pending') IS NOT NULL OR
|
|
3408
|
+
(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);
|
|
3412
|
+
if (counts[0].schedule_count >= ownerLimit) return yield* ScheduleCapacityError.make({ limit: ownerLimit });
|
|
3413
|
+
yield* scheduleFailpoint.hit("schedule:insert:before");
|
|
3414
|
+
yield* sql`
|
|
3415
|
+
INSERT INTO effect_agent_schedules (
|
|
3416
|
+
tenant_id, owner_id, schedule_id, deadline_at_millis, record_json
|
|
3417
|
+
) VALUES (
|
|
3418
|
+
${canonical.owner.tenantId},
|
|
3419
|
+
${canonical.owner.ownerId},
|
|
3420
|
+
${canonical.scheduleId},
|
|
3421
|
+
${scheduleDeadline(canonical)},
|
|
3422
|
+
${recordJson}
|
|
3423
|
+
)
|
|
3424
|
+
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
3425
|
+
const deadline = yield* readNextDeadline(void 0, operation);
|
|
3426
|
+
yield* replaceAlarm(replace, deadline, operation);
|
|
3427
|
+
return {
|
|
3428
|
+
record: canonical,
|
|
3429
|
+
inserted: true
|
|
3430
|
+
};
|
|
3431
|
+
}));
|
|
3432
|
+
if (result.inserted) yield* scheduleFailpoint.hit("schedule:insert:after");
|
|
3433
|
+
return result.record;
|
|
3434
|
+
});
|
|
3435
|
+
const get = Effect.fn("DoScheduleStore.get")(function* (key) {
|
|
3436
|
+
const canonical = yield* decodeBoundary(ScheduleKey, key, "get schedule");
|
|
3437
|
+
return yield* readOne(canonical, "get schedule");
|
|
3438
|
+
});
|
|
3439
|
+
const list = Effect.fn("DoScheduleStore.list")(function* (requestValue) {
|
|
3440
|
+
const operation = "list schedules";
|
|
3441
|
+
const request = yield* decodeBoundary(SchedulePageRequest, requestValue, operation);
|
|
3442
|
+
const rows = request.after === void 0 ? yield* sql`
|
|
3443
|
+
SELECT tenant_id, owner_id, schedule_id, deadline_at_millis, record_json
|
|
3444
|
+
FROM effect_agent_schedules
|
|
3445
|
+
WHERE tenant_id = ${request.owner.tenantId}
|
|
3446
|
+
AND owner_id = ${request.owner.ownerId}
|
|
3447
|
+
ORDER BY schedule_id
|
|
3448
|
+
LIMIT ${request.limit + 1}
|
|
3449
|
+
`.pipe(Effect.mapError(() => unavailable(operation))) : yield* sql`
|
|
3450
|
+
SELECT tenant_id, owner_id, schedule_id, deadline_at_millis, record_json
|
|
3451
|
+
FROM effect_agent_schedules
|
|
3452
|
+
WHERE tenant_id = ${request.owner.tenantId}
|
|
3453
|
+
AND owner_id = ${request.owner.ownerId}
|
|
3454
|
+
AND schedule_id > ${request.after}
|
|
3455
|
+
ORDER BY schedule_id
|
|
3456
|
+
LIMIT ${request.limit + 1}
|
|
3457
|
+
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
3458
|
+
const decoded = yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
|
|
3459
|
+
const records = yield* Effect.forEach(decoded, decodeRecord);
|
|
3460
|
+
const hasNext = records.length > request.limit;
|
|
3461
|
+
const items = hasNext ? records.slice(0, request.limit) : records;
|
|
3462
|
+
return {
|
|
3463
|
+
items,
|
|
3464
|
+
next: hasNext ? items.at(-1)?.scheduleId ?? null : null
|
|
3465
|
+
};
|
|
3466
|
+
});
|
|
3467
|
+
const change = Effect.fn("DoScheduleStore.change")(function* (key, change, ownerLimit = defaultSchedulingLimits.maxSchedulesPerOwner) {
|
|
3468
|
+
const operation = "change schedule";
|
|
3469
|
+
const canonicalKey = yield* decodeBoundary(ScheduleKey, key, operation);
|
|
3470
|
+
const canonicalChange = yield* decodeBoundary(ScheduleChange, change, operation);
|
|
3471
|
+
const result = yield* transactions.run((replace) => Effect.gen(function* () {
|
|
3472
|
+
const current = yield* readOne(canonicalKey, operation);
|
|
3473
|
+
if (current === null) return yield* ScheduleNotFound.make({ key: canonicalKey });
|
|
3474
|
+
const transition = applyScheduleChange(current, canonicalChange);
|
|
3475
|
+
if (Result.isFailure(transition)) return yield* transition.failure;
|
|
3476
|
+
const next = transition.success;
|
|
3477
|
+
if (!scheduleUsesCapacity(current) && scheduleUsesCapacity(next)) {
|
|
3478
|
+
const rawCounts = yield* sql`
|
|
3479
|
+
SELECT COUNT(*) AS schedule_count FROM effect_agent_schedules
|
|
3480
|
+
WHERE tenant_id = ${key.owner.tenantId} AND owner_id = ${key.owner.ownerId}
|
|
3481
|
+
AND (json_extract(record_json, '$.pending') IS NOT NULL OR
|
|
3482
|
+
(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);
|
|
3486
|
+
if (counts[0].schedule_count >= ownerLimit) return yield* ScheduleCapacityError.make({ limit: ownerLimit });
|
|
3487
|
+
}
|
|
3488
|
+
if (next === current) return {
|
|
3489
|
+
record: current,
|
|
3490
|
+
changed: false
|
|
3491
|
+
};
|
|
3492
|
+
const recordJson = yield* encodeRecord(next);
|
|
3493
|
+
yield* scheduleFailpoint.hit(`schedule:${canonicalChange._tag.toLowerCase()}:before`);
|
|
3494
|
+
yield* sql`
|
|
3495
|
+
UPDATE effect_agent_schedules
|
|
3496
|
+
SET deadline_at_millis = ${scheduleDeadline(next)}, record_json = ${recordJson}
|
|
3497
|
+
WHERE tenant_id = ${canonicalKey.owner.tenantId}
|
|
3498
|
+
AND owner_id = ${canonicalKey.owner.ownerId}
|
|
3499
|
+
AND schedule_id = ${canonicalKey.scheduleId}
|
|
3500
|
+
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
3501
|
+
const deadline = yield* readNextDeadline(void 0, operation);
|
|
3502
|
+
yield* replaceAlarm(replace, deadline, operation);
|
|
3503
|
+
return {
|
|
3504
|
+
record: next,
|
|
3505
|
+
changed: true
|
|
3506
|
+
};
|
|
3507
|
+
}));
|
|
3508
|
+
if (result.changed) yield* scheduleFailpoint.hit(`schedule:${canonicalChange._tag.toLowerCase()}:after`);
|
|
3509
|
+
return result.record;
|
|
3510
|
+
});
|
|
3511
|
+
const due = Effect.fn("DoScheduleStore.due")(function* (nowMillis, limit, owner, after) {
|
|
3512
|
+
const operation = "query due schedules";
|
|
3513
|
+
const cursor = after === void 0 ? void 0 : yield* Schema.decodeUnknownEffect(ScheduleDueCursor)(after).pipe(Effect.mapError(() => corrupt(operation)));
|
|
3514
|
+
const continuation = cursor === void 0 ? sql`1 = 1` : sql`
|
|
3515
|
+
(deadline_at_millis, tenant_id, owner_id, schedule_id) >
|
|
3516
|
+
(${cursor.deadlineAtMillis}, ${cursor.owner.tenantId}, ${cursor.owner.ownerId}, ${cursor.scheduleId})`;
|
|
3517
|
+
const rows = owner === void 0 ? yield* sql`
|
|
3518
|
+
SELECT tenant_id, owner_id, schedule_id, deadline_at_millis
|
|
3519
|
+
FROM effect_agent_schedules
|
|
3520
|
+
WHERE deadline_at_millis <= ${nowMillis} AND ${continuation}
|
|
3521
|
+
ORDER BY deadline_at_millis, tenant_id, owner_id, schedule_id
|
|
3522
|
+
LIMIT ${limit}
|
|
3523
|
+
`.pipe(Effect.mapError(() => unavailable(operation))) : yield* sql`
|
|
3524
|
+
SELECT tenant_id, owner_id, schedule_id, deadline_at_millis
|
|
3525
|
+
FROM effect_agent_schedules
|
|
3526
|
+
WHERE tenant_id = ${owner.tenantId}
|
|
3527
|
+
AND owner_id = ${owner.ownerId}
|
|
3528
|
+
AND deadline_at_millis <= ${nowMillis} AND ${continuation}
|
|
3529
|
+
ORDER BY deadline_at_millis, schedule_id
|
|
3530
|
+
LIMIT ${limit}
|
|
3531
|
+
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
3532
|
+
return (yield* decodeRows(Schema.Array(ScheduleDueRow), rows, operation)).map((row) => ({
|
|
3533
|
+
owner: {
|
|
3534
|
+
tenantId: row.tenant_id,
|
|
3535
|
+
ownerId: row.owner_id
|
|
3536
|
+
},
|
|
3537
|
+
scheduleId: row.schedule_id,
|
|
3538
|
+
deadlineAtMillis: row.deadline_at_millis
|
|
3539
|
+
}));
|
|
3540
|
+
});
|
|
3541
|
+
const nextDeadline = Effect.fn("DoScheduleStore.nextDeadline")(function* (owner) {
|
|
3542
|
+
return yield* readNextDeadline(owner, "query next schedule deadline");
|
|
3543
|
+
});
|
|
3544
|
+
const prearm = Effect.fn("DoScheduleStore.prearm")(function* (deadlineAtMillis) {
|
|
3545
|
+
yield* decodeBoundary(ScheduleInstant, deadlineAtMillis, "pre-arm schedule recovery");
|
|
3546
|
+
yield* transactions.run((replace) => replaceAlarm(replace, deadlineAtMillis, "pre-arm schedule recovery"));
|
|
3547
|
+
yield* scheduleFailpoint.hit("schedule:prearm:after");
|
|
3548
|
+
});
|
|
3549
|
+
const reconcile = Effect.gen(function* () {
|
|
3550
|
+
yield* transactions.run((replace) => Effect.gen(function* () {
|
|
3551
|
+
const deadline = yield* readNextDeadline(void 0, "reconcile schedule alarm");
|
|
3552
|
+
yield* replaceAlarm(replace, deadline, "reconcile schedule alarm");
|
|
3553
|
+
}));
|
|
3554
|
+
yield* scheduleFailpoint.hit("schedule:reconcile:after");
|
|
3555
|
+
});
|
|
3556
|
+
return Context.make(ScheduleStore, {
|
|
3557
|
+
insert,
|
|
3558
|
+
get,
|
|
3559
|
+
list,
|
|
3560
|
+
change,
|
|
3561
|
+
due,
|
|
3562
|
+
nextDeadline
|
|
3563
|
+
}).pipe(Context.add(DoScheduleAlarmControl, {
|
|
3564
|
+
prearm,
|
|
3565
|
+
reconcile
|
|
3566
|
+
}));
|
|
3567
|
+
});
|
|
3568
|
+
/**
|
|
3569
|
+
* Durable Object SQLite ScheduleStore. Platform code supplies the one transaction owner that
|
|
3570
|
+
* combines these SQL mutations with its logical and native alarm lifecycle.
|
|
3571
|
+
*/
|
|
3572
|
+
const scheduleStoreLayer = Layer.effectContext(makeServices);
|
|
3573
|
+
//#endregion
|
|
3218
3574
|
//#region src/port-protocol.ts
|
|
3219
3575
|
/**
|
|
3220
3576
|
* The cross-Durable-Object port protocol (plan §1.3, D-P6-3): Schema request/response/error
|
|
@@ -3727,6 +4083,6 @@ const handleEncodedPortRequest = Effect.fn("DoPortRouting.handleEncodedPortReque
|
|
|
3727
4083
|
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}`))));
|
|
3728
4084
|
});
|
|
3729
4085
|
//#endregion
|
|
3730
|
-
export { ConversationPortTransport, CurrentDoStorageVersion, DEFAULT_MAX_STORED_VALUE_BYTES, DoAppendConflict, DoCheckpointConflict, DoFenceRejected, DoLedgerError, 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, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer };
|
|
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 };
|
|
3731
4087
|
|
|
3732
4088
|
//# sourceMappingURL=index.mjs.map
|