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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ import { Context, Effect, Layer, Schema } from "effect";
2
+ //#region src/errors.d.ts
3
+ declare const SqliteStorageCompatibilityError_base: Schema.Class<SqliteStorageCompatibilityError, Schema.TaggedStruct<"SqliteStorageCompatibilityError", {
4
+ readonly actualVersion: Schema.Int;
5
+ readonly message: Schema.String;
6
+ readonly supportedVersion: Schema.Int;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ /** The SQLite file uses a private-development format this adapter cannot read. */
9
+ declare class SqliteStorageCompatibilityError extends SqliteStorageCompatibilityError_base {}
10
+ declare const SqliteStorageCorruptionError_base: Schema.Class<SqliteStorageCorruptionError, Schema.TaggedStruct<"SqliteStorageCorruptionError", {
11
+ readonly message: Schema.String;
12
+ readonly rowKey: Schema.String;
13
+ readonly table: Schema.String;
14
+ }>, import("effect/Cause").YieldableError>;
15
+ /** Stored bytes failed the current Schema and cannot be used as recovery truth. */
16
+ declare class SqliteStorageCorruptionError extends SqliteStorageCorruptionError_base {}
17
+ declare const SqliteStorageError_base: Schema.Class<SqliteStorageError, Schema.TaggedStruct<"SqliteStorageError", {
18
+ readonly cause: Schema.optionalKey<Schema.Defect>;
19
+ readonly message: Schema.String;
20
+ readonly operation: Schema.String;
21
+ }>, import("effect/Cause").YieldableError>;
22
+ /** SQLite infrastructure failed while opening or operating the store. */
23
+ declare class SqliteStorageError extends SqliteStorageError_base {}
24
+ declare const SqliteLedgerError_base: Schema.Class<SqliteLedgerError, Schema.TaggedStruct<"SqliteLedgerError", {
25
+ readonly cause: Schema.optionalKey<Schema.Defect>;
26
+ readonly message: Schema.String;
27
+ readonly operation: Schema.String;
28
+ }>, import("effect/Cause").YieldableError>;
29
+ /**
30
+ * SQLite infrastructure failed while operating the Submission Ledger. Surfaces at the
31
+ * SubmissionLedger port as the typed `LedgerError` with this error preserved as its cause,
32
+ * so the adapter-level tag is never erased.
33
+ */
34
+ declare class SqliteLedgerError extends SqliteLedgerError_base {}
35
+ declare const SqliteAppendConflict_base: Schema.Class<SqliteAppendConflict, Schema.TaggedStruct<"SqliteAppendConflict", {
36
+ readonly message: Schema.String;
37
+ readonly reason: Schema.Literals<readonly ["batch-digest", "record-identity", "tail"]>;
38
+ readonly actualTailSequence: Schema.optionalKey<Schema.brand<Schema.Natural, "@effect-agent/thread/CanonicalSequence">>;
39
+ readonly actualTailDigest: Schema.optionalKey<Schema.String>;
40
+ }>, import("effect/Cause").YieldableError>;
41
+ /**
42
+ * A canonical batch retry conflicts with existing append state. Tail conflicts carry the
43
+ * actual committed tail as a diagnostic resume hint.
44
+ */
45
+ declare class SqliteAppendConflict extends SqliteAppendConflict_base {}
46
+ declare const SqliteFenceRejected_base: Schema.Class<SqliteFenceRejected, Schema.TaggedStruct<"SqliteFenceRejected", {
47
+ readonly actualEpoch: Schema.brand<Schema.Natural, "@effect-agent/thread/ProducerEpoch">;
48
+ readonly message: Schema.String;
49
+ readonly producerEpoch: Schema.brand<Schema.Natural, "@effect-agent/thread/ProducerEpoch">;
50
+ }>, import("effect/Cause").YieldableError>;
51
+ /**
52
+ * A producer epoch does not match the Thread's current writer registration. Appends
53
+ * require the exact registered epoch, so both older and newer unregistered epochs are fenced;
54
+ * a newer epoch takes over by materializing first.
55
+ */
56
+ declare class SqliteFenceRejected extends SqliteFenceRejected_base {}
57
+ declare const SqliteWriteContention_base: Schema.Class<SqliteWriteContention, Schema.TaggedStruct<"SqliteWriteContention", {
58
+ readonly cause: Schema.optionalKey<Schema.Defect>;
59
+ readonly message: Schema.String;
60
+ readonly operation: Schema.String;
61
+ }>, import("effect/Cause").YieldableError>;
62
+ /**
63
+ * A write transaction could not acquire the SQLite write lock within the configured busy
64
+ * timeout (SQLITE_BUSY / SQLITE_LOCKED). Another transiently coexisting owner is writing;
65
+ * the operation did not mutate canonical state and is safe to retry.
66
+ */
67
+ declare class SqliteWriteContention extends SqliteWriteContention_base {}
68
+ declare const SqliteCheckpointConflict_base: Schema.Class<SqliteCheckpointConflict, Schema.TaggedStruct<"SqliteCheckpointConflict", {
69
+ readonly message: Schema.String;
70
+ }>, import("effect/Cause").YieldableError>;
71
+ /** A checkpoint conflicts with a previously stored checkpoint at the same offset. */
72
+ declare class SqliteCheckpointConflict extends SqliteCheckpointConflict_base {}
73
+ declare const SqliteStorageFailpointLocation: Schema.Literals<readonly ["materialize:before", "materialize:after", "append:before", "append:after-batch-insert", "append:after-record-insert", "append:after-tail-update", "append:after", "export:after-thread-read", "save-checkpoint:before", "save-checkpoint:after", "ledger:admit:before", "ledger:admit:after", "ledger:mark-ready:before", "ledger:mark-ready:after", "ledger:claim:before", "ledger:claim:after", "ledger:mark-input-applied:before", "ledger:mark-input-applied:after", "ledger:renew:before", "ledger:renew:after", "ledger:reserve-settlement:before", "ledger:reserve-settlement:after", "ledger:finalize-settlement:before", "ledger:finalize-settlement:after", "ledger:request-abort:before", "ledger:request-abort:after", "ledger:release:before", "ledger:release:after", "ledger:claim-joining:before", "ledger:claim-joining:after", "ledger:mark-joined:before", "ledger:mark-joined:after", "ledger:revert-joining:before", "ledger:revert-joining:after", "ledger:suspend:before", "ledger:suspend:after", "ledger:approval-decision:before", "ledger:approval-decision:after", "ledger:mark-unknown:before", "ledger:mark-unknown:after", "ledger:unknown-resolution:before", "ledger:unknown-resolution:after", "ledger:child-reservation:before", "ledger:child-reservation:after", "ledger:child-attach:before", "ledger:child-attach:after", "ledger:child-release-pending:before", "ledger:child-release-pending:after", "ledger:child-release:before", "ledger:child-release:after", "ledger:child-settled:before", "ledger:child-settled:after"]>;
74
+ type SqliteStorageFailpointLocation = typeof SqliteStorageFailpointLocation.Type;
75
+ declare const SqliteStorageFailpointError_base: Schema.Class<SqliteStorageFailpointError, Schema.TaggedStruct<"SqliteStorageFailpointError", {
76
+ readonly location: Schema.Literals<readonly ["materialize:before", "materialize:after", "append:before", "append:after-batch-insert", "append:after-record-insert", "append:after-tail-update", "append:after", "export:after-thread-read", "save-checkpoint:before", "save-checkpoint:after", "ledger:admit:before", "ledger:admit:after", "ledger:mark-ready:before", "ledger:mark-ready:after", "ledger:claim:before", "ledger:claim:after", "ledger:mark-input-applied:before", "ledger:mark-input-applied:after", "ledger:renew:before", "ledger:renew:after", "ledger:reserve-settlement:before", "ledger:reserve-settlement:after", "ledger:finalize-settlement:before", "ledger:finalize-settlement:after", "ledger:request-abort:before", "ledger:request-abort:after", "ledger:release:before", "ledger:release:after", "ledger:claim-joining:before", "ledger:claim-joining:after", "ledger:mark-joined:before", "ledger:mark-joined:after", "ledger:revert-joining:before", "ledger:revert-joining:after", "ledger:suspend:before", "ledger:suspend:after", "ledger:approval-decision:before", "ledger:approval-decision:after", "ledger:mark-unknown:before", "ledger:mark-unknown:after", "ledger:unknown-resolution:before", "ledger:unknown-resolution:after", "ledger:child-reservation:before", "ledger:child-reservation:after", "ledger:child-attach:before", "ledger:child-attach:after", "ledger:child-release-pending:before", "ledger:child-release-pending:after", "ledger:child-release:before", "ledger:child-release:after", "ledger:child-settled:before", "ledger:child-settled:after"]>;
77
+ }>, import("effect/Cause").YieldableError>;
78
+ /** Deterministic test-only fault or pause injected at a SQLite operation boundary. */
79
+ declare class SqliteStorageFailpointError extends SqliteStorageFailpointError_base {
80
+ get message(): string;
81
+ }
82
+ //#endregion
83
+ //#region src/sqlite-storage-failpoint.d.ts
84
+ type SqliteStorageFailpointHandler = (location: SqliteStorageFailpointLocation) => Effect.Effect<void, SqliteStorageFailpointError>;
85
+ declare const SqliteStorageFailpoint_base: Context.ServiceClass<SqliteStorageFailpoint, "@effect-agent/storage-sqlite/SqliteStorageFailpoint", {
86
+ readonly hit: SqliteStorageFailpointHandler;
87
+ }>;
88
+ /** Explicit fault-injection authority used at SQLite operation boundaries. */
89
+ declare class SqliteStorageFailpoint extends SqliteStorageFailpoint_base {
90
+ /** Production default: no fault injection. */
91
+ static readonly layer: Layer.Layer<SqliteStorageFailpoint, never, never>;
92
+ }
93
+ //#endregion
94
+ export { SqliteFenceRejected as a, SqliteStorageCorruptionError as c, SqliteStorageFailpointLocation as d, SqliteWriteContention as f, SqliteCheckpointConflict as i, SqliteStorageError as l, SqliteStorageFailpointHandler as n, SqliteLedgerError as o, SqliteAppendConflict as r, SqliteStorageCompatibilityError as s, SqliteStorageFailpoint as t, SqliteStorageFailpointError as u };
95
+ //# sourceMappingURL=sqlite-storage-failpoint-Cr0SXflP.d.mts.map
@@ -0,0 +1,12 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ //#region src/sqlite-storage-failpoint.ts
3
+ const noFailpoint = () => Effect.void;
4
+ /** Explicit fault-injection authority used at SQLite operation boundaries. */
5
+ var SqliteStorageFailpoint = class extends Context.Service()("@effect-agent/storage-sqlite/SqliteStorageFailpoint") {
6
+ /** Production default: no fault injection. */
7
+ static layer = Layer.succeed(this)({ hit: noFailpoint });
8
+ };
9
+ //#endregion
10
+ export { SqliteStorageFailpoint as t };
11
+
12
+ //# sourceMappingURL=sqlite-storage-failpoint-DIwWk5dw.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-storage-failpoint-DIwWk5dw.mjs","names":[],"sources":["../src/sqlite-storage-failpoint.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\n\nimport type { SqliteStorageFailpointError, SqliteStorageFailpointLocation } from \"./errors.ts\";\n\nexport type SqliteStorageFailpointHandler = (\n location: SqliteStorageFailpointLocation,\n) => Effect.Effect<void, SqliteStorageFailpointError>;\n\nconst noFailpoint: SqliteStorageFailpointHandler = () => Effect.void;\n\n/** Explicit fault-injection authority used at SQLite operation boundaries. */\nexport class SqliteStorageFailpoint extends Context.Service<\n SqliteStorageFailpoint,\n {\n readonly hit: SqliteStorageFailpointHandler;\n }\n>()(\"@effect-agent/storage-sqlite/SqliteStorageFailpoint\") {\n /** Production default: no fault injection. */\n static readonly layer = Layer.succeed(this)({ hit: noFailpoint });\n}\n"],"mappings":";;AAQA,MAAM,oBAAmD,OAAO;;AAGhE,IAAa,yBAAb,cAA4C,QAAQ,QAKlD,CAAC,CAAC,qDAAqD,CAAC,CAAC;;CAEzD,OAAgB,QAAQ,MAAM,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC;AAClE"}
@@ -0,0 +1,15 @@
1
+ import { n as SqliteStorageFailpointHandler, t as SqliteStorageFailpoint } from "./sqlite-storage-failpoint-Cr0SXflP.mjs";
2
+ import { Context, Effect, Layer } from "effect";
3
+ //#region src/sqlite-storage-failpoint-testing.d.ts
4
+ declare const SqliteStorageFailpointTestControl_base: Context.ServiceClass<SqliteStorageFailpointTestControl, "@effect-agent/storage-sqlite/SqliteStorageFailpointTestControl", {
5
+ readonly clear: Effect.Effect<void>;
6
+ readonly setHandler: (handler: SqliteStorageFailpointHandler) => Effect.Effect<void>;
7
+ }>;
8
+ /** Test-only control for replacing the active SQLite failpoint handler. */
9
+ declare class SqliteStorageFailpointTestControl extends SqliteStorageFailpointTestControl_base {
10
+ /** Reusable test Layer with a control service backed by the same handler Ref. */
11
+ static readonly layer: Layer.Layer<SqliteStorageFailpoint | SqliteStorageFailpointTestControl, never, never>;
12
+ }
13
+ //#endregion
14
+ export { SqliteStorageFailpointTestControl };
15
+ //# sourceMappingURL=testing.d.mts.map
@@ -0,0 +1,19 @@
1
+ import { t as SqliteStorageFailpoint } from "./sqlite-storage-failpoint-DIwWk5dw.mjs";
2
+ import { Context, Effect, Layer, Ref } from "effect";
3
+ //#region src/sqlite-storage-failpoint-testing.ts
4
+ const noFailpoint = () => Effect.void;
5
+ /** Test-only control for replacing the active SQLite failpoint handler. */
6
+ var SqliteStorageFailpointTestControl = class SqliteStorageFailpointTestControl extends Context.Service()("@effect-agent/storage-sqlite/SqliteStorageFailpointTestControl") {
7
+ /** Reusable test Layer with a control service backed by the same handler Ref. */
8
+ static layer = Layer.effectContext(Effect.gen(function* () {
9
+ const handler = yield* Ref.make(noFailpoint);
10
+ return Context.make(SqliteStorageFailpoint, SqliteStorageFailpoint.of({ hit: (location) => Ref.get(handler).pipe(Effect.flatMap((current) => current(location))) })).pipe(Context.add(SqliteStorageFailpointTestControl, SqliteStorageFailpointTestControl.of({
11
+ clear: Ref.set(handler, noFailpoint),
12
+ setHandler: (next) => Ref.set(handler, next)
13
+ })));
14
+ }));
15
+ };
16
+ //#endregion
17
+ export { SqliteStorageFailpointTestControl };
18
+
19
+ //# sourceMappingURL=testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../src/sqlite-storage-failpoint-testing.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref } from \"effect\";\n\nimport {\n SqliteStorageFailpoint,\n type SqliteStorageFailpointHandler,\n} from \"./sqlite-storage-failpoint.ts\";\n\nconst noFailpoint: SqliteStorageFailpointHandler = () => Effect.void;\n\n/** Test-only control for replacing the active SQLite failpoint handler. */\nexport class SqliteStorageFailpointTestControl extends Context.Service<\n SqliteStorageFailpointTestControl,\n {\n readonly clear: Effect.Effect<void>;\n readonly setHandler: (handler: SqliteStorageFailpointHandler) => Effect.Effect<void>;\n }\n>()(\"@effect-agent/storage-sqlite/SqliteStorageFailpointTestControl\") {\n /** Reusable test Layer with a control service backed by the same handler Ref. */\n static readonly layer = Layer.effectContext(\n Effect.gen(function* () {\n const handler = yield* Ref.make<SqliteStorageFailpointHandler>(noFailpoint);\n return Context.make(\n SqliteStorageFailpoint,\n SqliteStorageFailpoint.of({\n hit: (location) => Ref.get(handler).pipe(Effect.flatMap((current) => current(location))),\n }),\n ).pipe(\n Context.add(\n SqliteStorageFailpointTestControl,\n SqliteStorageFailpointTestControl.of({\n clear: Ref.set(handler, noFailpoint),\n setHandler: (next) => Ref.set(handler, next),\n }),\n ),\n );\n }),\n );\n}\n"],"mappings":";;;AAOA,MAAM,oBAAmD,OAAO;;AAGhE,IAAa,oCAAb,MAAa,0CAA0C,QAAQ,QAM7D,CAAC,CAAC,gEAAgE,CAAC,CAAC;;CAEpE,OAAgB,QAAQ,MAAM,cAC5B,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,IAAI,KAAoC,WAAW;EAC1E,OAAO,QAAQ,KACb,wBACA,uBAAuB,GAAG,EACxB,MAAM,aAAa,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,QAAQ,QAAQ,CAAC,CAAC,EACzF,CAAC,CACH,CAAC,CAAC,KACA,QAAQ,IACN,mCACA,kCAAkC,GAAG;GACnC,OAAO,IAAI,IAAI,SAAS,WAAW;GACnC,aAAa,SAAS,IAAI,IAAI,SAAS,IAAI;EAC7C,CAAC,CACH,CACF;CACF,CAAC,CACH;AACF"}
package/package.json CHANGED
@@ -1,19 +1,25 @@
1
1
  {
2
2
  "name": "@effect-agent/storage-sqlite",
3
- "version": "0.1.0-beta.37",
3
+ "version": "0.1.0-beta.39",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/index.d.mts",
7
7
  "default": "./dist/index.mjs"
8
+ },
9
+ "./testing": {
10
+ "types": "./dist/testing.d.mts",
11
+ "default": "./dist/testing.mjs"
8
12
  }
9
13
  },
10
14
  "dependencies": {
11
- "@effect-agent/session": "0.1.0-beta.37",
15
+ "@effect-agent/thread": "0.1.0-beta.39",
12
16
  "@effect/platform-node": "4.0.0-rc.111",
13
- "@effect/sql-sqlite-node": "4.0.0-rc.111",
14
- "effect": "4.0.0-rc.111"
17
+ "@effect/sql-sqlite-node": "4.0.0-rc.111"
15
18
  },
16
- "description": "Node SQLite Conversation store and submission ledger adapters for the Effect Agent durable runtime.",
19
+ "peerDependencies": {
20
+ "effect": "^4.0.0-rc.111"
21
+ },
22
+ "description": "Node SQLite Thread store and submission ledger adapters for the Effect Agent durable runtime.",
17
23
  "license": "MIT",
18
24
  "repository": {
19
25
  "type": "git",
@@ -35,6 +41,7 @@
35
41
  },
36
42
  "devDependencies": {
37
43
  "@effect/vitest": "4.0.0-rc.111",
44
+ "effect": "4.0.0-rc.111",
38
45
  "typescript": "7.0.2",
39
46
  "vite-plus": "0.2.6"
40
47
  }
package/src/errors.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CanonicalSequence, ProducerEpoch } from "@effect-agent/session";
1
+ import { CanonicalSequence, ProducerEpoch } from "@effect-agent/thread";
2
2
  import { Schema } from "effect";
3
3
 
4
4
  /** The SQLite file uses a private-development format this adapter cannot read. */
@@ -60,7 +60,7 @@ export class SqliteAppendConflict extends Schema.TaggedError<SqliteAppendConflic
60
60
  ) {}
61
61
 
62
62
  /**
63
- * A producer epoch does not match the Conversation's current writer registration. Appends
63
+ * A producer epoch does not match the Thread's current writer registration. Appends
64
64
  * require the exact registered epoch, so both older and newer unregistered epochs are fenced;
65
65
  * a newer epoch takes over by materializing first.
66
66
  */
@@ -103,7 +103,7 @@ export const SqliteStorageFailpointLocation = Schema.Literals([
103
103
  "append:after-record-insert",
104
104
  "append:after-tail-update",
105
105
  "append:after",
106
- "export:after-conversation-read",
106
+ "export:after-thread-read",
107
107
  "save-checkpoint:before",
108
108
  "save-checkpoint:after",
109
109
  "ledger:admit:before",
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export * from "./errors.ts";
2
- export * from "./migrations.ts";
3
- export * from "./sqlite-conversation-store.ts";
2
+ export { CurrentSqliteStorageVersion } from "./migrations.ts";
3
+ export * from "./sqlite-thread-store.ts";
4
4
  export * from "./sqlite-ledger.ts";
5
5
  export * from "./sqlite-schedule-store.ts";
6
+ export * from "./sqlite-subscription-store.ts";
6
7
  export * from "./sqlite-storage-config.ts";
7
8
  export * from "./sqlite-storage-failpoint.ts";
package/src/migrations.ts CHANGED
@@ -2,15 +2,15 @@ import { SqliteMigrator } from "@effect/sql-sqlite-node";
2
2
  import { Effect } from "effect";
3
3
  import * as SqlClient from "effect/unstable/sql/SqlClient";
4
4
 
5
- export const CurrentSqliteStorageVersion = 5;
5
+ export const CurrentSqliteStorageVersion = 7;
6
6
 
7
7
  export const sqliteMigrations = SqliteMigrator.fromRecord({
8
- "1_current_persistent_conversation_foundation": Effect.gen(function* () {
8
+ "1_current_persistent_thread_foundation": Effect.gen(function* () {
9
9
  const sql = yield* SqlClient.SqlClient;
10
10
 
11
11
  yield* sql`
12
- CREATE TABLE effect_agent_conversations (
13
- conversation_id TEXT PRIMARY KEY NOT NULL,
12
+ CREATE TABLE effect_agent_threads (
13
+ thread_id TEXT PRIMARY KEY NOT NULL,
14
14
  created_at TEXT NOT NULL,
15
15
  tail_sequence INTEGER NOT NULL,
16
16
  tail_digest TEXT NOT NULL,
@@ -20,49 +20,49 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
20
20
 
21
21
  yield* sql`
22
22
  CREATE TABLE effect_agent_canonical_batches (
23
- conversation_id TEXT NOT NULL,
23
+ thread_id TEXT NOT NULL,
24
24
  batch_id TEXT NOT NULL,
25
25
  first_sequence INTEGER NOT NULL,
26
26
  last_sequence INTEGER NOT NULL,
27
27
  batch_digest TEXT NOT NULL,
28
28
  tail_digest TEXT NOT NULL,
29
29
  batch_json TEXT NOT NULL,
30
- PRIMARY KEY (conversation_id, batch_id),
31
- FOREIGN KEY (conversation_id)
32
- REFERENCES effect_agent_conversations(conversation_id)
30
+ PRIMARY KEY (thread_id, batch_id),
31
+ FOREIGN KEY (thread_id)
32
+ REFERENCES effect_agent_threads(thread_id)
33
33
  ON DELETE RESTRICT
34
34
  )
35
35
  `.withoutTransform;
36
36
 
37
37
  yield* sql`
38
38
  CREATE TABLE effect_agent_canonical_records (
39
- conversation_id TEXT NOT NULL,
39
+ thread_id TEXT NOT NULL,
40
40
  sequence INTEGER NOT NULL,
41
41
  record_id TEXT NOT NULL,
42
42
  batch_id TEXT NOT NULL,
43
43
  record_json TEXT NOT NULL,
44
- PRIMARY KEY (conversation_id, sequence),
45
- UNIQUE (conversation_id, record_id),
46
- FOREIGN KEY (conversation_id, batch_id)
47
- REFERENCES effect_agent_canonical_batches(conversation_id, batch_id)
44
+ PRIMARY KEY (thread_id, sequence),
45
+ UNIQUE (thread_id, record_id),
46
+ FOREIGN KEY (thread_id, batch_id)
47
+ REFERENCES effect_agent_canonical_batches(thread_id, batch_id)
48
48
  ON DELETE RESTRICT
49
49
  )
50
50
  `.withoutTransform;
51
51
 
52
52
  yield* sql`
53
53
  CREATE INDEX effect_agent_canonical_records_batch
54
- ON effect_agent_canonical_records (conversation_id, batch_id, sequence)
54
+ ON effect_agent_canonical_records (thread_id, batch_id, sequence)
55
55
  `.withoutTransform;
56
56
 
57
57
  yield* sql`
58
58
  CREATE TABLE effect_agent_checkpoints (
59
- conversation_id TEXT NOT NULL,
59
+ thread_id TEXT NOT NULL,
60
60
  through_sequence INTEGER NOT NULL,
61
61
  tail_digest TEXT NOT NULL,
62
62
  checkpoint_json TEXT NOT NULL,
63
- PRIMARY KEY (conversation_id, through_sequence),
64
- FOREIGN KEY (conversation_id)
65
- REFERENCES effect_agent_conversations(conversation_id)
63
+ PRIMARY KEY (thread_id, through_sequence),
64
+ FOREIGN KEY (thread_id)
65
+ REFERENCES effect_agent_threads(thread_id)
66
66
  ON DELETE RESTRICT
67
67
  )
68
68
  `.withoutTransform;
@@ -72,12 +72,12 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
72
72
  "2_durable_submission_ledger": Effect.gen(function* () {
73
73
  const sql = yield* SqlClient.SqlClient;
74
74
 
75
- // Admission rows exist before Conversation materialization (durability §4), so
76
- // conversation_id intentionally carries no foreign key into effect_agent_conversations.
75
+ // Admission rows exist before Thread materialization (durability §4), so
76
+ // thread_id intentionally carries no foreign key into effect_agent_threads.
77
77
  yield* sql`
78
78
  CREATE TABLE effect_agent_submissions (
79
79
  submission_id TEXT PRIMARY KEY NOT NULL,
80
- conversation_id TEXT NOT NULL,
80
+ thread_id TEXT NOT NULL,
81
81
  queue_sequence INTEGER NOT NULL,
82
82
  principal TEXT NOT NULL,
83
83
  idempotency_key TEXT NOT NULL,
@@ -93,8 +93,8 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
93
93
  ready_at TEXT,
94
94
  input_applied_record_id TEXT,
95
95
  input_applied_sequence INTEGER,
96
- UNIQUE (conversation_id, principal, idempotency_key),
97
- UNIQUE (conversation_id, queue_sequence)
96
+ UNIQUE (thread_id, principal, idempotency_key),
97
+ UNIQUE (thread_id, queue_sequence)
98
98
  )
99
99
  `.withoutTransform;
100
100
 
@@ -116,7 +116,7 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
116
116
  CREATE TABLE effect_agent_attempts (
117
117
  attempt_id TEXT PRIMARY KEY NOT NULL,
118
118
  submission_id TEXT NOT NULL,
119
- conversation_id TEXT NOT NULL,
119
+ thread_id TEXT NOT NULL,
120
120
  owner_producer_id TEXT NOT NULL,
121
121
  producer_epoch INTEGER NOT NULL,
122
122
  claimed_at TEXT NOT NULL,
@@ -302,4 +302,85 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
302
302
 
303
303
  yield* sql`PRAGMA user_version = 5`.withoutTransform;
304
304
  }),
305
+ "6_durable_subscriptions": Effect.gen(function* () {
306
+ const sql = yield* SqlClient.SqlClient;
307
+ yield* sql`
308
+ CREATE TABLE effect_agent_subscription_sequences (
309
+ tenant_id TEXT NOT NULL,
310
+ source_address TEXT NOT NULL,
311
+ sequence INTEGER NOT NULL,
312
+ event_scan_cursor TEXT NOT NULL,
313
+ delivery_scan_cursor TEXT NOT NULL,
314
+ recovery_scan_cursor INTEGER NOT NULL,
315
+ PRIMARY KEY (tenant_id, source_address)
316
+ )
317
+ `.withoutTransform;
318
+ yield* sql`
319
+ CREATE TABLE effect_agent_subscriptions (
320
+ tenant_id TEXT NOT NULL,
321
+ source_address TEXT NOT NULL,
322
+ owner_id TEXT NOT NULL,
323
+ subscription_id TEXT NOT NULL,
324
+ ordinal INTEGER NOT NULL,
325
+ source_name TEXT NOT NULL,
326
+ source_version TEXT NOT NULL,
327
+ matching_key TEXT NOT NULL,
328
+ state TEXT NOT NULL,
329
+ expires_at_millis INTEGER NOT NULL,
330
+ recovery_at_millis INTEGER,
331
+ record_json TEXT NOT NULL,
332
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id),
333
+ UNIQUE (tenant_id, source_address, ordinal)
334
+ )
335
+ `.withoutTransform;
336
+ yield* sql`CREATE INDEX effect_agent_subscriptions_owner ON effect_agent_subscriptions (tenant_id, source_address, owner_id, ordinal)`
337
+ .withoutTransform;
338
+ yield* sql`CREATE INDEX effect_agent_subscriptions_candidates ON effect_agent_subscriptions (tenant_id, source_address, source_name, source_version, matching_key, ordinal)`
339
+ .withoutTransform;
340
+ yield* sql`CREATE INDEX effect_agent_subscriptions_recovery ON effect_agent_subscriptions (tenant_id, source_address, recovery_at_millis, ordinal) WHERE recovery_at_millis IS NOT NULL`
341
+ .withoutTransform;
342
+ yield* sql`
343
+ CREATE TABLE effect_agent_subscription_events (
344
+ tenant_id TEXT NOT NULL,
345
+ source_address TEXT NOT NULL,
346
+ event_id TEXT NOT NULL,
347
+ source_name TEXT NOT NULL,
348
+ source_version TEXT NOT NULL,
349
+ matching_key TEXT NOT NULL,
350
+ payload_digest TEXT NOT NULL,
351
+ cutoff INTEGER NOT NULL,
352
+ cursor INTEGER NOT NULL,
353
+ routing_complete INTEGER NOT NULL,
354
+ next_attempt_at_millis INTEGER NOT NULL,
355
+ record_json TEXT NOT NULL,
356
+ PRIMARY KEY (tenant_id, source_address, event_id)
357
+ )
358
+ `.withoutTransform;
359
+ yield* sql`CREATE INDEX effect_agent_subscription_events_pending ON effect_agent_subscription_events (tenant_id, source_address, routing_complete, next_attempt_at_millis, event_id)`
360
+ .withoutTransform;
361
+ yield* sql`
362
+ CREATE TABLE effect_agent_subscription_deliveries (
363
+ tenant_id TEXT NOT NULL,
364
+ source_address TEXT NOT NULL,
365
+ owner_id TEXT NOT NULL,
366
+ subscription_id TEXT NOT NULL,
367
+ event_id TEXT NOT NULL,
368
+ delivery_key TEXT NOT NULL,
369
+ state TEXT NOT NULL,
370
+ next_attempt_at_millis INTEGER NOT NULL,
371
+ record_json TEXT NOT NULL,
372
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id, event_id),
373
+ UNIQUE (tenant_id, source_address, delivery_key)
374
+ )
375
+ `.withoutTransform;
376
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_pending ON effect_agent_subscription_deliveries (tenant_id, source_address, state, next_attempt_at_millis, delivery_key)`
377
+ .withoutTransform;
378
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_registration ON effect_agent_subscription_deliveries (tenant_id, source_address, owner_id, subscription_id, delivery_key)`
379
+ .withoutTransform;
380
+ yield* sql`PRAGMA user_version = 6`.withoutTransform;
381
+ }),
382
+ "7_thread_terminology_cutover": Effect.gen(function* () {
383
+ const sql = yield* SqlClient.SqlClient;
384
+ yield* sql`PRAGMA user_version = 7`.withoutTransform;
385
+ }),
305
386
  });