@yielded/agent-storage-cloudflare 0.1.0-beta.175 → 0.1.0-beta.177
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/DoMemoryStore.mjs +1 -1
- package/dist/DoMessageDeliveryStore.mjs +1 -1
- package/dist/DoSubmissionLedger.d.mts +1 -1
- package/dist/DoSubmissionLedger.mjs +81 -36
- package/dist/DoSubmissionLedger.mjs.map +1 -1
- package/dist/{DoThreadStore-Bd4fTns3.d.mts → DoThreadStore-BzYJjm4O.d.mts} +18 -8
- package/dist/{DoThreadStore-CgIZQUNR.mjs → DoThreadStore-DG0h5J6u.mjs} +4 -3
- package/dist/{DoThreadStore-CgIZQUNR.mjs.map → DoThreadStore-DG0h5J6u.mjs.map} +1 -1
- package/dist/DoThreadStore.d.mts +2 -2
- package/dist/DoThreadStore.mjs +3 -2
- package/dist/PortProtocol.d.mts +6615 -6615
- package/dist/PortRouting.mjs +15 -10
- package/dist/PortRouting.mjs.map +1 -1
- package/dist/{do-journal-7P3DUBz0.mjs → do-journal-ySC3eXNv.mjs} +24 -13
- package/dist/do-journal-ySC3eXNv.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{owned-state-B4QhezMp.mjs → owned-state-BgGOA2Ak.mjs} +3 -3
- package/dist/owned-state-BgGOA2Ak.mjs.map +1 -0
- package/package.json +1 -1
- package/src/DoSubmissionLedger.ts +168 -79
- package/src/DoThreadStore.ts +7 -0
- package/src/PortRouting.ts +47 -29
- package/src/internal/do-journal.ts +52 -24
- package/src/internal/owned-state.ts +3 -4
- package/dist/do-journal-7P3DUBz0.mjs.map +0 -1
- package/dist/owned-state-B4QhezMp.mjs.map +0 -1
|
@@ -110,6 +110,8 @@ import {
|
|
|
110
110
|
UnknownResolutionKind,
|
|
111
111
|
unknownResolutionKind,
|
|
112
112
|
unknownResolutionMatchesResult,
|
|
113
|
+
submissionInputRecordId,
|
|
114
|
+
submissionInputBatchId,
|
|
113
115
|
submissionAbortRecordId,
|
|
114
116
|
submissionSettlementRecordId,
|
|
115
117
|
submissionSettlementBatchId,
|
|
@@ -1146,9 +1148,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1146
1148
|
Effect.gen(function* () {
|
|
1147
1149
|
const keyRowKey = `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`;
|
|
1148
1150
|
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
const existing = yield* decodeSubmissionRows(operation, keyRowKey, existingRows);
|
|
1151
|
+
const existing = yield* byKey(validated, operation);
|
|
1152
1152
|
|
|
1153
1153
|
if (existing.length > 1) {
|
|
1154
1154
|
return yield* corruptionFailure(
|
|
@@ -1530,13 +1530,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1530
1530
|
return Option.some(yield* decodeSubmissionSnapshot(operation, row.value));
|
|
1531
1531
|
}
|
|
1532
1532
|
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
const decoded = yield* decodeSubmissionRows(
|
|
1536
|
-
operation,
|
|
1537
|
-
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1538
|
-
foundRows,
|
|
1539
|
-
);
|
|
1533
|
+
const decoded = yield* byKey(validated, operation);
|
|
1540
1534
|
|
|
1541
1535
|
if (decoded.length > 1) {
|
|
1542
1536
|
return yield* corruptionFailure(
|
|
@@ -1563,13 +1557,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1563
1557
|
request,
|
|
1564
1558
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1565
1559
|
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
const decoded = yield* decodeSubmissionRows(
|
|
1569
|
-
operation,
|
|
1570
|
-
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1571
|
-
foundRows,
|
|
1572
|
-
);
|
|
1560
|
+
const decoded = yield* byKey(validated, operation);
|
|
1573
1561
|
|
|
1574
1562
|
if (decoded.length > 1) {
|
|
1575
1563
|
return yield* corruptionFailure(
|
|
@@ -1706,6 +1694,45 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1706
1694
|
return claimed;
|
|
1707
1695
|
});
|
|
1708
1696
|
|
|
1697
|
+
const markInputAppliedInTransaction = Effect.fnUntraced(function* (
|
|
1698
|
+
validated: MarkInputAppliedRequest,
|
|
1699
|
+
) {
|
|
1700
|
+
const operation = "ledger mark input applied";
|
|
1701
|
+
|
|
1702
|
+
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1703
|
+
|
|
1704
|
+
yield* requireOwner(operation, submission);
|
|
1705
|
+
if (submission.input_applied_record_id !== null) {
|
|
1706
|
+
if (
|
|
1707
|
+
submission.input_applied_record_id === validated.recordId &&
|
|
1708
|
+
submission.input_applied_sequence === validated.sequence
|
|
1709
|
+
) {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
return yield* corruptionFailure(
|
|
1714
|
+
operation,
|
|
1715
|
+
"effect_agent_submissions",
|
|
1716
|
+
validated.submissionId,
|
|
1717
|
+
"A different canonical input marker is already recorded for this Submission.",
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
yield* sql`
|
|
1721
|
+
UPDATE effect_agent_submissions
|
|
1722
|
+
SET
|
|
1723
|
+
input_applied_record_id = ${validated.recordId},
|
|
1724
|
+
input_applied_sequence = ${validated.sequence},
|
|
1725
|
+
state = CASE
|
|
1726
|
+
WHEN state IN ('admitted', 'ready') THEN 'input-applied'
|
|
1727
|
+
ELSE state
|
|
1728
|
+
END
|
|
1729
|
+
WHERE submission_id = ${validated.submissionId}
|
|
1730
|
+
RETURNING *, ${definitionJson(sql)} AS agent_digests_json`.pipe(
|
|
1731
|
+
rows.submissions.write,
|
|
1732
|
+
Effect.mapError(internalFailure(operation)),
|
|
1733
|
+
);
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1709
1736
|
const markInputApplied: SubmissionLedger["Service"]["markInputApplied"] = Effect.fnUntraced(
|
|
1710
1737
|
function* (request: MarkInputAppliedRequest) {
|
|
1711
1738
|
const operation = "ledger mark input applied";
|
|
@@ -1715,44 +1742,111 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1715
1742
|
);
|
|
1716
1743
|
|
|
1717
1744
|
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1718
|
-
yield* inWriteTransaction(
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1745
|
+
yield* inWriteTransaction(operation, markInputAppliedInTransaction(validated));
|
|
1746
|
+
yield* hitFailpoint("ledger:mark-input-applied:after", operation);
|
|
1747
|
+
},
|
|
1748
|
+
);
|
|
1722
1749
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
submission.input_applied_record_id === validated.recordId &&
|
|
1727
|
-
submission.input_applied_sequence === validated.sequence
|
|
1728
|
-
) {
|
|
1729
|
-
return;
|
|
1730
|
-
}
|
|
1750
|
+
const appendInput: NonNullable<SubmissionLedger["Service"]["appendInput"]> = Effect.fnUntraced(
|
|
1751
|
+
function* (submissionId, request) {
|
|
1752
|
+
const operation = "append original input and context";
|
|
1731
1753
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
+
const prepared = yield* prepareCanonicalAppend(request).pipe(
|
|
1755
|
+
Effect.provideService(Crypto.Crypto, crypto),
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
const record = prepared.request.batch.records[0];
|
|
1759
|
+
|
|
1760
|
+
const context = prepared.request.executions
|
|
1761
|
+
?.flatMap((update) => update.facts ?? [])
|
|
1762
|
+
.find(({ payload }) => payload._tag === "RunContextRecorded");
|
|
1763
|
+
|
|
1764
|
+
if (
|
|
1765
|
+
prepared.request.executions?.length !== 1 ||
|
|
1766
|
+
prepared.request.executions[0]?.previousRevision !== 0 ||
|
|
1767
|
+
prepared.request.batch.records.length !== 1 ||
|
|
1768
|
+
record?.payload._tag !== "UserInputRecorded" ||
|
|
1769
|
+
record.payload.kind !== "user" ||
|
|
1770
|
+
record.payload.submissionId !== submissionId ||
|
|
1771
|
+
record.payload.runId !== runIdForSubmission(submissionId) ||
|
|
1772
|
+
record.recordId !== submissionInputRecordId(submissionId) ||
|
|
1773
|
+
prepared.request.batch.batchId !== submissionInputBatchId(submissionId) ||
|
|
1774
|
+
(context?.payload._tag === "RunContextRecorded" &&
|
|
1775
|
+
context.payload.historyThrough !== prepared.request.expectedTailSequence)
|
|
1776
|
+
)
|
|
1777
|
+
return yield* ThreadStoreError.make({
|
|
1778
|
+
operation,
|
|
1779
|
+
message: "Original input publication has a different context boundary",
|
|
1780
|
+
});
|
|
1781
|
+
|
|
1782
|
+
const mapError = (cause: { readonly message: string }) =>
|
|
1783
|
+
ThreadStoreError.make({ operation, message: cause.message, cause });
|
|
1784
|
+
|
|
1785
|
+
yield* journal.prepareAppend(prepared.raw).pipe(Effect.mapError(mapError));
|
|
1786
|
+
|
|
1787
|
+
return yield* owner.withProgress(
|
|
1788
|
+
prepared.request.threadId,
|
|
1789
|
+
owner.withMutation(
|
|
1790
|
+
Effect.gen(function* () {
|
|
1791
|
+
yield* hitFailpoint("append:before", operation);
|
|
1792
|
+
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1793
|
+
|
|
1794
|
+
const result = yield* journal
|
|
1795
|
+
.withWriteTransaction(operation)(
|
|
1796
|
+
Effect.gen(function* () {
|
|
1797
|
+
const submission = yield* requireSubmission(operation, submissionId);
|
|
1798
|
+
|
|
1799
|
+
yield* requireOwner(operation, submission);
|
|
1800
|
+
if (submission.thread_id !== prepared.request.threadId)
|
|
1801
|
+
return yield* LedgerError.make({
|
|
1802
|
+
operation,
|
|
1803
|
+
message: "Original input belongs to another Thread",
|
|
1804
|
+
});
|
|
1805
|
+
const result = yield* journal.appendPrepared(prepared.raw);
|
|
1806
|
+
|
|
1807
|
+
yield* markInputAppliedInTransaction(
|
|
1808
|
+
MarkInputAppliedRequest.make({
|
|
1809
|
+
submissionId,
|
|
1810
|
+
recordId: record.recordId,
|
|
1811
|
+
sequence: result.firstSequence,
|
|
1812
|
+
}),
|
|
1813
|
+
);
|
|
1814
|
+
|
|
1815
|
+
return result;
|
|
1816
|
+
}),
|
|
1817
|
+
)
|
|
1818
|
+
.pipe(
|
|
1819
|
+
Effect.mapError((error) =>
|
|
1820
|
+
error._tag === "DoAppendConflict"
|
|
1821
|
+
? AppendConflict.make({
|
|
1822
|
+
threadId: prepared.request.threadId,
|
|
1823
|
+
batchId: prepared.request.batch.batchId,
|
|
1824
|
+
reason: error.reason,
|
|
1825
|
+
...(error.actualTailSequence === undefined
|
|
1826
|
+
? {}
|
|
1827
|
+
: { actualTailSequence: error.actualTailSequence }),
|
|
1828
|
+
})
|
|
1829
|
+
: error._tag === "OwnershipLost"
|
|
1830
|
+
? error
|
|
1831
|
+
: mapError(error),
|
|
1832
|
+
),
|
|
1833
|
+
);
|
|
1834
|
+
|
|
1835
|
+
yield* hitFailpoint("ledger:mark-input-applied:after", operation);
|
|
1836
|
+
yield* hitFailpoint("append:after", operation);
|
|
1837
|
+
|
|
1838
|
+
return result;
|
|
1839
|
+
}).pipe(
|
|
1840
|
+
Effect.mapError((error) =>
|
|
1841
|
+
error._tag === "OwnershipLost" ||
|
|
1842
|
+
error._tag === "ThreadStoreError" ||
|
|
1843
|
+
error._tag === "AppendConflict"
|
|
1844
|
+
? error
|
|
1845
|
+
: mapError(error),
|
|
1846
|
+
),
|
|
1847
|
+
),
|
|
1848
|
+
),
|
|
1754
1849
|
);
|
|
1755
|
-
yield* hitFailpoint("ledger:mark-input-applied:after", operation);
|
|
1756
1850
|
},
|
|
1757
1851
|
);
|
|
1758
1852
|
|
|
@@ -3702,19 +3796,16 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3702
3796
|
.filter((row) => row.joined_host_submission_id === validated.submissionId)
|
|
3703
3797
|
.sort((a, b) => a.queue_sequence - b.queue_sequence);
|
|
3704
3798
|
|
|
3705
|
-
const
|
|
3706
|
-
operation,
|
|
3707
|
-
validated.submissionId,
|
|
3708
|
-
joinRows,
|
|
3709
|
-
);
|
|
3799
|
+
const joins: Array<JoinSnapshot> = [];
|
|
3710
3800
|
|
|
3711
|
-
const
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3801
|
+
for (const row of joinRows)
|
|
3802
|
+
joins.push(
|
|
3803
|
+
yield* decodeJoinSnapshot({
|
|
3804
|
+
submissionId: row.submission_id,
|
|
3805
|
+
state: row.state,
|
|
3806
|
+
hostSubmissionId: validated.submissionId,
|
|
3807
|
+
}).pipe(Effect.mapError(internalFailure(operation))),
|
|
3808
|
+
);
|
|
3718
3809
|
|
|
3719
3810
|
let hostSubmissionId: RecoverySnapshot["hostSubmissionId"];
|
|
3720
3811
|
|
|
@@ -3755,15 +3846,17 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3755
3846
|
|
|
3756
3847
|
const decisionRows = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
3757
3848
|
|
|
3758
|
-
const approvalDecisions =
|
|
3759
|
-
|
|
3760
|
-
)
|
|
3849
|
+
const approvalDecisions: Array<ApprovalDecisionIntent> = [];
|
|
3850
|
+
|
|
3851
|
+
for (const row of decisionRows)
|
|
3852
|
+
approvalDecisions.push(yield* approvalIntentFromRow(operation, row));
|
|
3761
3853
|
|
|
3762
3854
|
const resolutionRows = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
3763
3855
|
|
|
3764
|
-
const unknownResolutions =
|
|
3765
|
-
|
|
3766
|
-
)
|
|
3856
|
+
const unknownResolutions: Array<UnknownResolutionIntent> = [];
|
|
3857
|
+
|
|
3858
|
+
for (const row of resolutionRows)
|
|
3859
|
+
unknownResolutions.push(yield* unknownResolutionIntentFromRow(operation, row));
|
|
3767
3860
|
|
|
3768
3861
|
// Parent-side subagent view: this Submission's child budget reservations in parent
|
|
3769
3862
|
// Tool Call order, plus each attached child's current lane state (a disposable
|
|
@@ -3778,21 +3871,16 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3778
3871
|
.filter((row) => row.parent_submission_id === validated.submissionId)
|
|
3779
3872
|
.sort((a, b) => a.parent_tool_call_id.localeCompare(b.parent_tool_call_id));
|
|
3780
3873
|
|
|
3781
|
-
const
|
|
3782
|
-
operation,
|
|
3783
|
-
validated.submissionId,
|
|
3784
|
-
childReservationRows,
|
|
3785
|
-
);
|
|
3874
|
+
const childReservations: Array<ChildBudgetReservationSnapshot> = [];
|
|
3786
3875
|
|
|
3787
|
-
const
|
|
3788
|
-
childReservationSnapshotFromRow(operation, row)
|
|
3789
|
-
);
|
|
3876
|
+
for (const row of childReservationRows)
|
|
3877
|
+
childReservations.push(yield* childReservationSnapshotFromRow(operation, row));
|
|
3790
3878
|
|
|
3791
3879
|
const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
|
|
3792
3880
|
const markersByChild = new Map(markers.map((row) => [row.child_submission_id, row]));
|
|
3793
3881
|
const childAttachments: Array<ChildAttachmentSnapshot> = [];
|
|
3794
3882
|
|
|
3795
|
-
for (const row of
|
|
3883
|
+
for (const row of childReservationRows) {
|
|
3796
3884
|
if (row.child_submission_id === null) continue;
|
|
3797
3885
|
const child = yield* readSubmission(operation, row.child_submission_id);
|
|
3798
3886
|
|
|
@@ -3863,6 +3951,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3863
3951
|
resolveAdmission,
|
|
3864
3952
|
selectHead,
|
|
3865
3953
|
markInputApplied,
|
|
3954
|
+
appendInput,
|
|
3866
3955
|
finalizeSettlement: (request) => owner.withMutation(finalizeSettlement(request)),
|
|
3867
3956
|
requestAbort,
|
|
3868
3957
|
stopWorker,
|
package/src/DoThreadStore.ts
CHANGED
|
@@ -633,6 +633,13 @@ export const threadStoreLayer: Layer.Layer<
|
|
|
633
633
|
DoStorageConfig | DoStorageFailpoint | SqlClientService.SqlClient | Crypto.Crypto
|
|
634
634
|
> = ThreadReader.layer().pipe(Layer.provideMerge(Layer.effectContext(makeServices())));
|
|
635
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Share one initialized journal between local storage ports in one Object build. Provide
|
|
638
|
+
* this beneath both ports and above their common SQL, owner, configuration, failpoint,
|
|
639
|
+
* progress and lifecycle providers. Standalone ports initialize their own journal.
|
|
640
|
+
*/
|
|
641
|
+
export { initializedDoJournalLayer as journalLayer } from "./internal/do-journal.ts";
|
|
642
|
+
|
|
636
643
|
/**
|
|
637
644
|
* Validated Durable Object storage configuration Layer with the documented defaults applied.
|
|
638
645
|
* Shared by the ThreadStore and SubmissionLedger convenience layers so their defaults
|
package/src/PortRouting.ts
CHANGED
|
@@ -236,35 +236,36 @@ const LOCAL: RouteTarget = { _tag: "local" };
|
|
|
236
236
|
* Identities beyond the ledger's 1,024-character row bound fail typed: the minting side
|
|
237
237
|
* refused them at admission, so they cannot name any stored row anywhere.
|
|
238
238
|
*/
|
|
239
|
-
const routableSubmissionTarget =
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Effect.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
239
|
+
const routableSubmissionTarget =
|
|
240
|
+
(
|
|
241
|
+
ownsThread: RoutedPortOptions["ownsThread"],
|
|
242
|
+
): ((operation: string, submissionId: string) => Effect.Effect<RouteTarget, LedgerError>) =>
|
|
243
|
+
(operation, submissionId) =>
|
|
244
|
+
Effect.suspend(() => {
|
|
245
|
+
if (submissionId.length > MAX_ROUTABLE_SUBMISSION_ID_LENGTH) {
|
|
246
|
+
return Effect.fail(
|
|
247
|
+
LedgerError.make({
|
|
248
|
+
operation,
|
|
249
|
+
message:
|
|
250
|
+
`A Submission identity of ${submissionId.length} characters exceeds the ` +
|
|
251
|
+
`${MAX_ROUTABLE_SUBMISSION_ID_LENGTH}-character routable identity bound; admission ` +
|
|
252
|
+
"refuses such identities, so it cannot name any stored row.",
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const separator = submissionId.indexOf(":");
|
|
257
|
+
|
|
258
|
+
if (separator === -1 || !UUID_HEAD_PATTERN.test(submissionId.slice(0, separator)))
|
|
259
|
+
return Effect.succeed(LOCAL);
|
|
260
|
+
|
|
261
|
+
return decodeThreadId(submissionId.slice(separator + 1)).pipe(
|
|
262
|
+
Effect.match({
|
|
263
|
+
onFailure: () => LOCAL,
|
|
264
|
+
onSuccess: (threadId): RouteTarget =>
|
|
265
|
+
ownsThread(threadId) ? LOCAL : { _tag: "foreign", threadId },
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
});
|
|
268
269
|
|
|
269
270
|
/**
|
|
270
271
|
* Read a Submission only from this physical owner. Encoded foreign identities fail before
|
|
@@ -730,6 +731,23 @@ const makeRoutedLedgerServices = Effect.fnUntraced(function* (options: RoutedPor
|
|
|
730
731
|
? local.claimJoining(request)
|
|
731
732
|
: Effect.fail(crossThreadLedgerError("ledger claim joining", request.threadId)),
|
|
732
733
|
|
|
734
|
+
...(local.appendInput === undefined
|
|
735
|
+
? {}
|
|
736
|
+
: {
|
|
737
|
+
appendInput: (
|
|
738
|
+
submissionId: SubmissionSnapshot["submissionId"],
|
|
739
|
+
request: Parameters<NonNullable<SubmissionLedger["Service"]["appendInput"]>>[1],
|
|
740
|
+
) =>
|
|
741
|
+
options.ownsThread(request.threadId)
|
|
742
|
+
? local.appendInput!(submissionId, request)
|
|
743
|
+
: Effect.fail(
|
|
744
|
+
ThreadStoreError.make({
|
|
745
|
+
operation: "append original input",
|
|
746
|
+
message: "Original input publication is lane-local",
|
|
747
|
+
}),
|
|
748
|
+
),
|
|
749
|
+
}),
|
|
750
|
+
|
|
733
751
|
markInputApplied: (request) =>
|
|
734
752
|
submissionTarget("ledger mark input applied", request.submissionId).pipe(
|
|
735
753
|
Effect.flatMap((target) =>
|
|
@@ -33,7 +33,9 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
Cause,
|
|
35
35
|
Clock,
|
|
36
|
+
Context,
|
|
36
37
|
Effect,
|
|
38
|
+
Layer,
|
|
37
39
|
Option,
|
|
38
40
|
Predicate,
|
|
39
41
|
References,
|
|
@@ -46,6 +48,7 @@ import * as SqlClient from "effect/sql/SqlClient";
|
|
|
46
48
|
import { classifySqliteError, SqlError } from "effect/sql/SqlError";
|
|
47
49
|
import { CurrentTransformer, type Statement } from "effect/sql/Statement";
|
|
48
50
|
|
|
51
|
+
import { DoStorageConfig } from "../DoStorageConfig.ts";
|
|
49
52
|
import {
|
|
50
53
|
type DoStorageFailpointError,
|
|
51
54
|
DoAppendConflict,
|
|
@@ -55,7 +58,7 @@ import {
|
|
|
55
58
|
DoValueBoundExceeded,
|
|
56
59
|
type DoStorageFailpointLocation,
|
|
57
60
|
} from "../DoStorageError.ts";
|
|
58
|
-
import { isDefaultDoStorageFailpoint } from "../DoStorageFailpoint.ts";
|
|
61
|
+
import { DoStorageFailpoint, isDefaultDoStorageFailpoint } from "../DoStorageFailpoint.ts";
|
|
59
62
|
import { ensureDoStorageLayout } from "./migrations.ts";
|
|
60
63
|
import { ownedState, ownedRows, type OwnedState } from "./owned-state.ts";
|
|
61
64
|
import {
|
|
@@ -498,35 +501,37 @@ const makeJournal = Effect.fnUntraced(function* (
|
|
|
498
501
|
* transaction. Expected outcomes may be captured only when every such refusal precedes
|
|
499
502
|
* mutation; real faults still roll back. Successful preflight reads never authorize a write.
|
|
500
503
|
*/
|
|
501
|
-
const withWriteTransaction =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
+
const withWriteTransaction = (
|
|
505
|
+
operation: string,
|
|
506
|
+
expected?: (error: { readonly _tag: string }) => boolean,
|
|
507
|
+
) => {
|
|
508
|
+
const transaction = <A, E extends { readonly _tag: string }>(
|
|
504
509
|
effect: Effect.Effect<A, E>,
|
|
505
510
|
): Effect.Effect<A, E | DoStorageError> =>
|
|
506
511
|
Effect.withFiber((fiber) => {
|
|
507
|
-
const body = state.owner
|
|
508
|
-
.
|
|
509
|
-
.
|
|
510
|
-
|
|
511
|
-
Effect.mapError((error) =>
|
|
512
|
-
isSqlError(error) ? storageError(operation)(error) : error,
|
|
513
|
-
),
|
|
514
|
-
);
|
|
512
|
+
const body = state.owner.withMutation(owner.transaction(effect)).pipe(
|
|
513
|
+
Effect.provideService(SqlClient.SqlClient, sql),
|
|
514
|
+
Effect.mapError((error) => (isSqlError(error) ? storageError(operation)(error) : error)),
|
|
515
|
+
);
|
|
515
516
|
|
|
516
517
|
const traced =
|
|
517
518
|
fiber.getRef(References.TracerEnabled) &&
|
|
518
519
|
fiber.getRef(Tracer.Tracer) !== Tracer.nativeTracer;
|
|
519
520
|
|
|
520
|
-
return
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
: body
|
|
527
|
-
).pipe(Effect.flatMap(Effect.fromResult));
|
|
521
|
+
return traced
|
|
522
|
+
? body.pipe(
|
|
523
|
+
Effect.tapError(annotateStorageError),
|
|
524
|
+
Effect.withSpan("DoJournal.withWriteTransaction", { attributes: { operation } }),
|
|
525
|
+
)
|
|
526
|
+
: body;
|
|
528
527
|
});
|
|
529
528
|
|
|
529
|
+
return <A, E extends { readonly _tag: string }>(effect: Effect.Effect<A, E>) =>
|
|
530
|
+
expected === undefined
|
|
531
|
+
? transaction(effect)
|
|
532
|
+
: transaction(storageResult(effect, expected)).pipe(Effect.flatMap(Effect.fromResult));
|
|
533
|
+
};
|
|
534
|
+
|
|
530
535
|
const materialize = Effect.fnUntraced(function* (
|
|
531
536
|
threadId: string,
|
|
532
537
|
createdAt: string,
|
|
@@ -690,7 +695,7 @@ const makeJournal = Effect.fnUntraced(function* (
|
|
|
690
695
|
)
|
|
691
696
|
return false;
|
|
692
697
|
|
|
693
|
-
const workPlan = work.prepareClosedPair(
|
|
698
|
+
const workPlan = yield* work.prepareClosedPair(
|
|
694
699
|
request.threadId,
|
|
695
700
|
first,
|
|
696
701
|
last,
|
|
@@ -729,7 +734,6 @@ const makeJournal = Effect.fnUntraced(function* (
|
|
|
729
734
|
// A zero-row predicate miss has made no change. A successful update
|
|
730
735
|
// must never fall back: every later refusal rolls back this transaction.
|
|
731
736
|
if (rows.length === 0) return undefined;
|
|
732
|
-
const closed = yield* workPlan.fold();
|
|
733
737
|
const executionPlans = executions.prepareCommit(request);
|
|
734
738
|
|
|
735
739
|
if (!batchInserted)
|
|
@@ -760,7 +764,8 @@ const makeJournal = Effect.fnUntraced(function* (
|
|
|
760
764
|
sql`INSERT INTO effect_agent_record_runs ${sql.insert(group)}`,
|
|
761
765
|
"index canonical Run membership",
|
|
762
766
|
);
|
|
763
|
-
for (const pointer of
|
|
767
|
+
for (const pointer of workPlan.pointers)
|
|
768
|
+
yield* query(pointer, "index canonical pointers");
|
|
764
769
|
|
|
765
770
|
for (const plan of executionPlans) {
|
|
766
771
|
const changed = yield* query(plan.statement, "commit active execution");
|
|
@@ -1568,4 +1573,27 @@ const makeJournal = Effect.fnUntraced(function* (
|
|
|
1568
1573
|
|
|
1569
1574
|
export type DoJournal = Effect.Success<ReturnType<typeof makeJournal>>;
|
|
1570
1575
|
|
|
1571
|
-
|
|
1576
|
+
const InitializedDoJournal = Context.Reference<DoJournal | undefined>(
|
|
1577
|
+
"@effect-agent/storage-cloudflare/InitializedDoJournal",
|
|
1578
|
+
{ defaultValue: () => undefined },
|
|
1579
|
+
);
|
|
1580
|
+
|
|
1581
|
+
/** Build once in the common owner, SQL, configuration, failpoint and lifecycle context. */
|
|
1582
|
+
export const initializedDoJournalLayer = Layer.effect(InitializedDoJournal)(
|
|
1583
|
+
Effect.gen(function* () {
|
|
1584
|
+
const sql = yield* SqlClient.SqlClient;
|
|
1585
|
+
const config = yield* DoStorageConfig;
|
|
1586
|
+
const failpoint = yield* DoStorageFailpoint;
|
|
1587
|
+
|
|
1588
|
+
return yield* ensureCurrentStorage(sql, failpoint.hit, config.maxStoredValueBytes);
|
|
1589
|
+
}),
|
|
1590
|
+
);
|
|
1591
|
+
|
|
1592
|
+
export const initializeDoJournal = (
|
|
1593
|
+
sql: SqlClient.SqlClient,
|
|
1594
|
+
failpoint: DoJournalFailpoint,
|
|
1595
|
+
maxStoredValueBytes: number,
|
|
1596
|
+
) =>
|
|
1597
|
+
Effect.filterOrElse(InitializedDoJournal, Predicate.isNotUndefined, () =>
|
|
1598
|
+
ensureCurrentStorage(sql, failpoint, maxStoredValueBytes),
|
|
1599
|
+
);
|
|
@@ -156,7 +156,7 @@ export const ownedRows = <A, I>(
|
|
|
156
156
|
|
|
157
157
|
const decode = (rows: unknown) =>
|
|
158
158
|
decodeRows(rows).pipe(
|
|
159
|
-
Effect.
|
|
159
|
+
Effect.mapErrorEager(() =>
|
|
160
160
|
DoStorageCorruptionError.make({
|
|
161
161
|
table,
|
|
162
162
|
rowKey: "owned-state",
|
|
@@ -200,9 +200,8 @@ export const ownedRows = <A, I>(
|
|
|
200
200
|
|
|
201
201
|
if (view !== undefined) return Effect.succeed(view.rows);
|
|
202
202
|
|
|
203
|
-
return
|
|
204
|
-
Effect.
|
|
205
|
-
Effect.map((rows) => {
|
|
203
|
+
return Effect.flatMap(query, (rows) =>
|
|
204
|
+
Effect.mapEager(decode(rows), (rows) => {
|
|
206
205
|
retain(id, matches, rows, maxRows);
|
|
207
206
|
|
|
208
207
|
return rows;
|