@effect-agent/storage-cloudflare 0.1.0-beta.38 → 0.1.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/do-storage-failpoint-9XB9tuif.d.mts +107 -0
- package/dist/do-storage-failpoint-C-Qtchs7.mjs +12 -0
- package/dist/do-storage-failpoint-C-Qtchs7.mjs.map +1 -0
- package/dist/index.d.mts +161 -248
- package/dist/index.mjs +1211 -539
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +33 -0
- package/dist/testing.mjs +35 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +14 -7
- package/src/do-journal.ts +148 -157
- package/src/do-ledger.ts +66 -66
- package/src/do-schedule-store.ts +7 -3
- package/src/do-storage-failpoint-testing.ts +73 -0
- package/src/do-storage-failpoint.ts +1 -68
- package/src/do-subscription-store.ts +1122 -0
- package/src/{do-conversation-store.ts → do-thread-store.ts} +313 -334
- package/src/errors.ts +3 -3
- package/src/index.ts +9 -8
- package/src/migrations.ts +28 -35
- package/src/port-protocol.ts +29 -29
- package/src/routing.ts +146 -188
- package/src/testing.ts +2 -0
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CanonicalSequence, ProducerEpoch } from "@effect-agent/
|
|
1
|
+
import { CanonicalSequence, ProducerEpoch } from "@effect-agent/thread";
|
|
2
2
|
import { Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
/** The Durable Object's SQLite storage uses a private-development format this adapter cannot read. */
|
|
@@ -75,7 +75,7 @@ export class DoAppendConflict extends Schema.TaggedError<DoAppendConflict>()("Do
|
|
|
75
75
|
}) {}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* A producer epoch does not match the
|
|
78
|
+
* A producer epoch does not match the Thread's current writer registration. Appends
|
|
79
79
|
* require the exact registered epoch, so both older and newer unregistered epochs are fenced;
|
|
80
80
|
* a newer epoch takes over by materializing first.
|
|
81
81
|
*/
|
|
@@ -109,7 +109,7 @@ export const DoStorageFailpointLocation = Schema.Literals([
|
|
|
109
109
|
"append:after-record-insert",
|
|
110
110
|
"append:after-tail-update",
|
|
111
111
|
"append:after",
|
|
112
|
-
"export:after-
|
|
112
|
+
"export:after-thread-read",
|
|
113
113
|
"save-checkpoint:before",
|
|
114
114
|
"save-checkpoint:after",
|
|
115
115
|
"ledger:admit:before",
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@effect-agent/storage-cloudflare` — Durable Object SQLite adapters for the
|
|
3
|
-
* (`
|
|
2
|
+
* `@effect-agent/storage-cloudflare` — Durable Object SQLite adapters for the thread ports
|
|
3
|
+
* (`ThreadStore`, `SubmissionLedger`).
|
|
4
4
|
*
|
|
5
|
-
* WP1 lands the LOCAL facets: the full port implementations against one
|
|
5
|
+
* WP1 lands the LOCAL facets: the full port implementations against one Thread Durable
|
|
6
6
|
* Object's private SQLite database, structurally mirroring the Node/SQLite adapters (same
|
|
7
7
|
* tables, same failpoint-location names, same conformance suites) with the DC-specific
|
|
8
8
|
* differences documented in each module — Durable Object storage-backed transactions instead
|
|
@@ -13,20 +13,21 @@
|
|
|
13
13
|
*
|
|
14
14
|
* WP2 adds the cross-Object distribution seam: `port-protocol.ts` (the Schema
|
|
15
15
|
* request/response/failure envelopes for the CLOSED route-capable port subset) and
|
|
16
|
-
* `routing.ts` (the `
|
|
17
|
-
* the local facets — this-
|
|
16
|
+
* `routing.ts` (the `ThreadPortTransport` service, the routed decorator Layers over
|
|
17
|
+
* the local facets — this-thread → local, route-capable foreign → transport, anything
|
|
18
18
|
* else foreign → fail fast typed — and the owner-side `handleEncodedPortRequest` endpoint
|
|
19
|
-
* body for the
|
|
19
|
+
* body for the Thread Object's `portCall`).
|
|
20
20
|
*
|
|
21
21
|
* This package never imports the `cloudflare:workers` runtime module — Durable Object handles
|
|
22
22
|
* (`ctx.storage`) are injected as Layer construction values, and `@cloudflare/workers-types`
|
|
23
23
|
* stays a types-only devDependency.
|
|
24
24
|
*/
|
|
25
25
|
export * from "./errors.ts";
|
|
26
|
-
export
|
|
27
|
-
export * from "./do-
|
|
26
|
+
export { CurrentDoStorageVersion } from "./migrations.ts";
|
|
27
|
+
export * from "./do-thread-store.ts";
|
|
28
28
|
export * from "./do-ledger.ts";
|
|
29
29
|
export * from "./do-schedule-store.ts";
|
|
30
|
+
export * from "./do-subscription-store.ts";
|
|
30
31
|
export * from "./do-storage-config.ts";
|
|
31
32
|
export * from "./do-storage-failpoint.ts";
|
|
32
33
|
export * from "./port-protocol.ts";
|
package/src/migrations.ts
CHANGED
|
@@ -2,35 +2,28 @@ import { SqliteMigrator } from "@effect/sql-sqlite-do";
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
* platform, so there is exactly ONE migration carrying the complete current schema — no
|
|
8
|
-
* v1→v4 history to replay (deployment spec §9: no rolling data-version promise during
|
|
9
|
-
* private development).
|
|
10
|
-
*/
|
|
11
|
-
export const CurrentDoStorageVersion = 1;
|
|
5
|
+
/** The current storage version recorded in `effect_agent_meta`. */
|
|
6
|
+
export const CurrentDoStorageVersion = 2;
|
|
12
7
|
|
|
13
8
|
/**
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
16
|
-
* into their final shape) so the shared conformance suites and crash-matrix rows address
|
|
17
|
-
* identical durable state. Two DC-specific additions:
|
|
9
|
+
* The Thread Durable Object schema shares its thread and ledger tables with Node/SQLite.
|
|
10
|
+
* Schedules and subscriptions use separate Durable Objects. Two DC-specific additions:
|
|
18
11
|
*
|
|
19
12
|
* 1. `effect_agent_meta` replaces `PRAGMA user_version` as the exact-or-fresh version gate —
|
|
20
13
|
* a meta table is portable regardless of which PRAGMAs Durable Object SQL storage allows.
|
|
21
14
|
* 2. `effect_agent_child_settlements` is the durable cross-store notification marker the
|
|
22
15
|
* SubmissionLedger port contract mandates for cross-store adapters (`suspend`'s covering
|
|
23
|
-
* check and `recordChildSettled`'s wake both consult it): parent and child
|
|
16
|
+
* check and `recordChildSettled`'s wake both consult it): parent and child Threads
|
|
24
17
|
* live in different Durable Objects, so a child settlement reported before the parent's
|
|
25
18
|
* suspend commits must be observable from the PARENT's own storage.
|
|
26
19
|
*/
|
|
27
20
|
export const doMigrations = SqliteMigrator.fromRecord({
|
|
28
|
-
"
|
|
21
|
+
"1_current_cloudflare_thread_object": Effect.gen(function* () {
|
|
29
22
|
const sql = yield* SqlClient.SqlClient;
|
|
30
23
|
|
|
31
24
|
yield* sql`
|
|
32
|
-
CREATE TABLE
|
|
33
|
-
|
|
25
|
+
CREATE TABLE effect_agent_threads (
|
|
26
|
+
thread_id TEXT PRIMARY KEY NOT NULL,
|
|
34
27
|
created_at TEXT NOT NULL,
|
|
35
28
|
tail_sequence INTEGER NOT NULL,
|
|
36
29
|
tail_digest TEXT NOT NULL,
|
|
@@ -40,59 +33,59 @@ export const doMigrations = SqliteMigrator.fromRecord({
|
|
|
40
33
|
|
|
41
34
|
yield* sql`
|
|
42
35
|
CREATE TABLE effect_agent_canonical_batches (
|
|
43
|
-
|
|
36
|
+
thread_id TEXT NOT NULL,
|
|
44
37
|
batch_id TEXT NOT NULL,
|
|
45
38
|
first_sequence INTEGER NOT NULL,
|
|
46
39
|
last_sequence INTEGER NOT NULL,
|
|
47
40
|
batch_digest TEXT NOT NULL,
|
|
48
41
|
tail_digest TEXT NOT NULL,
|
|
49
42
|
batch_json TEXT NOT NULL,
|
|
50
|
-
PRIMARY KEY (
|
|
51
|
-
FOREIGN KEY (
|
|
52
|
-
REFERENCES
|
|
43
|
+
PRIMARY KEY (thread_id, batch_id),
|
|
44
|
+
FOREIGN KEY (thread_id)
|
|
45
|
+
REFERENCES effect_agent_threads(thread_id)
|
|
53
46
|
ON DELETE RESTRICT
|
|
54
47
|
)
|
|
55
48
|
`.withoutTransform;
|
|
56
49
|
|
|
57
50
|
yield* sql`
|
|
58
51
|
CREATE TABLE effect_agent_canonical_records (
|
|
59
|
-
|
|
52
|
+
thread_id TEXT NOT NULL,
|
|
60
53
|
sequence INTEGER NOT NULL,
|
|
61
54
|
record_id TEXT NOT NULL,
|
|
62
55
|
batch_id TEXT NOT NULL,
|
|
63
56
|
record_json TEXT NOT NULL,
|
|
64
|
-
PRIMARY KEY (
|
|
65
|
-
UNIQUE (
|
|
66
|
-
FOREIGN KEY (
|
|
67
|
-
REFERENCES effect_agent_canonical_batches(
|
|
57
|
+
PRIMARY KEY (thread_id, sequence),
|
|
58
|
+
UNIQUE (thread_id, record_id),
|
|
59
|
+
FOREIGN KEY (thread_id, batch_id)
|
|
60
|
+
REFERENCES effect_agent_canonical_batches(thread_id, batch_id)
|
|
68
61
|
ON DELETE RESTRICT
|
|
69
62
|
)
|
|
70
63
|
`.withoutTransform;
|
|
71
64
|
|
|
72
65
|
yield* sql`
|
|
73
66
|
CREATE INDEX effect_agent_canonical_records_batch
|
|
74
|
-
ON effect_agent_canonical_records (
|
|
67
|
+
ON effect_agent_canonical_records (thread_id, batch_id, sequence)
|
|
75
68
|
`.withoutTransform;
|
|
76
69
|
|
|
77
70
|
yield* sql`
|
|
78
71
|
CREATE TABLE effect_agent_checkpoints (
|
|
79
|
-
|
|
72
|
+
thread_id TEXT NOT NULL,
|
|
80
73
|
through_sequence INTEGER NOT NULL,
|
|
81
74
|
tail_digest TEXT NOT NULL,
|
|
82
75
|
checkpoint_json TEXT NOT NULL,
|
|
83
|
-
PRIMARY KEY (
|
|
84
|
-
FOREIGN KEY (
|
|
85
|
-
REFERENCES
|
|
76
|
+
PRIMARY KEY (thread_id, through_sequence),
|
|
77
|
+
FOREIGN KEY (thread_id)
|
|
78
|
+
REFERENCES effect_agent_threads(thread_id)
|
|
86
79
|
ON DELETE RESTRICT
|
|
87
80
|
)
|
|
88
81
|
`.withoutTransform;
|
|
89
82
|
|
|
90
|
-
// Admission rows exist before
|
|
91
|
-
//
|
|
83
|
+
// Admission rows exist before Thread materialization (durability §4), so
|
|
84
|
+
// thread_id intentionally carries no foreign key into effect_agent_threads.
|
|
92
85
|
yield* sql`
|
|
93
86
|
CREATE TABLE effect_agent_submissions (
|
|
94
87
|
submission_id TEXT PRIMARY KEY NOT NULL,
|
|
95
|
-
|
|
88
|
+
thread_id TEXT NOT NULL,
|
|
96
89
|
queue_sequence INTEGER NOT NULL,
|
|
97
90
|
principal TEXT NOT NULL,
|
|
98
91
|
idempotency_key TEXT NOT NULL,
|
|
@@ -115,8 +108,8 @@ export const doMigrations = SqliteMigrator.fromRecord({
|
|
|
115
108
|
unknown_tool_call_ids_json TEXT,
|
|
116
109
|
parent_submission_id TEXT,
|
|
117
110
|
parent_tool_call_id TEXT,
|
|
118
|
-
UNIQUE (
|
|
119
|
-
UNIQUE (
|
|
111
|
+
UNIQUE (thread_id, principal, idempotency_key),
|
|
112
|
+
UNIQUE (thread_id, queue_sequence)
|
|
120
113
|
)
|
|
121
114
|
`.withoutTransform;
|
|
122
115
|
|
|
@@ -148,7 +141,7 @@ export const doMigrations = SqliteMigrator.fromRecord({
|
|
|
148
141
|
CREATE TABLE effect_agent_attempts (
|
|
149
142
|
attempt_id TEXT PRIMARY KEY NOT NULL,
|
|
150
143
|
submission_id TEXT NOT NULL,
|
|
151
|
-
|
|
144
|
+
thread_id TEXT NOT NULL,
|
|
152
145
|
owner_producer_id TEXT NOT NULL,
|
|
153
146
|
producer_epoch INTEGER NOT NULL,
|
|
154
147
|
claimed_at TEXT NOT NULL,
|
package/src/port-protocol.ts
CHANGED
|
@@ -10,14 +10,14 @@ import {
|
|
|
10
10
|
CanonicalRecordEnvelope,
|
|
11
11
|
ChildSettledNotification,
|
|
12
12
|
ChildSettledOutcome,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
ThreadExport,
|
|
14
|
+
ThreadExportRequest,
|
|
15
|
+
ThreadMaterialization,
|
|
16
|
+
ThreadNotMaterialized,
|
|
17
|
+
ThreadRead,
|
|
18
|
+
ThreadStoreError,
|
|
19
|
+
ThreadTail,
|
|
20
|
+
ThreadTailRequest,
|
|
21
21
|
FenceRejected,
|
|
22
22
|
FencedAppendRequest,
|
|
23
23
|
JoinedToHost,
|
|
@@ -27,20 +27,20 @@ import {
|
|
|
27
27
|
SubmissionLookup,
|
|
28
28
|
SubmissionLookupByKey,
|
|
29
29
|
SubmissionSnapshot,
|
|
30
|
-
} from "@effect-agent/
|
|
30
|
+
} from "@effect-agent/thread";
|
|
31
31
|
import { Schema } from "effect";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* The cross-Durable-Object port protocol (plan §1.3, D-P6-3): Schema request/response/error
|
|
35
|
-
* envelopes for the CLOSED route-capable subset of the
|
|
36
|
-
* Durable Object executes another
|
|
35
|
+
* envelopes for the CLOSED route-capable subset of the thread ports. One Thread's
|
|
36
|
+
* Durable Object executes another Thread's request against its OWN local facets; the
|
|
37
37
|
* envelopes here are the only values that cross the Object boundary, and they are
|
|
38
38
|
* transport-agnostic — native Durable Object JS RPC is the shipped carrier, fetch-with-JSON
|
|
39
39
|
* the documented fallback, and both move the same Schema-encoded JSON.
|
|
40
40
|
*
|
|
41
41
|
* The closed subset is exactly the set of operations the durable coordinator performs against
|
|
42
|
-
* a FOREIGN
|
|
43
|
-
* child-settlement notification, and the child-
|
|
42
|
+
* a FOREIGN Thread (parent/child establishment, status checks, abort propagation,
|
|
43
|
+
* child-settlement notification, and the child-thread store operations used by
|
|
44
44
|
* establishment, `verifySettledChild`, and result projection):
|
|
45
45
|
*
|
|
46
46
|
* - ledger: `admit`, `markReady`, `lookup`, `resolveAdmission`, `requestAbort`,
|
|
@@ -53,7 +53,7 @@ import { Schema } from "effect";
|
|
|
53
53
|
*
|
|
54
54
|
* Failures cross the boundary as the `PortFailure` union and re-decode on the caller side to
|
|
55
55
|
* the SAME tagged error types the local facet would have produced, so routed calls keep
|
|
56
|
-
* error-tag fidelity. `cause` chains inside `LedgerError`/`
|
|
56
|
+
* error-tag fidelity. `cause` chains inside `LedgerError`/`ThreadStoreError` travel as
|
|
57
57
|
* Schema defects and do not claim instance fidelity across Objects (plan §2.8).
|
|
58
58
|
*/
|
|
59
59
|
|
|
@@ -71,7 +71,7 @@ export const boundPortDiagnostic = (value: string): string =>
|
|
|
71
71
|
/**
|
|
72
72
|
* The envelope itself could not be honored: the receiving Object could not decode the
|
|
73
73
|
* request, or a response could not be encoded/decoded. It never carries port semantics —
|
|
74
|
-
* callers fold it into the operation's base error (`LedgerError`/`
|
|
74
|
+
* callers fold it into the operation's base error (`LedgerError`/`ThreadStoreError`),
|
|
75
75
|
* except `resolveAdmission`, which folds it into `AdmissionIndeterminate` because a
|
|
76
76
|
* non-answer is never proof of absence (SUB-031).
|
|
77
77
|
*/
|
|
@@ -128,39 +128,39 @@ export class LedgerRecordChildSettledCall extends Schema.TaggedClass<LedgerRecor
|
|
|
128
128
|
request: ChildSettledNotification,
|
|
129
129
|
}) {}
|
|
130
130
|
|
|
131
|
-
/** Routed `
|
|
131
|
+
/** Routed `ThreadStore.materialize` against the owning Object. */
|
|
132
132
|
export class StoreMaterializeCall extends Schema.TaggedClass<StoreMaterializeCall>(
|
|
133
133
|
"@effect-agent/storage-cloudflare/StoreMaterializeCall",
|
|
134
134
|
)("StoreMaterialize", {
|
|
135
|
-
request:
|
|
135
|
+
request: ThreadMaterialization,
|
|
136
136
|
}) {}
|
|
137
137
|
|
|
138
|
-
/** Routed `
|
|
138
|
+
/** Routed `ThreadStore.append` against the owning Object. */
|
|
139
139
|
export class StoreAppendCall extends Schema.TaggedClass<StoreAppendCall>(
|
|
140
140
|
"@effect-agent/storage-cloudflare/StoreAppendCall",
|
|
141
141
|
)("StoreAppend", {
|
|
142
142
|
request: FencedAppendRequest,
|
|
143
143
|
}) {}
|
|
144
144
|
|
|
145
|
-
/** Routed one-page `
|
|
145
|
+
/** Routed one-page `ThreadStore.read`; the page bound is the request's own `limit`. */
|
|
146
146
|
export class StoreReadPageCall extends Schema.TaggedClass<StoreReadPageCall>(
|
|
147
147
|
"@effect-agent/storage-cloudflare/StoreReadPageCall",
|
|
148
148
|
)("StoreReadPage", {
|
|
149
|
-
request:
|
|
149
|
+
request: ThreadRead,
|
|
150
150
|
}) {}
|
|
151
151
|
|
|
152
|
-
/** Routed `
|
|
152
|
+
/** Routed `ThreadStore.inspectTail` against the owning Object. */
|
|
153
153
|
export class StoreInspectTailCall extends Schema.TaggedClass<StoreInspectTailCall>(
|
|
154
154
|
"@effect-agent/storage-cloudflare/StoreInspectTailCall",
|
|
155
155
|
)("StoreInspectTail", {
|
|
156
|
-
request:
|
|
156
|
+
request: ThreadTailRequest,
|
|
157
157
|
}) {}
|
|
158
158
|
|
|
159
|
-
/** Routed `
|
|
159
|
+
/** Routed `ThreadStore.export` against the owning Object. */
|
|
160
160
|
export class StoreExportCall extends Schema.TaggedClass<StoreExportCall>(
|
|
161
161
|
"@effect-agent/storage-cloudflare/StoreExportCall",
|
|
162
162
|
)("StoreExport", {
|
|
163
|
-
request:
|
|
163
|
+
request: ThreadExportRequest,
|
|
164
164
|
}) {}
|
|
165
165
|
|
|
166
166
|
/** Every request that may cross a Durable Object boundary — the CLOSED route-capable subset. */
|
|
@@ -241,13 +241,13 @@ export class StoreReadPageResult extends Schema.TaggedClass<StoreReadPageResult>
|
|
|
241
241
|
export class StoreInspectTailResult extends Schema.TaggedClass<StoreInspectTailResult>(
|
|
242
242
|
"@effect-agent/storage-cloudflare/StoreInspectTailResult",
|
|
243
243
|
)("StoreInspectTailResult", {
|
|
244
|
-
tail:
|
|
244
|
+
tail: ThreadTail,
|
|
245
245
|
}) {}
|
|
246
246
|
|
|
247
247
|
export class StoreExportResult extends Schema.TaggedClass<StoreExportResult>(
|
|
248
248
|
"@effect-agent/storage-cloudflare/StoreExportResult",
|
|
249
249
|
)("StoreExportResult", {
|
|
250
|
-
export:
|
|
250
|
+
export: ThreadExport,
|
|
251
251
|
}) {}
|
|
252
252
|
|
|
253
253
|
/** Every successful routed result. Callers narrow by the tag their request implies. */
|
|
@@ -273,15 +273,15 @@ export type PortResult = typeof PortResult.Type;
|
|
|
273
273
|
/**
|
|
274
274
|
* Every typed failure a route-capable operation can produce on its owning Object, plus the
|
|
275
275
|
* protocol's own `PortProtocolError`. Members re-decode to the SAME tagged classes the
|
|
276
|
-
*
|
|
276
|
+
* thread ports declare, so a routed caller observes identical error tags and fields.
|
|
277
277
|
*/
|
|
278
278
|
export const PortFailure = Schema.Union([
|
|
279
279
|
AdmissionConflict,
|
|
280
280
|
SettlementConflict,
|
|
281
281
|
JoinedToHost,
|
|
282
282
|
LedgerError,
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
ThreadStoreError,
|
|
284
|
+
ThreadNotMaterialized,
|
|
285
285
|
AppendConflict,
|
|
286
286
|
FenceRejected,
|
|
287
287
|
PortProtocolError,
|