@hraness/oh 0.4.0 → 0.4.2

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.
Files changed (92) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +12 -9
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +15590 -1705
  6. package/dist/index.js +15209 -261
  7. package/dist/libsql-model.d.ts +75 -0
  8. package/dist/libsql-model.d.ts.map +1 -0
  9. package/dist/libsql-platform.d.ts +24 -0
  10. package/dist/libsql-platform.d.ts.map +1 -0
  11. package/dist/libsql-program.d.ts +64 -0
  12. package/dist/libsql-program.d.ts.map +1 -0
  13. package/dist/libsql-runtime.d.ts +13 -0
  14. package/dist/libsql-runtime.d.ts.map +1 -0
  15. package/dist/libsql.d.ts +4 -36
  16. package/dist/libsql.d.ts.map +1 -1
  17. package/dist/libsql.js +16583 -1426
  18. package/dist/memory-authority-platform.d.ts +210 -0
  19. package/dist/memory-authority-platform.d.ts.map +1 -0
  20. package/dist/memory-authority-program.d.ts +15 -0
  21. package/dist/memory-authority-program.d.ts.map +1 -0
  22. package/dist/memory-authority-runtime.d.ts +4 -0
  23. package/dist/memory-authority-runtime.d.ts.map +1 -0
  24. package/dist/memory-core.d.ts +522 -0
  25. package/dist/memory-core.d.ts.map +1 -0
  26. package/dist/memory.d.ts +5 -445
  27. package/dist/memory.d.ts.map +1 -1
  28. package/dist/memory.js +18232 -4143
  29. package/dist/sdk.js +15266 -318
  30. package/dist/semantic-cloud.js +1 -260
  31. package/dist/semantic-model.d.ts +75 -0
  32. package/dist/semantic-model.d.ts.map +1 -0
  33. package/dist/semantic-platform.d.ts +32 -0
  34. package/dist/semantic-platform.d.ts.map +1 -0
  35. package/dist/semantic-program.d.ts +18 -0
  36. package/dist/semantic-program.d.ts.map +1 -0
  37. package/dist/semantic-runtime.d.ts +6 -0
  38. package/dist/semantic-runtime.d.ts.map +1 -0
  39. package/dist/semantic.d.ts +6 -57
  40. package/dist/semantic.d.ts.map +1 -1
  41. package/dist/semantic.js +15541 -514
  42. package/dist/sqlite/index.js +1148 -1378
  43. package/dist/sqlite/port.d.ts +1 -1
  44. package/dist/sqlite/port.d.ts.map +1 -1
  45. package/dist/sync-libsql-program.d.ts +5 -0
  46. package/dist/sync-libsql-program.d.ts.map +1 -0
  47. package/dist/sync-model.d.ts +60 -0
  48. package/dist/sync-model.d.ts.map +1 -0
  49. package/dist/sync-platform.d.ts +41 -0
  50. package/dist/sync-platform.d.ts.map +1 -0
  51. package/dist/sync-program.d.ts +11 -0
  52. package/dist/sync-program.d.ts.map +1 -0
  53. package/dist/sync-runtime.d.ts +6 -0
  54. package/dist/sync-runtime.d.ts.map +1 -0
  55. package/dist/sync.d.ts +5 -55
  56. package/dist/sync.d.ts.map +1 -1
  57. package/dist/sync.js +15174 -226
  58. package/package.json +7 -5
  59. package/skills/oh/SKILL.md +3 -2
  60. package/spec/v1/store.md +14 -0
  61. package/src/cli.test.ts +35 -0
  62. package/src/cli.ts +4 -3
  63. package/src/cloudflare-embedding.ts +1 -1
  64. package/src/libsql-lifecycle.test.ts +76 -0
  65. package/src/libsql-model.ts +350 -0
  66. package/src/libsql-platform.ts +51 -0
  67. package/src/libsql-program.ts +1583 -0
  68. package/src/libsql-runtime.ts +89 -0
  69. package/src/libsql-semantic-v2.ts +1 -1
  70. package/src/libsql-semantic.ts +1 -1
  71. package/src/libsql.test.ts +245 -0
  72. package/src/libsql.ts +26 -1698
  73. package/src/memory-authority-platform.ts +133 -0
  74. package/src/memory-authority-program.ts +309 -0
  75. package/src/memory-authority-runtime.ts +40 -0
  76. package/src/memory-core.ts +2401 -0
  77. package/src/memory.test.ts +55 -0
  78. package/src/memory.ts +68 -2759
  79. package/src/semantic-model.ts +142 -0
  80. package/src/semantic-platform.ts +130 -0
  81. package/src/semantic-program.ts +113 -0
  82. package/src/semantic-runtime.ts +53 -0
  83. package/src/semantic.test.ts +115 -1
  84. package/src/semantic.ts +14 -324
  85. package/src/sqlite/port.ts +1 -1
  86. package/src/sync-libsql-program.ts +170 -0
  87. package/src/sync-lifecycle.test.ts +110 -0
  88. package/src/sync-model.ts +530 -0
  89. package/src/sync-platform.ts +87 -0
  90. package/src/sync-program.ts +117 -0
  91. package/src/sync-runtime.ts +33 -0
  92. package/src/sync.ts +10 -765
@@ -0,0 +1,1583 @@
1
+ import { Deferred, Effect, Either, Ref } from "effect";
2
+ import {
3
+ canonicalJson,
4
+ canonicalSha256,
5
+ parseSha256Hex,
6
+ safeCode,
7
+ utf8ByteLength,
8
+ type Sha256Hex,
9
+ } from "./canonical";
10
+ import { OH_CONTRACT_MANIFEST_V1 } from "./contract";
11
+ import {
12
+ canonicalKnowledgeGraphChangesV1,
13
+ knowledgeGraphRecordRefV1,
14
+ OH_GRAPH_LIMITS_V1,
15
+ parseKnowledgeGraphRecordV1,
16
+ type KnowledgeGraphRecordV1,
17
+ } from "./graph";
18
+ import { OH_OPERATION_MAX_BYTES_V1, type OhOperationV1 } from "./operation";
19
+ import {
20
+ createOhDependencyClosureV1,
21
+ createOhSpacePurgeReceiptV1,
22
+ createOhStoreBindingV1,
23
+ emptyOhHeadV1,
24
+ OH_CANONICAL_STORE_PROFILE_V1,
25
+ OH_WORKING_STORE_PROFILE_V1,
26
+ OhConflictError,
27
+ OhIntegrityError,
28
+ OhOperationSizeError,
29
+ OhProfileError,
30
+ OhPurgedSpaceError,
31
+ parseOhHeadRefV1,
32
+ parseOhStoreProfileV1,
33
+ replayOhOperationsV1,
34
+ transitionOhSnapshotV1,
35
+ type OhChangesPageV1,
36
+ type OhCommitInputV1,
37
+ type OhDependencyClosureV1,
38
+ type OhHeadRefV1,
39
+ type OhHeadV1,
40
+ type OhSnapshotV1,
41
+ type OhSpacePurgeReceiptV1,
42
+ type OhStoreBindingV1,
43
+ type OhStoreVerificationV1,
44
+ } from "./store";
45
+ import {
46
+ type OhLibSqlStatementV1,
47
+ type OhLibSqlResultV1,
48
+ type OhLibSqlStoreAuthorityOptionsV1,
49
+ OH_LIBSQL_STORE_LIMITS_V1,
50
+ AUTHORITY_SCHEMA_NAME,
51
+ AUTHORITY_SCHEMA_VERSION,
52
+ EMPTY_RECORDS_SHA256,
53
+ PURGE_ROW_SELECT,
54
+ BINDING_ROW_SELECT,
55
+ SPACE_PURGE_PROOF_SELECT,
56
+ OPERATION_ROW_COLUMNS,
57
+ OPERATION_RESPONSE_BYTES,
58
+ RECORD_RESPONSE_BYTES,
59
+ DEPENDENCY_RESPONSE_BYTES,
60
+ OPERATION_RECORD_RESPONSE_BYTES,
61
+ AUTHORITY_SCHEMA_TABLE_STATEMENT,
62
+ AUTHORITY_SCHEMA_STATEMENTS,
63
+ normalizedSchemaSql,
64
+ AUTHORITY_SCHEMA_OBJECTS,
65
+ AUTHORITY_SCHEMA_SHA256,
66
+ rowValue,
67
+ integer,
68
+ normalizeLimit,
69
+ parseOperationRow,
70
+ parseBindingRow,
71
+ parsePurgeReceiptRow,
72
+ parseHeadRow,
73
+ PURGE_PAYLOAD_TABLES,
74
+ } from "./libsql-model";
75
+ import {
76
+ libSqlValue,
77
+ libSqlFailure,
78
+ type LibSqlFailure,
79
+ type LibSqlAuthorityClientService,
80
+ } from "./libsql-platform";
81
+
82
+ function queryOne(
83
+ client: LibSqlAuthorityClientService,
84
+ statement: OhLibSqlStatementV1,
85
+ ): Effect.Effect<Readonly<Record<string, unknown>> | readonly unknown[] | null, LibSqlFailure> {
86
+ return Effect.gen(function* () {
87
+ return (yield* client.execute(statement)).rows[0] ?? null;
88
+ });
89
+ }
90
+
91
+ function verifyAuthoritySchemaObjects(client: LibSqlAuthorityClientService): Effect.Effect<void, LibSqlFailure> {
92
+ return Effect.gen(function* () {
93
+ const rows = (yield* client.execute({
94
+ sql: `SELECT type, name, tbl_name, sql FROM sqlite_schema
95
+ WHERE sql IS NOT NULL AND (name = 'oh_authority_schemas' OR name GLOB 'oh_authority_*'
96
+ OR tbl_name GLOB 'oh_authority_*')
97
+ ORDER BY type, name` })).rows;
98
+ const actual = (yield* libSqlValue(() => rows.map((row) => {
99
+ const type = rowValue(row, "type", 0);
100
+ const name = rowValue(row, "name", 1);
101
+ const tableName = rowValue(row, "tbl_name", 2);
102
+ const sql = rowValue(row, "sql", 3);
103
+ if ((type !== "table" && type !== "index" && type !== "trigger") || typeof name !== "string"
104
+ || typeof tableName !== "string" || typeof sql !== "string") {
105
+ throw new OhIntegrityError("The installed libSQL authority has an invalid schema object.");
106
+ }
107
+ return { name, sql: normalizedSchemaSql(sql), tableName, type };
108
+ })));
109
+ if ((yield* libSqlValue(() => canonicalJson(actual))) !== (yield* libSqlValue(() => canonicalJson(AUTHORITY_SCHEMA_OBJECTS)))) {
110
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The installed libSQL authority objects differ from this runtime.")));
111
+ }
112
+ });
113
+ }
114
+
115
+ function verifyAuthoritySchema(client: LibSqlAuthorityClientService): Effect.Effect<void, LibSqlFailure> {
116
+ return Effect.gen(function* () {
117
+ const installed = yield* queryOne(client, {
118
+ sql: `SELECT name, schema_sha256
119
+ FROM oh_authority_schemas WHERE version = ?`, args: [AUTHORITY_SCHEMA_VERSION]
120
+ });
121
+ if (installed === null || rowValue(installed, "name", 0) !== AUTHORITY_SCHEMA_NAME
122
+ || rowValue(installed, "schema_sha256", 1) !== AUTHORITY_SCHEMA_SHA256) {
123
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The installed libSQL authority schema differs from this runtime.")));
124
+ }
125
+ const contract = yield* queryOne(client, {
126
+ sql: `SELECT contract_sha256, manifest_json
127
+ FROM oh_authority_contracts WHERE contract_id = ?`, args: [OH_CONTRACT_MANIFEST_V1.contractId]
128
+ });
129
+ if (contract === null || rowValue(contract, "contract_sha256", 0) !== OH_CONTRACT_MANIFEST_V1.contractSha256
130
+ || rowValue(contract, "manifest_json", 1) !== (yield* libSqlValue(() => canonicalJson(OH_CONTRACT_MANIFEST_V1)))) {
131
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority contract differs from this runtime.")));
132
+ }
133
+ yield* verifyAuthoritySchemaObjects(client);
134
+ });
135
+ }
136
+
137
+ /** One-time schema operation for a client authorized to create authority tables. */
138
+ export function bootstrapAuthority(
139
+ client: LibSqlAuthorityClientService,
140
+ ): Effect.Effect<Readonly<{ schemaSha256: Sha256Hex; schemaVersion: 1; v: 1 }>, LibSqlFailure> {
141
+ return Effect.gen(function* () {
142
+ const existingObjects = (yield* client.execute({
143
+ sql: `SELECT name FROM sqlite_schema
144
+ WHERE sql IS NOT NULL AND (name = 'oh_authority_schemas' OR name GLOB 'oh_authority_*'
145
+ OR tbl_name GLOB 'oh_authority_*')` })).rows;
146
+ if (existingObjects.length > 0) {
147
+ if (!(yield* libSqlValue(() => existingObjects.some((row) => rowValue(row, "name", 0) === "oh_authority_schemas")))) {
148
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Refusing to bootstrap over preexisting Oh authority objects.")));
149
+ }
150
+ yield* verifyAuthoritySchema(client);
151
+ return { schemaSha256: AUTHORITY_SCHEMA_SHA256, schemaVersion: 1, v: 1 };
152
+ }
153
+ const setup: OhLibSqlStatementV1[] = (yield* libSqlValue(() => [AUTHORITY_SCHEMA_TABLE_STATEMENT, ...AUTHORITY_SCHEMA_STATEMENTS]
154
+ .map((sql) => ({ sql }))));
155
+ setup.push({
156
+ sql: `INSERT INTO oh_authority_schemas(version, name, schema_sha256, applied_at)
157
+ VALUES (?, ?, ?, ?) ON CONFLICT(version) DO NOTHING`,
158
+ args: [AUTHORITY_SCHEMA_VERSION, AUTHORITY_SCHEMA_NAME, AUTHORITY_SCHEMA_SHA256, (yield* client.currentInstant)]
159
+ });
160
+ setup.push({
161
+ sql: `INSERT INTO oh_authority_contracts(contract_id, contract_sha256, manifest_json)
162
+ VALUES (?, ?, ?) ON CONFLICT(contract_id) DO NOTHING`, args: [OH_CONTRACT_MANIFEST_V1.contractId,
163
+ OH_CONTRACT_MANIFEST_V1.contractSha256, (yield* libSqlValue(() => canonicalJson(OH_CONTRACT_MANIFEST_V1)))]
164
+ });
165
+ yield* client.batch(setup, "write");
166
+ yield* verifyAuthoritySchema(client);
167
+ return { schemaSha256: AUTHORITY_SCHEMA_SHA256, schemaVersion: 1, v: 1 };
168
+ });
169
+ }
170
+
171
+ function initializeSpace(
172
+ client: LibSqlAuthorityClientService,
173
+ binding: OhStoreBindingV1,
174
+ ): Effect.Effect<void, LibSqlFailure> {
175
+ return Effect.gen(function* () {
176
+ const purged = yield* queryOne(client, {
177
+ sql: PURGE_ROW_SELECT,
178
+ args: [binding.spaceId]
179
+ });
180
+ if (purged !== null) return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError((yield* libSqlValue(() => parsePurgeReceiptRow(
181
+ purged, binding.spaceId, binding.bindingSha256))))));
182
+ const now = yield* client.currentInstant;
183
+ const attempt = yield* Effect.either(client.batch([
184
+ {
185
+ sql: `INSERT INTO oh_authority_spaces(space_id, contract_id, generation,
186
+ head_operation_sha256, graph_revision_sha256, records_sha256, sequence, created_at, updated_at)
187
+ SELECT ?, ?, 0, NULL, NULL, ?, 0, ?, ?
188
+ WHERE NOT EXISTS (SELECT 1 FROM oh_authority_purges WHERE space_id = ?)
189
+ ON CONFLICT(space_id) DO NOTHING`,
190
+ args: [binding.spaceId, OH_CONTRACT_MANIFEST_V1.contractId, EMPTY_RECORDS_SHA256, now, now,
191
+ binding.spaceId]
192
+ },
193
+ {
194
+ sql: `INSERT INTO oh_authority_bindings(space_id, realm_id, profile_id, profile_kind,
195
+ profile_sha256, binding_sha256, binding_json, created_at)
196
+ SELECT ?, ?, ?, ?, ?, ?, ?, ?
197
+ WHERE NOT EXISTS (SELECT 1 FROM oh_authority_purges WHERE space_id = ?)
198
+ AND EXISTS (SELECT 1 FROM oh_authority_spaces WHERE space_id = ?)
199
+ ON CONFLICT(space_id) DO NOTHING`,
200
+ args: [binding.spaceId, binding.realmId, binding.profile.profileId, binding.profile.profileKind,
201
+ binding.profile.profileSha256, binding.bindingSha256, (yield* libSqlValue(() => canonicalJson(binding))), now,
202
+ binding.spaceId, binding.spaceId]
203
+ },
204
+ {
205
+ sql: `INSERT INTO oh_authority_commit_guards(value)
206
+ SELECT 'invalid' WHERE EXISTS (SELECT 1 FROM oh_authority_purges WHERE space_id = ?)
207
+ OR NOT EXISTS (SELECT 1 FROM oh_authority_spaces WHERE space_id = ?)
208
+ OR NOT EXISTS (SELECT 1 FROM oh_authority_bindings WHERE space_id = ? AND binding_sha256 = ?)`,
209
+ args: [binding.spaceId, binding.spaceId, binding.spaceId, binding.bindingSha256]
210
+ },
211
+ ], "write"));
212
+ if (Either.isLeft(attempt)) {
213
+ const error = attempt.left;
214
+ const raced = yield* queryOne(client, {
215
+ sql: PURGE_ROW_SELECT,
216
+ args: [binding.spaceId]
217
+ });
218
+ if (raced !== null) return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError((yield* libSqlValue(() => parsePurgeReceiptRow(
219
+ raced, binding.spaceId, binding.bindingSha256))))));
220
+ const persisted = yield* queryOne(client, { sql: BINDING_ROW_SELECT, args: [binding.spaceId] });
221
+ if (persisted !== null
222
+ && (yield* libSqlValue(() => canonicalJson(parseBindingRow(persisted, binding.spaceId)))) !== (yield* libSqlValue(() => canonicalJson(binding)))) {
223
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("The remote space is already bound to a different realm or profile.")));
224
+ }
225
+ return yield* Effect.fail(error);
226
+ }
227
+ const persisted = yield* queryOne(client, { sql: BINDING_ROW_SELECT, args: [binding.spaceId] });
228
+ if (persisted === null) {
229
+ const raced = yield* queryOne(client, {
230
+ sql: PURGE_ROW_SELECT,
231
+ args: [binding.spaceId]
232
+ });
233
+ if (raced !== null) return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError((yield* libSqlValue(() => parsePurgeReceiptRow(
234
+ raced, binding.spaceId, binding.bindingSha256))))));
235
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote space has no persisted binding after initialization.")));
236
+ }
237
+ if ((yield* libSqlValue(() => canonicalJson(parseBindingRow(persisted, binding.spaceId)))) !== (yield* libSqlValue(() => canonicalJson(binding)))) {
238
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("The remote space is already bound to a different realm or profile.")));
239
+ }
240
+ });
241
+ }
242
+
243
+ function requireExistingSpace(
244
+ client: LibSqlAuthorityClientService,
245
+ binding: OhStoreBindingV1,
246
+ ): Effect.Effect<void, LibSqlFailure> {
247
+ return Effect.gen(function* () {
248
+ const results = yield* client.batch([
249
+ { sql: BINDING_ROW_SELECT, args: [binding.spaceId] },
250
+ {
251
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256,
252
+ records_sha256, sequence, contract_id FROM oh_authority_spaces WHERE space_id = ?`,
253
+ args: [binding.spaceId]
254
+ },
255
+ { sql: PURGE_ROW_SELECT, args: [binding.spaceId] },
256
+ ], "read");
257
+ if (results.length !== 3) {
258
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete existing-space proof.")));
259
+ }
260
+ const [bindingResult, spaceResult, purgeResult] = results as
261
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1];
262
+ const purgeRow = purgeResult.rows[0];
263
+ if (purgeRow !== undefined) {
264
+ return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError((yield* libSqlValue(() => parsePurgeReceiptRow(
265
+ purgeRow, binding.spaceId, binding.bindingSha256))))));
266
+ }
267
+ const bindingRow = bindingResult.rows[0];
268
+ const spaceRow = spaceResult.rows[0];
269
+ if (bindingRow === undefined || spaceRow === undefined) {
270
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The requested remote Oh space does not already exist.")));
271
+ }
272
+ const persisted = (yield* libSqlValue(() => parseBindingRow(bindingRow, binding.spaceId)));
273
+ if ((yield* libSqlValue(() => canonicalJson(persisted))) !== (yield* libSqlValue(() => canonicalJson(binding)))) {
274
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("The remote space is bound to a different realm or profile.")));
275
+ }
276
+ if (rowValue(spaceRow, "contract_id", 5) !== OH_CONTRACT_MANIFEST_V1.contractId) {
277
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The existing remote space uses a different Oh contract.")));
278
+ }
279
+ (yield* libSqlValue(() => parseHeadRow(spaceRow)));
280
+ });
281
+ }
282
+
283
+ function assertRemotePurgeComplete(
284
+ client: LibSqlAuthorityClientService,
285
+ binding: OhStoreBindingV1,
286
+ expected: OhSpacePurgeReceiptV1,
287
+ ): Effect.Effect<void, LibSqlFailure> {
288
+ return Effect.gen(function* () {
289
+ const results = yield* client.batch([
290
+ { sql: PURGE_ROW_SELECT, args: [binding.spaceId] },
291
+ ...(yield* libSqlValue(() => PURGE_PAYLOAD_TABLES.map((table) => ({
292
+ sql: `SELECT count(*) AS count FROM ${table} WHERE space_id = ?`,
293
+ args: [binding.spaceId],
294
+ })))),
295
+ {
296
+ sql: `SELECT count(*) AS count FROM oh_authority_operation_records AS materialized
297
+ LEFT JOIN oh_authority_operations AS operation
298
+ ON operation.operation_sha256 = materialized.operation_sha256
299
+ WHERE operation.operation_sha256 IS NULL OR operation.space_id <> materialized.space_id` },
300
+ ], "read");
301
+ const receiptRow = results[0]?.rows[0];
302
+ if (receiptRow === undefined
303
+ || (yield* libSqlValue(() => canonicalJson(parsePurgeReceiptRow(receiptRow, binding.spaceId,
304
+ binding.bindingSha256)))) !== (yield* libSqlValue(() => canonicalJson(expected)))) {
305
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote purge receipt differs from the requested purge.")));
306
+ }
307
+ for (let index = 0; index < PURGE_PAYLOAD_TABLES.length; index += 1) {
308
+ const countRow = results[index + 1]?.rows[0];
309
+ if (countRow === undefined || integer(rowValue(countRow, "count", 0)) !== 0) {
310
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError(`Remote purge left rows in ${PURGE_PAYLOAD_TABLES[index]}.`)));
311
+ }
312
+ }
313
+ const orphanRow = results[PURGE_PAYLOAD_TABLES.length + 1]?.rows[0];
314
+ if (orphanRow === undefined || integer(rowValue(orphanRow, "count", 0)) !== 0) {
315
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Remote purge left an orphaned or cross-space operation record.")));
316
+ }
317
+ });
318
+ }
319
+
320
+ export class LibSqlStoreProgram {
321
+ readonly binding: OhStoreBindingV1;
322
+ readonly #client: LibSqlAuthorityClientService;
323
+ #purged: OhSpacePurgeReceiptV1 | null = null;
324
+ #hostPurge: OhSpacePurgeReceiptV1 | null = null;
325
+
326
+ constructor(client: LibSqlAuthorityClientService, binding: OhStoreBindingV1) {
327
+ this.#client = client;
328
+ this.binding = binding;
329
+ }
330
+
331
+ closedFailure(): LibSqlFailure {
332
+ return this.#purged === null
333
+ ? { _tag: "LibSqlClosed", cause: new Error("The Oh libSQL store is closed.") }
334
+ : libSqlFailure(new OhPurgedSpaceError(this.#purged));
335
+ }
336
+
337
+ purgeFromHost(input: Readonly<{ purgedAt?: string }>): Effect.Effect<OhSpacePurgeReceiptV1, LibSqlFailure> {
338
+ return Effect.gen(this, function* () {
339
+ if (this.binding.profile.profileKind !== "working" || !this.binding.profile.capabilities.wholeSpacePurge) {
340
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("This host handle is not bound to a purgeable working profile.")));
341
+ }
342
+ if (this.#hostPurge !== null) return this.#hostPurge;
343
+ this.#hostPurge = yield* this.purgeWorkingSpace(input.purgedAt ?? (yield* this.#client.currentInstant));
344
+ return this.#hostPurge;
345
+ });
346
+ }
347
+
348
+ #assertOpen(): void {
349
+ if (this.#purged !== null) throw new OhPurgedSpaceError(this.#purged);
350
+ }
351
+
352
+ head(): Effect.Effect<OhHeadV1, LibSqlFailure> {
353
+ return Effect.gen(this, function* () {
354
+ yield* libSqlValue(() => this.#assertOpen());
355
+ const row = yield* queryOne(this.#client, {
356
+ sql: `SELECT generation, graph_revision_sha256,
357
+ head_operation_sha256, records_sha256, sequence FROM oh_authority_spaces WHERE space_id = ?`,
358
+ args: [this.binding.spaceId]
359
+ });
360
+ if (row === null) {
361
+ const purged = yield* this.#readPurge();
362
+ if (purged !== null) { this.#purged = purged; return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError(purged))); }
363
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote Oh space does not exist.")));
364
+ }
365
+ return (yield* libSqlValue(() => parseHeadRow(row)));
366
+ });
367
+ }
368
+
369
+ #readPurge(): Effect.Effect<OhSpacePurgeReceiptV1 | null, LibSqlFailure> {
370
+ return Effect.gen(this, function* () {
371
+ const row = yield* queryOne(this.#client, {
372
+ sql: PURGE_ROW_SELECT,
373
+ args: [this.binding.spaceId]
374
+ });
375
+ if (row === null) return null;
376
+ return (yield* libSqlValue(() => parsePurgeReceiptRow(row, this.binding.spaceId, this.binding.bindingSha256)));
377
+ });
378
+ }
379
+
380
+ #headAt(reference: OhHeadRefV1): Effect.Effect<OhHeadV1, LibSqlFailure> {
381
+ return Effect.gen(this, function* () {
382
+ const parsed = (yield* libSqlValue(() => parseOhHeadRefV1(reference)));
383
+ if (parsed === null) return yield* Effect.fail(libSqlFailure(new TypeError("Invalid Oh head reference.")));
384
+ if (parsed.sequence === 0) return emptyOhHeadV1();
385
+ const row = yield* queryOne(this.#client, {
386
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
387
+ WHERE space_id = ? AND sequence = ?`, args: [this.binding.spaceId, parsed.sequence]
388
+ });
389
+ if (row === null) return yield* Effect.fail(libSqlFailure(new OhConflictError("The requested head is not present in this space.")));
390
+ const operation = (yield* libSqlValue(() => parseOperationRow(row, { spaceId: this.binding.spaceId })));
391
+ if (operation.spaceId !== this.binding.spaceId || operation.sequence !== parsed.sequence
392
+ || operation.operationSha256 !== parsed.operationSha256) {
393
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The requested sequence identifies a different operation head.")));
394
+ }
395
+ return {
396
+ generation: operation.sequence, graphRevisionSha256: operation.graphRevisionSha256,
397
+ operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
398
+ sequence: operation.sequence, v: 1
399
+ };
400
+ });
401
+ }
402
+
403
+ #currentMaterializedSnapshot(expectedHead: OhHeadV1, maximumRecords: number): Effect.Effect<OhSnapshotV1, LibSqlFailure> {
404
+ return Effect.gen(this, function* () {
405
+ const provenancePredicate = `operation.space_id = ? AND (operation.operation_sha256 IS ?
406
+ OR operation.operation_sha256 IN (SELECT record.operation_sha256
407
+ FROM oh_authority_records AS record WHERE record.space_id = ?))`;
408
+ const results = yield* this.#client.batch([
409
+ {
410
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256, records_sha256, sequence
411
+ FROM oh_authority_spaces WHERE space_id = ?`, args: [this.binding.spaceId]
412
+ },
413
+ {
414
+ sql: `SELECT
415
+ (SELECT count(*) FROM (SELECT DISTINCT operation.operation_sha256
416
+ FROM oh_authority_operations AS operation WHERE ${provenancePredicate})) AS provenance_count,
417
+ (SELECT coalesce(sum(bytes), 0) FROM (SELECT DISTINCT operation.operation_sha256,
418
+ ${OPERATION_RESPONSE_BYTES} AS bytes FROM oh_authority_operations AS operation
419
+ WHERE ${provenancePredicate})) AS provenance_bytes,
420
+ (SELECT count(*) FROM oh_authority_records WHERE space_id = ?) AS record_count,
421
+ (SELECT coalesce(sum(${RECORD_RESPONSE_BYTES}), 0) FROM oh_authority_records AS record
422
+ WHERE record.space_id = ?) AS record_bytes,
423
+ (SELECT coalesce(sum(${DEPENDENCY_RESPONSE_BYTES}), 0)
424
+ FROM oh_authority_dependencies AS dependency
425
+ WHERE dependency.space_id = ?) AS dependency_bytes`,
426
+ args: [this.binding.spaceId, expectedHead.operationSha256, this.binding.spaceId,
427
+ this.binding.spaceId, expectedHead.operationSha256, this.binding.spaceId,
428
+ this.binding.spaceId, this.binding.spaceId, this.binding.spaceId]
429
+ },
430
+ {
431
+ sql: `SELECT record_key, kind, record_sha256, record_json, operation_sha256, sequence
432
+ FROM oh_authority_records AS record WHERE record.space_id = ?
433
+ AND (SELECT coalesce(sum(${RECORD_RESPONSE_BYTES}), 0)
434
+ FROM oh_authority_records AS record WHERE record.space_id = ?) <= ? ORDER BY record_key`, args: [this.binding.spaceId,
435
+ this.binding.spaceId, OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes]
436
+ },
437
+ {
438
+ sql: `SELECT record_key, dependency_key FROM oh_authority_dependencies
439
+ AS dependency WHERE dependency.space_id = ?
440
+ AND (SELECT coalesce(sum(${DEPENDENCY_RESPONSE_BYTES}), 0)
441
+ FROM oh_authority_dependencies AS dependency WHERE dependency.space_id = ?) <= ?
442
+ ORDER BY record_key, dependency_key`,
443
+ args: [this.binding.spaceId, this.binding.spaceId, OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes]
444
+ },
445
+ {
446
+ sql: `SELECT ${OPERATION_ROW_COLUMNS}
447
+ FROM oh_authority_operations AS operation
448
+ WHERE ${provenancePredicate}
449
+ AND (SELECT coalesce(sum(bytes), 0) FROM (SELECT DISTINCT candidate.operation_sha256,
450
+ ${OPERATION_RESPONSE_BYTES.replaceAll("operation.", "candidate.")} AS bytes
451
+ FROM oh_authority_operations AS candidate
452
+ WHERE candidate.space_id = ? AND (candidate.operation_sha256 IS ?
453
+ OR candidate.operation_sha256 IN (SELECT record.operation_sha256
454
+ FROM oh_authority_records AS record WHERE record.space_id = ?)))) <= ?
455
+ ORDER BY operation.sequence`,
456
+ args: [this.binding.spaceId, expectedHead.operationSha256, this.binding.spaceId,
457
+ this.binding.spaceId, expectedHead.operationSha256, this.binding.spaceId,
458
+ OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes]
459
+ },
460
+ {
461
+ sql: `SELECT count(*) AS count, min(sequence) AS minimum, max(sequence) AS maximum
462
+ FROM oh_authority_operations WHERE space_id = ?`, args: [this.binding.spaceId]
463
+ },
464
+ ], "read");
465
+ if (results.length !== 6) return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete snapshot batch.")));
466
+ const [headResult, sizeResult, recordResult, dependencyResult, provenanceResult, historyResult] = results as
467
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1,
468
+ OhLibSqlResultV1, OhLibSqlResultV1];
469
+ const headRow = headResult.rows[0];
470
+ if (headRow === undefined) {
471
+ const purge = yield* this.#readPurge();
472
+ if (purge !== null) { this.#purged = purge; return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError(purge))); }
473
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote Oh space disappeared while reading its snapshot.")));
474
+ }
475
+ const head = (yield* libSqlValue(() => parseHeadRow(headRow)));
476
+ if ((yield* libSqlValue(() => canonicalJson(head))) !== (yield* libSqlValue(() => canonicalJson(expectedHead)))) {
477
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The remote space head changed while reading its current snapshot.")));
478
+ }
479
+ const size = sizeResult.rows[0];
480
+ const provenanceOperations = size === undefined ? null : integer(rowValue(size, "provenance_count", 0));
481
+ const provenanceBytes = size === undefined ? null : integer(rowValue(size, "provenance_bytes", 1));
482
+ const recordCount = size === undefined ? null : integer(rowValue(size, "record_count", 2));
483
+ const recordBytes = size === undefined ? null : integer(rowValue(size, "record_bytes", 3));
484
+ const dependencyBytes = size === undefined ? null : integer(rowValue(size, "dependency_bytes", 4));
485
+ if (provenanceOperations === null || provenanceBytes === null || recordCount === null
486
+ || recordBytes === null || dependencyBytes === null
487
+ || provenanceOperations > OH_LIBSQL_STORE_LIMITS_V1.historyOperations
488
+ || provenanceBytes > OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes
489
+ || recordBytes > OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes
490
+ || dependencyBytes > OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes) {
491
+ return yield* Effect.fail(libSqlFailure(new RangeError("The current libSQL materialization exceeds its provider-safe response bounds.")));
492
+ }
493
+ if (recordResult.rows.length !== recordCount || provenanceResult.rows.length !== provenanceOperations) {
494
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The provider-safe snapshot queries omitted bounded authority rows.")));
495
+ }
496
+ const history = historyResult.rows[0];
497
+ const operationCount = history === undefined ? null : integer(rowValue(history, "count", 0));
498
+ const minimumValue = history === undefined ? undefined : rowValue(history, "minimum", 1);
499
+ const maximumValue = history === undefined ? undefined : rowValue(history, "maximum", 2);
500
+ const minimumSequence = history === undefined ? null : integer(rowValue(history, "minimum", 1));
501
+ const maximumSequence = history === undefined ? null : integer(rowValue(history, "maximum", 2));
502
+ if (operationCount !== head.sequence
503
+ || (head.sequence === 0 && (minimumValue !== null || maximumValue !== null))
504
+ || (head.sequence > 0 && (minimumSequence !== 1 || maximumSequence !== head.sequence))) {
505
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote operation history does not exactly cover its current head.")));
506
+ }
507
+ if (recordResult.rows.length > maximumRecords) {
508
+ return yield* Effect.fail(libSqlFailure(new RangeError("The remote graph exceeds the requested record snapshot bound.")));
509
+ }
510
+ const provenanceBySha256 = new Map<Sha256Hex, OhOperationV1>();
511
+ for (const row of provenanceResult.rows) {
512
+ const operation = (yield* libSqlValue(() => parseOperationRow(row, { spaceId: this.binding.spaceId })));
513
+ if (provenanceBySha256.has(operation.operationSha256)) {
514
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("A current materialization provenance operation is invalid.")));
515
+ }
516
+ provenanceBySha256.set(operation.operationSha256, operation);
517
+ }
518
+ if (provenanceBySha256.size !== provenanceOperations
519
+ || (head.operationSha256 !== null && !provenanceBySha256.has(head.operationSha256))) {
520
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The current materialization omitted required provenance operations.")));
521
+ }
522
+ if (head.sequence > 0) {
523
+ const terminal = head.operationSha256 === null ? undefined : provenanceBySha256.get(head.operationSha256);
524
+ if (terminal === undefined || terminal.sequence !== head.sequence
525
+ || terminal.graphRevisionSha256 !== head.graphRevisionSha256
526
+ || terminal.recordsSha256 !== head.recordsSha256) {
527
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote space head differs from its terminal canonical operation.")));
528
+ }
529
+ }
530
+ const materialized = (yield* libSqlValue(() => recordResult.rows.map((row) => {
531
+ const json = rowValue(row, "record_json", 3);
532
+ if (typeof json !== "string") throw new OhIntegrityError("A materialized remote record is not JSON text.");
533
+ let value: unknown;
534
+ try { value = JSON.parse(json); } catch { throw new OhIntegrityError("A materialized remote record is invalid."); }
535
+ const record = parseKnowledgeGraphRecordV1(value);
536
+ const operationSha256 = parseSha256Hex(rowValue(row, "operation_sha256", 4));
537
+ const sequence = integer(rowValue(row, "sequence", 5));
538
+ if (record === null || canonicalJson(record) !== json || operationSha256 === null
539
+ || sequence === null || sequence < 1 || sequence > head.sequence
540
+ || rowValue(row, "record_key", 0) !== record.key
541
+ || rowValue(row, "kind", 1) !== record.kind
542
+ || rowValue(row, "record_sha256", 2) !== record.recordSha256) {
543
+ throw new OhIntegrityError("A materialized remote record is invalid.");
544
+ }
545
+ const provenance = provenanceBySha256.get(operationSha256);
546
+ if (provenance === undefined || provenance.sequence !== sequence
547
+ || !provenance.changes.some((change) => change.kind === "put"
548
+ && canonicalJson(change.record) === json)) {
549
+ throw new OhIntegrityError("A materialized remote record has no exact canonical provenance put.");
550
+ }
551
+ return { record, sequence };
552
+ })));
553
+ const records = (yield* libSqlValue(() => materialized.map(({ record }) => record)));
554
+ if ((yield* libSqlValue(() => canonicalSha256(records.map(knowledgeGraphRecordRefV1)))) !== head.recordsSha256) {
555
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Materialized remote records do not reproduce the current head.")));
556
+ }
557
+ const dependencyRows = (yield* libSqlValue(() => dependencyResult.rows.map((row) => ({
558
+ dependency_key: rowValue(row, "dependency_key", 1),
559
+ record_key: rowValue(row, "record_key", 0),
560
+ }))));
561
+ const expectedDependencies = (yield* libSqlValue(() => records.flatMap((record) =>
562
+ record.dependencies.map((dependency) => ({ dependency_key: dependency, record_key: record.key })))));
563
+ if ((yield* libSqlValue(() => canonicalJson(dependencyRows))) !== (yield* libSqlValue(() => canonicalJson(expectedDependencies)))) {
564
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Materialized remote dependencies do not match their record envelopes.")));
565
+ }
566
+ return { head, records, v: 1 };
567
+ });
568
+ }
569
+
570
+ snapshot(options: Readonly<{
571
+ head?: OhHeadRefV1;
572
+ maximumRecords?: number;
573
+ }> = {}): Effect.Effect<OhSnapshotV1, LibSqlFailure> {
574
+ return Effect.gen(this, function* () {
575
+ yield* libSqlValue(() => this.#assertOpen());
576
+ const current = yield* this.head();
577
+ const target = options.head === undefined ? current : yield* this.#headAt(options.head);
578
+ if (target.sequence > current.sequence) return yield* Effect.fail(libSqlFailure(new OhConflictError("The requested head is ahead of this space.")));
579
+ const maximumRecords = options.maximumRecords ?? OH_GRAPH_LIMITS_V1.recordsPerSnapshot;
580
+ if (!Number.isSafeInteger(maximumRecords) || maximumRecords < 1
581
+ || maximumRecords > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
582
+ return yield* Effect.fail(libSqlFailure(new RangeError(`maximumRecords must be an integer from 1 through ${OH_GRAPH_LIMITS_V1.recordsPerSnapshot}.`)));
583
+ }
584
+ if (target.operationSha256 === current.operationSha256) {
585
+ return yield* this.#currentMaterializedSnapshot(current, maximumRecords);
586
+ }
587
+ if (target.sequence > OH_LIBSQL_STORE_LIMITS_V1.historyOperations) {
588
+ return yield* Effect.fail(libSqlFailure(new RangeError("The requested libSQL history exceeds its operation replay bound.")));
589
+ }
590
+ const historyResults = yield* this.#client.batch([
591
+ {
592
+ sql: `SELECT count(*) AS count, min(operation.sequence) AS minimum,
593
+ max(operation.sequence) AS maximum,
594
+ coalesce(sum(length(CAST(operation.operation_json AS BLOB))), 0) AS canonical_bytes,
595
+ coalesce(sum(${OPERATION_RESPONSE_BYTES}), 0) AS response_bytes
596
+ FROM oh_authority_operations AS operation
597
+ WHERE operation.space_id = ? AND operation.sequence <= ?`,
598
+ args: [this.binding.spaceId, target.sequence]
599
+ },
600
+ {
601
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations AS operation
602
+ WHERE operation.space_id = ? AND operation.sequence <= ?
603
+ AND (SELECT coalesce(sum(length(CAST(candidate.operation_json AS BLOB))), 0)
604
+ FROM oh_authority_operations AS candidate
605
+ WHERE candidate.space_id = ? AND candidate.sequence <= ?) <= ?
606
+ AND (SELECT coalesce(sum(${OPERATION_RESPONSE_BYTES.replaceAll("operation.", "candidate.")}), 0)
607
+ FROM oh_authority_operations AS candidate
608
+ WHERE candidate.space_id = ? AND candidate.sequence <= ?) <= ?
609
+ ORDER BY operation.sequence`, args: [this.binding.spaceId, target.sequence,
610
+ this.binding.spaceId, target.sequence, OH_LIBSQL_STORE_LIMITS_V1.historyBytes,
611
+ this.binding.spaceId, target.sequence, OH_LIBSQL_STORE_LIMITS_V1.providerResponseBytes]
612
+ },
613
+ { sql: PURGE_ROW_SELECT, args: [this.binding.spaceId] },
614
+ ], "read");
615
+ if (historyResults.length !== 3) {
616
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete history batch.")));
617
+ }
618
+ const [historySizeResult, historyRowResult, purgeResult] = historyResults as
619
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1];
620
+ const historySizeRow = historySizeResult.rows[0];
621
+ const historyBytes = historySizeRow === undefined ? null
622
+ : integer(rowValue(historySizeRow, "canonical_bytes", 3));
623
+ const responseBytes = historySizeRow === undefined ? null
624
+ : integer(rowValue(historySizeRow, "response_bytes", 4));
625
+ if (historyBytes === null || historyBytes > OH_LIBSQL_STORE_LIMITS_V1.historyBytes
626
+ || responseBytes === null || responseBytes > OH_LIBSQL_STORE_LIMITS_V1.providerResponseBytes) {
627
+ return yield* Effect.fail(libSqlFailure(new RangeError("The requested libSQL history exceeds its provider-safe replay bounds.")));
628
+ }
629
+ const historyCount = historySizeRow === undefined ? null : integer(rowValue(historySizeRow, "count", 0));
630
+ const minimumSequence = historySizeRow === undefined ? null : integer(rowValue(historySizeRow, "minimum", 1));
631
+ const maximumSequence = historySizeRow === undefined ? null : integer(rowValue(historySizeRow, "maximum", 2));
632
+ if (historyCount !== target.sequence
633
+ || (target.sequence === 0 && (rowValue(historySizeRow!, "minimum", 1) !== null
634
+ || rowValue(historySizeRow!, "maximum", 2) !== null))
635
+ || (target.sequence > 0 && (minimumSequence !== 1 || maximumSequence !== target.sequence))
636
+ || historyRowResult.rows.length !== historyCount) {
637
+ const purgeRow = purgeResult.rows[0];
638
+ if (purgeRow !== undefined) {
639
+ const purge = (yield* libSqlValue(() => parsePurgeReceiptRow(purgeRow, this.binding.spaceId, this.binding.bindingSha256)));
640
+ this.#purged = purge;
641
+ return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError(purge)));
642
+ }
643
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote operation history does not exactly cover the requested head.")));
644
+ }
645
+ const operations = (yield* libSqlValue(() => historyRowResult.rows.map((row) => parseOperationRow(row,
646
+ { spaceId: this.binding.spaceId }))));
647
+ const snapshot = (yield* libSqlValue(() => replayOhOperationsV1(this.binding.spaceId, operations, maximumRecords)));
648
+ if (snapshot.head.operationSha256 !== target.operationSha256
649
+ || snapshot.head.recordsSha256 !== target.recordsSha256) {
650
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Remote operation replay does not reproduce the requested head.")));
651
+ }
652
+ return snapshot;
653
+ });
654
+ }
655
+
656
+ changesSince(
657
+ fromValue: OhHeadRefV1,
658
+ options: Readonly<{ limit?: number; through?: OhHeadRefV1 }> = {},
659
+ ): Effect.Effect<OhChangesPageV1, LibSqlFailure> {
660
+ return Effect.gen(this, function* () {
661
+ yield* libSqlValue(() => this.#assertOpen());
662
+ const from = (yield* libSqlValue(() => parseOhHeadRefV1(fromValue)));
663
+ if (from === null) return yield* Effect.fail(libSqlFailure(new TypeError("Invalid change-feed cursor.")));
664
+ const requestedThrough = options.through === undefined ? undefined : (yield* libSqlValue(() => parseOhHeadRefV1(options.through)));
665
+ if (requestedThrough === null) return yield* Effect.fail(libSqlFailure(new TypeError("Invalid change-feed through head.")));
666
+ const limit = (yield* libSqlValue(() => normalizeLimit(options.limit, OH_LIBSQL_STORE_LIMITS_V1.changeFeedLimit,
667
+ OH_LIBSQL_STORE_LIMITS_V1.changeFeedLimit)));
668
+ const throughSequence = requestedThrough?.sequence ?? null;
669
+ const results = yield* this.#client.batch([
670
+ {
671
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256, records_sha256, sequence
672
+ FROM oh_authority_spaces WHERE space_id = ?`, args: [this.binding.spaceId]
673
+ },
674
+ {
675
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
676
+ WHERE space_id = ? AND sequence = ?`, args: [this.binding.spaceId, from.sequence]
677
+ },
678
+ {
679
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
680
+ WHERE space_id = ? AND sequence = ?`, args: [this.binding.spaceId, throughSequence]
681
+ },
682
+ {
683
+ sql: `SELECT count(*) AS count, coalesce(sum(response_bytes), 0) AS response_bytes FROM (
684
+ SELECT ${OPERATION_RESPONSE_BYTES.replaceAll("operation.", "candidate.")} AS response_bytes
685
+ FROM oh_authority_operations AS candidate
686
+ WHERE candidate.space_id = ? AND candidate.sequence > ?
687
+ AND candidate.sequence <= coalesce(?,
688
+ (SELECT sequence FROM oh_authority_spaces WHERE space_id = ?))
689
+ ORDER BY candidate.sequence LIMIT ?
690
+ )`, args: [this.binding.spaceId, from.sequence, throughSequence,
691
+ this.binding.spaceId, limit + 1]
692
+ },
693
+ {
694
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
695
+ AS operation WHERE operation.space_id = ? AND operation.sequence > ?
696
+ AND operation.sequence <= coalesce(?,
697
+ (SELECT sequence FROM oh_authority_spaces WHERE space_id = ?))
698
+ AND (SELECT coalesce(sum(response_bytes), 0) FROM (
699
+ SELECT ${OPERATION_RESPONSE_BYTES.replaceAll("operation.", "candidate.")} AS response_bytes
700
+ FROM oh_authority_operations AS candidate
701
+ WHERE candidate.space_id = ? AND candidate.sequence > ?
702
+ AND candidate.sequence <= coalesce(?,
703
+ (SELECT sequence FROM oh_authority_spaces WHERE space_id = ?))
704
+ ORDER BY candidate.sequence LIMIT ?
705
+ )) <= ?
706
+ ORDER BY operation.sequence LIMIT ?`,
707
+ args: [this.binding.spaceId, from.sequence, throughSequence, this.binding.spaceId,
708
+ this.binding.spaceId, from.sequence, throughSequence, this.binding.spaceId, limit + 1,
709
+ OH_LIBSQL_STORE_LIMITS_V1.providerResponseBytes, limit + 1]
710
+ },
711
+ { sql: PURGE_ROW_SELECT, args: [this.binding.spaceId] },
712
+ ], "read");
713
+ if (results.length !== 6) return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete change-feed batch.")));
714
+ const [currentResult, fromResult, throughResult, pageSizeResult, pageResult, purgeResult] = results as
715
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1,
716
+ OhLibSqlResultV1, OhLibSqlResultV1];
717
+ const currentRow = currentResult.rows[0];
718
+ if (currentRow === undefined) {
719
+ const purgeRow = purgeResult.rows[0];
720
+ if (purgeRow !== undefined) {
721
+ const purge = (yield* libSqlValue(() => parsePurgeReceiptRow(purgeRow, this.binding.spaceId, this.binding.bindingSha256)));
722
+ this.#purged = purge;
723
+ return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError(purge)));
724
+ }
725
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote Oh space disappeared while reading its change feed.")));
726
+ }
727
+ const current = (yield* libSqlValue(() => parseHeadRow(currentRow)));
728
+ const resolveHead = (reference: OhHeadRefV1, result: OhLibSqlResultV1): OhHeadV1 => {
729
+ if (reference.sequence === 0) return emptyOhHeadV1();
730
+ const row = result.rows[0];
731
+ if (row === undefined) throw new OhConflictError("A requested change-feed head is not present in this space.");
732
+ const operation = parseOperationRow(row, { spaceId: this.binding.spaceId });
733
+ if (operation.spaceId !== this.binding.spaceId || operation.sequence !== reference.sequence
734
+ || operation.operationSha256 !== reference.operationSha256) {
735
+ throw new OhConflictError("A requested change-feed sequence identifies a different operation head.");
736
+ }
737
+ return {
738
+ generation: operation.sequence, graphRevisionSha256: operation.graphRevisionSha256,
739
+ operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
740
+ sequence: operation.sequence, v: 1
741
+ };
742
+ };
743
+ const fromHead = yield* libSqlValue(() => resolveHead(from, fromResult));
744
+ const through = requestedThrough === undefined ? current : yield* libSqlValue(() => resolveHead(requestedThrough, throughResult));
745
+ if (fromHead.sequence > through.sequence || through.sequence > current.sequence) {
746
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The change-feed bounds do not identify one remote history prefix.")));
747
+ }
748
+ const pageSizeRow = pageSizeResult.rows[0];
749
+ const pageCount = pageSizeRow === undefined ? null : integer(rowValue(pageSizeRow, "count", 0));
750
+ const pageResponseBytes = pageSizeRow === undefined ? null
751
+ : integer(rowValue(pageSizeRow, "response_bytes", 1));
752
+ if (pageCount === null || pageCount > limit + 1 || pageResponseBytes === null) {
753
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote change feed returned invalid response bounds.")));
754
+ }
755
+ if (pageResponseBytes > OH_LIBSQL_STORE_LIMITS_V1.providerResponseBytes) {
756
+ return yield* Effect.fail(libSqlFailure(new RangeError("The requested change-feed page exceeds its provider response bound.")));
757
+ }
758
+ const parsed = (yield* libSqlValue(() => pageResult.rows.map((row) => parseOperationRow(row, { spaceId: this.binding.spaceId }))));
759
+ if (parsed.length !== pageCount) {
760
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote change feed omitted provider-bounded rows.")));
761
+ }
762
+ const hasMore = parsed.length > limit;
763
+ const operations = parsed.slice(0, limit);
764
+ let prior: OhHeadRefV1 = fromHead;
765
+ for (const operation of parsed) {
766
+ if (operation.spaceId !== this.binding.spaceId
767
+ || operation.sequence !== prior.sequence + 1
768
+ || operation.parentOperationSha256 !== prior.operationSha256) {
769
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote change feed contains a gap or fork.")));
770
+ }
771
+ prior = { operationSha256: operation.operationSha256, sequence: operation.sequence };
772
+ }
773
+ if (!hasMore && (prior.sequence !== through.sequence
774
+ || prior.operationSha256 !== through.operationSha256)) {
775
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote change feed does not reach its pinned through head.")));
776
+ }
777
+ const last = operations.at(-1);
778
+ const to = last === undefined
779
+ ? { operationSha256: fromHead.operationSha256, sequence: fromHead.sequence }
780
+ : { operationSha256: last.operationSha256, sequence: last.sequence };
781
+ return {
782
+ from: { operationSha256: fromHead.operationSha256, sequence: fromHead.sequence },
783
+ hasMore, operations, through, to, v: 1
784
+ };
785
+ });
786
+ }
787
+
788
+ #assertMaterializedSnapshot(snapshot: OhSnapshotV1): Effect.Effect<void, LibSqlFailure> {
789
+ return Effect.gen(this, function* () {
790
+ if (snapshot.head.sequence > OH_LIBSQL_STORE_LIMITS_V1.historyOperations) {
791
+ return yield* Effect.fail(libSqlFailure(new RangeError("The libSQL authority exceeds its explicit verification operation bound.")));
792
+ }
793
+ const verificationResults = yield* this.#client.batch([
794
+ {
795
+ sql: `SELECT * FROM (WITH bounded_operation AS (
796
+ SELECT * FROM oh_authority_operations
797
+ WHERE space_id = ? AND sequence <= ?
798
+ ) SELECT count(*) AS operation_count, min(operation.sequence) AS minimum,
799
+ max(operation.sequence) AS maximum,
800
+ coalesce(sum(length(CAST(operation.operation_json AS BLOB))), 0) AS canonical_bytes,
801
+ coalesce(sum(${OPERATION_RESPONSE_BYTES}), 0) AS operation_response_bytes,
802
+ (SELECT coalesce(sum(${RECORD_RESPONSE_BYTES}), 0)
803
+ FROM oh_authority_records AS record WHERE record.space_id = ?) AS record_response_bytes,
804
+ (SELECT coalesce(sum(${DEPENDENCY_RESPONSE_BYTES}), 0)
805
+ FROM oh_authority_dependencies AS dependency
806
+ WHERE dependency.space_id = ?) AS dependency_response_bytes,
807
+ (SELECT coalesce(sum(${OPERATION_RECORD_RESPONSE_BYTES}), 0)
808
+ FROM oh_authority_operation_records AS materialized
809
+ JOIN bounded_operation AS owner
810
+ ON owner.operation_sha256 = materialized.operation_sha256) AS operation_record_response_bytes
811
+ FROM bounded_operation AS operation)`, args: [this.binding.spaceId, snapshot.head.sequence,
812
+ this.binding.spaceId, this.binding.spaceId]
813
+ },
814
+ {
815
+ sql: `SELECT ${OPERATION_ROW_COLUMNS}
816
+ FROM oh_authority_operations AS operation
817
+ WHERE operation.space_id = ? AND operation.sequence <= ?
818
+ AND (SELECT coalesce(sum(length(CAST(candidate.operation_json AS BLOB))), 0)
819
+ FROM oh_authority_operations AS candidate
820
+ WHERE candidate.space_id = ? AND candidate.sequence <= ?) <= ?
821
+ AND (SELECT coalesce(sum(${OPERATION_RESPONSE_BYTES.replaceAll("operation.", "candidate.")}), 0)
822
+ FROM oh_authority_operations AS candidate
823
+ WHERE candidate.space_id = ? AND candidate.sequence <= ?) <= ?
824
+ ORDER BY operation.sequence`, args: [this.binding.spaceId, snapshot.head.sequence,
825
+ this.binding.spaceId, snapshot.head.sequence, OH_LIBSQL_STORE_LIMITS_V1.historyBytes,
826
+ this.binding.spaceId, snapshot.head.sequence, OH_LIBSQL_STORE_LIMITS_V1.providerResponseBytes]
827
+ },
828
+ {
829
+ sql: `SELECT record_key, kind, record_sha256, record_json, operation_sha256, sequence
830
+ FROM oh_authority_records AS record WHERE record.space_id = ?
831
+ AND (SELECT coalesce(sum(${RECORD_RESPONSE_BYTES}), 0)
832
+ FROM oh_authority_records AS record WHERE record.space_id = ?) <= ?
833
+ ORDER BY record.record_key`, args: [this.binding.spaceId, this.binding.spaceId,
834
+ OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes]
835
+ },
836
+ {
837
+ sql: `SELECT record_key, dependency_key FROM oh_authority_dependencies AS dependency
838
+ WHERE dependency.space_id = ?
839
+ AND (SELECT coalesce(sum(${DEPENDENCY_RESPONSE_BYTES}), 0)
840
+ FROM oh_authority_dependencies AS dependency WHERE dependency.space_id = ?) <= ?
841
+ ORDER BY dependency.record_key, dependency.dependency_key`, args: [this.binding.spaceId,
842
+ this.binding.spaceId, OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes]
843
+ },
844
+ {
845
+ sql: `SELECT materialized.space_id, materialized.operation_sha256, materialized.ordinal,
846
+ materialized.record_key, materialized.change_kind, materialized.record_sha256
847
+ FROM oh_authority_operation_records AS materialized
848
+ JOIN oh_authority_operations AS operation
849
+ ON operation.operation_sha256 = materialized.operation_sha256
850
+ WHERE operation.space_id = ? AND operation.sequence <= ?
851
+ AND (SELECT coalesce(sum(${OPERATION_RECORD_RESPONSE_BYTES}), 0)
852
+ FROM oh_authority_operation_records AS materialized
853
+ JOIN oh_authority_operations AS owner
854
+ ON owner.operation_sha256 = materialized.operation_sha256
855
+ WHERE owner.space_id = ? AND owner.sequence <= ?) <= ?
856
+ ORDER BY operation.sequence, materialized.ordinal`, args: [this.binding.spaceId,
857
+ snapshot.head.sequence, this.binding.spaceId, snapshot.head.sequence,
858
+ OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes]
859
+ },
860
+ {
861
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256, records_sha256, sequence
862
+ FROM oh_authority_spaces WHERE space_id = ?`, args: [this.binding.spaceId]
863
+ },
864
+ ], "read");
865
+ if (verificationResults.length !== 6) {
866
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete verification batch.")));
867
+ }
868
+ const [sizeResult, operationResult, recordResult, dependencyResult, operationRecordResult, headResult] =
869
+ verificationResults as [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1,
870
+ OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1];
871
+ const sizeRow = sizeResult.rows[0];
872
+ const operationCount = sizeRow === undefined ? null : integer(rowValue(sizeRow, "operation_count", 0));
873
+ const minimumValue = sizeRow === undefined ? undefined : rowValue(sizeRow, "minimum", 1);
874
+ const maximumValue = sizeRow === undefined ? undefined : rowValue(sizeRow, "maximum", 2);
875
+ const minimumSequence = sizeRow === undefined ? null : integer(rowValue(sizeRow, "minimum", 1));
876
+ const maximumSequence = sizeRow === undefined ? null : integer(rowValue(sizeRow, "maximum", 2));
877
+ const historyBytes = sizeRow === undefined ? null : integer(rowValue(sizeRow, "canonical_bytes", 3));
878
+ const operationResponseBytes = sizeRow === undefined ? null
879
+ : integer(rowValue(sizeRow, "operation_response_bytes", 4));
880
+ const recordResponseBytes = sizeRow === undefined ? null
881
+ : integer(rowValue(sizeRow, "record_response_bytes", 5));
882
+ const dependencyResponseBytes = sizeRow === undefined ? null
883
+ : integer(rowValue(sizeRow, "dependency_response_bytes", 6));
884
+ const operationRecordResponseBytes = sizeRow === undefined ? null
885
+ : integer(rowValue(sizeRow, "operation_record_response_bytes", 7));
886
+ if (historyBytes === null || historyBytes > OH_LIBSQL_STORE_LIMITS_V1.historyBytes
887
+ || operationResponseBytes === null
888
+ || operationResponseBytes > OH_LIBSQL_STORE_LIMITS_V1.providerResponseBytes
889
+ || recordResponseBytes === null
890
+ || recordResponseBytes > OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes
891
+ || dependencyResponseBytes === null
892
+ || dependencyResponseBytes > OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes
893
+ || operationRecordResponseBytes === null
894
+ || operationRecordResponseBytes > OH_LIBSQL_STORE_LIMITS_V1.snapshotComponentBytes) {
895
+ return yield* Effect.fail(libSqlFailure(new RangeError("The libSQL authority exceeds its provider-safe verification bounds.")));
896
+ }
897
+ if (operationCount !== snapshot.head.sequence
898
+ || (snapshot.head.sequence === 0 && (minimumValue !== null || maximumValue !== null))
899
+ || (snapshot.head.sequence > 0
900
+ && (minimumSequence !== 1 || maximumSequence !== snapshot.head.sequence))
901
+ || operationResult.rows.length !== operationCount) {
902
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote operation history does not exactly cover its verified head.")));
903
+ }
904
+ const headRow = headResult.rows[0];
905
+ if (headRow === undefined) return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority lost its head during verification.")));
906
+ if ((yield* libSqlValue(() => canonicalJson(parseHeadRow(headRow)))) !== (yield* libSqlValue(() => canonicalJson(snapshot.head)))) {
907
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The remote authority head changed during verification.")));
908
+ }
909
+ const operations = (yield* libSqlValue(() => operationResult.rows.map((row) => {
910
+ return parseOperationRow(row, { spaceId: this.binding.spaceId });
911
+ })));
912
+ const replayed = (yield* libSqlValue(() => replayOhOperationsV1(this.binding.spaceId, operations)));
913
+ if ((yield* libSqlValue(() => canonicalJson(replayed))) !== (yield* libSqlValue(() => canonicalJson(snapshot)))) {
914
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Remote operation replay changed during materialization verification.")));
915
+ }
916
+ const materializedBy = new Map<string, Readonly<{ operationSha256: Sha256Hex; sequence: number }>>();
917
+ for (const operation of operations) {
918
+ for (const change of operation.changes) {
919
+ const key = change.kind === "put" ? change.record.key : change.key;
920
+ if (change.kind === "put") materializedBy.set(key,
921
+ { operationSha256: operation.operationSha256, sequence: operation.sequence });
922
+ else materializedBy.delete(key);
923
+ }
924
+ }
925
+ const records: KnowledgeGraphRecordV1[] = (yield* libSqlValue(() => recordResult.rows.map((row) => {
926
+ const json = rowValue(row, "record_json", 3);
927
+ if (typeof json !== "string") throw new OhIntegrityError("A materialized remote record is not JSON text.");
928
+ let value: unknown;
929
+ try { value = JSON.parse(json); } catch { throw new OhIntegrityError("A materialized remote record is invalid."); }
930
+ const record = parseKnowledgeGraphRecordV1(value);
931
+ const provenance = record === null ? undefined : materializedBy.get(record.key);
932
+ if (record === null || canonicalJson(record) !== json
933
+ || rowValue(row, "record_key", 0) !== record.key
934
+ || rowValue(row, "kind", 1) !== record.kind
935
+ || rowValue(row, "record_sha256", 2) !== record.recordSha256
936
+ || provenance === undefined
937
+ || rowValue(row, "operation_sha256", 4) !== provenance.operationSha256
938
+ || integer(rowValue(row, "sequence", 5)) !== provenance.sequence) {
939
+ throw new OhIntegrityError("A materialized remote record differs from operation replay.");
940
+ }
941
+ return record;
942
+ })));
943
+ if ((yield* libSqlValue(() => canonicalJson(records))) !== (yield* libSqlValue(() => canonicalJson(snapshot.records)))) {
944
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Remote materialized records do not match operation replay.")));
945
+ }
946
+ const dependencyRows = (yield* libSqlValue(() => dependencyResult.rows.map((row) => ({
947
+ dependency_key: rowValue(row, "dependency_key", 1),
948
+ record_key: rowValue(row, "record_key", 0),
949
+ }))));
950
+ const expectedDependencies = (yield* libSqlValue(() => snapshot.records.flatMap((record) =>
951
+ record.dependencies.map((dependency) => ({ dependency_key: dependency, record_key: record.key })))));
952
+ if ((yield* libSqlValue(() => canonicalJson(dependencyRows))) !== (yield* libSqlValue(() => canonicalJson(expectedDependencies)))) {
953
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Remote materialized dependencies do not match operation replay.")));
954
+ }
955
+ const operationRecordRows = (yield* libSqlValue(() => operationRecordResult.rows.map((row) => ({
956
+ change_kind: rowValue(row, "change_kind", 4),
957
+ operation_sha256: rowValue(row, "operation_sha256", 1),
958
+ ordinal: integer(rowValue(row, "ordinal", 2)),
959
+ record_key: rowValue(row, "record_key", 3),
960
+ record_sha256: rowValue(row, "record_sha256", 5),
961
+ space_id: rowValue(row, "space_id", 0),
962
+ }))));
963
+ const expectedOperationRecords = (yield* libSqlValue(() => operations.flatMap((operation) =>
964
+ operation.changes.map((change, ordinal) => ({
965
+ change_kind: change.kind,
966
+ operation_sha256: operation.operationSha256, ordinal,
967
+ record_key: change.kind === "put" ? change.record.key : change.key,
968
+ record_sha256: change.kind === "put" ? change.record.recordSha256 : change.priorSha256,
969
+ space_id: this.binding.spaceId
970
+ })))));
971
+ if ((yield* libSqlValue(() => canonicalJson(operationRecordRows))) !== (yield* libSqlValue(() => canonicalJson(expectedOperationRecords)))) {
972
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("Remote operation-record rows do not match operation replay.")));
973
+ }
974
+ });
975
+ }
976
+
977
+ #operationById(operationId: string): Effect.Effect<OhOperationV1 | null, LibSqlFailure> {
978
+ return Effect.gen(this, function* () {
979
+ const row = yield* queryOne(this.#client, {
980
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
981
+ WHERE space_id = ? AND operation_id = ?`, args: [this.binding.spaceId, operationId]
982
+ });
983
+ if (row === null) return null;
984
+ return (yield* libSqlValue(() => parseOperationRow(row, { operationId, spaceId: this.binding.spaceId })));
985
+ });
986
+ }
987
+
988
+ #commitPreflight(operationId: string): Effect.Effect<Readonly<{
989
+ current: OhHeadV1;
990
+ duplicate: OhOperationV1 | null;
991
+ }>, LibSqlFailure> {
992
+ return Effect.gen(this, function* () {
993
+ const results = yield* this.#client.batch([
994
+ {
995
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
996
+ WHERE space_id = ? AND operation_id = ?`, args: [this.binding.spaceId, operationId]
997
+ },
998
+ {
999
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256, records_sha256, sequence
1000
+ FROM oh_authority_spaces WHERE space_id = ?`, args: [this.binding.spaceId]
1001
+ },
1002
+ { sql: PURGE_ROW_SELECT, args: [this.binding.spaceId] },
1003
+ ], "read");
1004
+ if (results.length !== 3) return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete commit preflight.")));
1005
+ const [duplicateResult, headResult, purgeResult] = results as
1006
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1];
1007
+ const headRow = headResult.rows[0];
1008
+ if (headRow === undefined) {
1009
+ const purgeRow = purgeResult.rows[0];
1010
+ if (purgeRow !== undefined) {
1011
+ const purge = (yield* libSqlValue(() => parsePurgeReceiptRow(purgeRow, this.binding.spaceId, this.binding.bindingSha256)));
1012
+ this.#purged = purge;
1013
+ return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError(purge)));
1014
+ }
1015
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote space disappeared during commit preflight.")));
1016
+ }
1017
+ const duplicateRow = duplicateResult.rows[0];
1018
+ return {
1019
+ current: (yield* libSqlValue(() => parseHeadRow(headRow))), duplicate: duplicateRow === undefined ? null
1020
+ : (yield* libSqlValue(() => parseOperationRow(duplicateRow, { operationId, spaceId: this.binding.spaceId })))
1021
+ };
1022
+ });
1023
+ }
1024
+
1025
+ #assertOperationReachable(operation: OhOperationV1, expectedHead: OhHeadV1): Effect.Effect<void, LibSqlFailure> {
1026
+ return Effect.gen(this, function* () {
1027
+ if (operation.sequence < 1 || operation.sequence > expectedHead.sequence) {
1028
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("A remote idempotent operation is not reachable from the current head.")));
1029
+ }
1030
+ const results = yield* this.#client.batch([
1031
+ {
1032
+ sql: `SELECT * FROM (WITH RECURSIVE authority_chain(sequence, operation_sha256) AS (
1033
+ SELECT sequence, operation_sha256 FROM oh_authority_operations
1034
+ WHERE space_id = ? AND sequence = ? AND operation_sha256 = ?
1035
+ UNION ALL
1036
+ SELECT candidate.sequence, candidate.operation_sha256
1037
+ FROM oh_authority_operations AS candidate
1038
+ JOIN authority_chain AS prior
1039
+ ON candidate.space_id = ? AND candidate.sequence = prior.sequence + 1
1040
+ AND candidate.parent_operation_sha256 = prior.operation_sha256
1041
+ WHERE candidate.sequence <= ?
1042
+ ) SELECT count(*) AS count, min(sequence) AS minimum, max(sequence) AS maximum,
1043
+ (SELECT operation_sha256 FROM authority_chain ORDER BY sequence DESC LIMIT 1) AS terminal_sha256
1044
+ FROM authority_chain)`, args: [this.binding.spaceId, operation.sequence,
1045
+ operation.operationSha256, this.binding.spaceId, expectedHead.sequence]
1046
+ },
1047
+ {
1048
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
1049
+ WHERE space_id = ? AND sequence = ?`, args: [this.binding.spaceId, expectedHead.sequence]
1050
+ },
1051
+ {
1052
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256, records_sha256, sequence
1053
+ FROM oh_authority_spaces WHERE space_id = ?`, args: [this.binding.spaceId]
1054
+ },
1055
+ { sql: PURGE_ROW_SELECT, args: [this.binding.spaceId] },
1056
+ ], "read");
1057
+ if (results.length !== 4) return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete reachability proof.")));
1058
+ const [chainResult, terminalResult, headResult, purgeResult] = results as
1059
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1];
1060
+ const headRow = headResult.rows[0];
1061
+ if (headRow === undefined) {
1062
+ const purgeRow = purgeResult.rows[0];
1063
+ if (purgeRow !== undefined) {
1064
+ const purge = (yield* libSqlValue(() => parsePurgeReceiptRow(purgeRow, this.binding.spaceId, this.binding.bindingSha256)));
1065
+ this.#purged = purge;
1066
+ return yield* Effect.fail(libSqlFailure(new OhPurgedSpaceError(purge)));
1067
+ }
1068
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote space disappeared during an idempotency proof.")));
1069
+ }
1070
+ const current = (yield* libSqlValue(() => parseHeadRow(headRow)));
1071
+ if ((yield* libSqlValue(() => canonicalJson(current))) !== (yield* libSqlValue(() => canonicalJson(expectedHead)))) {
1072
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The remote space head changed during an idempotency proof.")));
1073
+ }
1074
+ const chain = chainResult.rows[0];
1075
+ const count = chain === undefined ? null : integer(rowValue(chain, "count", 0));
1076
+ const minimum = chain === undefined ? null : integer(rowValue(chain, "minimum", 1));
1077
+ const maximum = chain === undefined ? null : integer(rowValue(chain, "maximum", 2));
1078
+ const terminalSha256 = chain === undefined ? null : rowValue(chain, "terminal_sha256", 3);
1079
+ if (count !== expectedHead.sequence - operation.sequence + 1
1080
+ || minimum !== operation.sequence || maximum !== expectedHead.sequence
1081
+ || terminalSha256 !== expectedHead.operationSha256) {
1082
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("A remote idempotent operation has no exact path to the current head.")));
1083
+ }
1084
+ const terminalRow = terminalResult.rows[0];
1085
+ const expectedOperationSha256 = expectedHead.operationSha256;
1086
+ if (terminalRow === undefined || expectedOperationSha256 === null) {
1087
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote current head operation is missing.")));
1088
+ }
1089
+ const terminal = (yield* libSqlValue(() => parseOperationRow(terminalRow, {
1090
+ operationSha256: expectedOperationSha256,
1091
+ spaceId: this.binding.spaceId
1092
+ })));
1093
+ if (terminal.sequence !== expectedHead.sequence
1094
+ || terminal.graphRevisionSha256 !== expectedHead.graphRevisionSha256
1095
+ || terminal.recordsSha256 !== expectedHead.recordsSha256) {
1096
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote space head differs from its terminal canonical operation.")));
1097
+ }
1098
+ });
1099
+ }
1100
+
1101
+ commit(input: OhCommitInputV1): Effect.Effect<OhOperationV1, LibSqlFailure> {
1102
+ return Effect.gen(this, function* () {
1103
+ yield* libSqlValue(() => this.#assertOpen());
1104
+ const actorId = (yield* libSqlValue(() => safeCode(input.actorId)));
1105
+ const operationId = (yield* libSqlValue(() => safeCode(input.operationId)));
1106
+ const maximumOperationBytes = input.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
1107
+ const changes = (yield* libSqlValue(() => canonicalKnowledgeGraphChangesV1(input.changes)));
1108
+ if (actorId === null || operationId === null || changes.length === 0
1109
+ || !Number.isSafeInteger(maximumOperationBytes)
1110
+ || maximumOperationBytes < 1
1111
+ || maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
1112
+ return yield* Effect.fail(libSqlFailure(new TypeError("Invalid Oh commit input.")));
1113
+ }
1114
+ if (changes.length > OH_LIBSQL_STORE_LIMITS_V1.changesPerCommit) {
1115
+ return yield* Effect.fail(libSqlFailure(new RangeError("A direct libSQL commit exceeds its change-count bound.")));
1116
+ }
1117
+ const dependencies = (yield* libSqlValue(() => changes.reduce((count, change) => count
1118
+ + (change.kind === "put" ? change.record.dependencies.length : 0), 0)));
1119
+ if (dependencies > OH_LIBSQL_STORE_LIMITS_V1.dependenciesPerCommit) {
1120
+ return yield* Effect.fail(libSqlFailure(new RangeError("A direct libSQL commit exceeds its dependency-count bound.")));
1121
+ }
1122
+ const { current, duplicate } = yield* this.#commitPreflight(operationId);
1123
+ if (duplicate !== null) {
1124
+ yield* this.#assertOperationReachable(duplicate, current);
1125
+ if (duplicate.actorId !== actorId || (yield* libSqlValue(() => canonicalJson(duplicate.changes))) !== (yield* libSqlValue(() => canonicalJson(changes)))) {
1126
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The operation ID is already bound to different content.")));
1127
+ }
1128
+ const operationBytes = utf8ByteLength((yield* libSqlValue(() => canonicalJson(duplicate))));
1129
+ if (operationBytes > maximumOperationBytes) {
1130
+ return yield* Effect.fail(libSqlFailure(new OhOperationSizeError(operationBytes, maximumOperationBytes)));
1131
+ }
1132
+ return duplicate;
1133
+ }
1134
+ if (!Number.isSafeInteger(input.expectedHead.generation) || input.expectedHead.generation < 0
1135
+ || current.generation !== input.expectedHead.generation
1136
+ || current.operationSha256 !== input.expectedHead.operationSha256) {
1137
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The expected head does not match the current remote space head.")));
1138
+ }
1139
+ const snapshot = yield* this.#currentMaterializedSnapshot(current, OH_GRAPH_LIMITS_V1.recordsPerSnapshot);
1140
+ const instant = (yield* libSqlValue(() => input.instant)) ?? (yield* this.#client.currentInstant);
1141
+ const transition = (yield* libSqlValue(() => transitionOhSnapshotV1({
1142
+ actorId, changes,
1143
+ instant, maximumOperationBytes,
1144
+ operationId, snapshot, spaceId: this.binding.spaceId
1145
+ })));
1146
+ const operation = transition.operation;
1147
+ const operationJson = (yield* libSqlValue(() => canonicalJson(operation)));
1148
+ const operationBytes = utf8ByteLength(operationJson);
1149
+ if (operationBytes > maximumOperationBytes) {
1150
+ return yield* Effect.fail(libSqlFailure(new OhOperationSizeError(operationBytes, maximumOperationBytes)));
1151
+ }
1152
+ if (operationBytes > OH_LIBSQL_STORE_LIMITS_V1.operationBytes) {
1153
+ return yield* Effect.fail(libSqlFailure(new RangeError("A direct libSQL operation exceeds its canonical byte bound.")));
1154
+ }
1155
+ const existsOperation = "EXISTS (SELECT 1 FROM oh_authority_operations WHERE operation_sha256 = ?)";
1156
+ const statements: OhLibSqlStatementV1[] = [{
1157
+ sql: `INSERT INTO oh_authority_operations(operation_sha256, space_id, sequence,
1158
+ operation_id, parent_operation_sha256, graph_revision_sha256, records_sha256,
1159
+ operation_json, instant)
1160
+ SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?
1161
+ WHERE EXISTS (SELECT 1 FROM oh_authority_spaces
1162
+ WHERE space_id = ? AND generation = ? AND head_operation_sha256 IS ?)`,
1163
+ args: [operation.operationSha256, this.binding.spaceId, operation.sequence, operation.operationId,
1164
+ operation.parentOperationSha256, operation.graphRevisionSha256, operation.recordsSha256,
1165
+ operationJson, operation.instant, this.binding.spaceId, current.generation,
1166
+ current.operationSha256],
1167
+ }];
1168
+ for (const [ordinal, change] of operation.changes.entries()) {
1169
+ const key = change.kind === "put" ? change.record.key : change.key;
1170
+ const digest = change.kind === "put" ? change.record.recordSha256 : change.priorSha256;
1171
+ statements.push({
1172
+ sql: `INSERT INTO oh_authority_operation_records(space_id, operation_sha256,
1173
+ ordinal, record_key, change_kind, record_sha256)
1174
+ SELECT ?, ?, ?, ?, ?, ? WHERE ${existsOperation}`,
1175
+ args: [this.binding.spaceId, operation.operationSha256, ordinal, key, change.kind, digest,
1176
+ operation.operationSha256]
1177
+ });
1178
+ statements.push({
1179
+ sql: `DELETE FROM oh_authority_dependencies WHERE space_id = ? AND record_key = ?
1180
+ AND ${existsOperation}`, args: [this.binding.spaceId, key, operation.operationSha256]
1181
+ });
1182
+ if (change.kind === "put") {
1183
+ statements.push({
1184
+ sql: `INSERT INTO oh_authority_records(space_id, record_key, kind,
1185
+ record_sha256, record_json, operation_sha256, sequence)
1186
+ SELECT ?, ?, ?, ?, ?, ?, ? WHERE ${existsOperation}
1187
+ ON CONFLICT(space_id, record_key) DO UPDATE SET kind = excluded.kind,
1188
+ record_sha256 = excluded.record_sha256, record_json = excluded.record_json,
1189
+ operation_sha256 = excluded.operation_sha256, sequence = excluded.sequence`,
1190
+ args: [this.binding.spaceId, key, change.record.kind, change.record.recordSha256,
1191
+ (yield* libSqlValue(() => canonicalJson(change.record))), operation.operationSha256, operation.sequence,
1192
+ operation.operationSha256]
1193
+ });
1194
+ } else {
1195
+ statements.push({
1196
+ sql: `DELETE FROM oh_authority_records WHERE space_id = ? AND record_key = ?
1197
+ AND record_sha256 = ? AND ${existsOperation}`,
1198
+ args: [this.binding.spaceId, key, change.priorSha256, operation.operationSha256]
1199
+ });
1200
+ }
1201
+ }
1202
+ for (const change of operation.changes) {
1203
+ if (change.kind !== "put") continue;
1204
+ for (const dependency of change.record.dependencies) {
1205
+ statements.push({
1206
+ sql: `INSERT INTO oh_authority_dependencies(space_id, record_key, dependency_key)
1207
+ SELECT ?, ?, ? WHERE ${existsOperation}`,
1208
+ args: [this.binding.spaceId, change.record.key, dependency, operation.operationSha256]
1209
+ });
1210
+ }
1211
+ }
1212
+ statements.push({
1213
+ sql: `UPDATE oh_authority_spaces SET generation = ?, head_operation_sha256 = ?,
1214
+ graph_revision_sha256 = ?, records_sha256 = ?, sequence = ?, updated_at = ?
1215
+ WHERE space_id = ? AND generation = ? AND head_operation_sha256 IS ? AND ${existsOperation}`,
1216
+ args: [operation.sequence, operation.operationSha256, operation.graphRevisionSha256,
1217
+ operation.recordsSha256, operation.sequence, operation.instant, this.binding.spaceId,
1218
+ current.generation, current.operationSha256, operation.operationSha256]
1219
+ });
1220
+ statements.push({
1221
+ sql: `INSERT INTO oh_authority_commit_guards(value)
1222
+ SELECT 'invalid' WHERE NOT EXISTS (SELECT 1 FROM oh_authority_spaces
1223
+ WHERE space_id = ? AND generation = ? AND head_operation_sha256 = ?)`,
1224
+ args: [this.binding.spaceId, operation.sequence, operation.operationSha256]
1225
+ });
1226
+ statements.push({
1227
+ sql: `SELECT ${OPERATION_ROW_COLUMNS} FROM oh_authority_operations
1228
+ WHERE space_id = ? AND operation_id = ?`, args: [this.binding.spaceId, operationId]
1229
+ });
1230
+ statements.push({
1231
+ sql: `SELECT generation, graph_revision_sha256, head_operation_sha256, records_sha256, sequence
1232
+ FROM oh_authority_spaces WHERE space_id = ?`, args: [this.binding.spaceId]
1233
+ });
1234
+ const write = yield* Effect.either(this.#client.batch(statements, "write"));
1235
+ if (Either.isLeft(write)) {
1236
+ const error = write.left;
1237
+ const raced = yield* this.#operationById(operationId);
1238
+ const head = yield* this.head();
1239
+ if (raced !== null && raced.actorId === actorId
1240
+ && (yield* libSqlValue(() => canonicalJson(raced.changes))) === (yield* libSqlValue(() => canonicalJson(changes)))) {
1241
+ yield* this.#assertOperationReachable(raced, head);
1242
+ return raced;
1243
+ }
1244
+ if (head.operationSha256 !== current.operationSha256) {
1245
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The remote space head changed while committing.")));
1246
+ }
1247
+ return yield* Effect.fail(error);
1248
+ }
1249
+ const writeResults = write.right;
1250
+ if (writeResults.length !== statements.length) {
1251
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete commit result batch.")));
1252
+ }
1253
+ const persistedRow = writeResults.at(-2)?.rows[0];
1254
+ const persistedHeadRow = writeResults.at(-1)?.rows[0];
1255
+ if (persistedRow === undefined || persistedHeadRow === undefined) {
1256
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority omitted its persisted commit result.")));
1257
+ }
1258
+ const persisted = (yield* libSqlValue(() => parseOperationRow(persistedRow, { operationId, spaceId: this.binding.spaceId })));
1259
+ const persistedHead = (yield* libSqlValue(() => parseHeadRow(persistedHeadRow)));
1260
+ if ((yield* libSqlValue(() => canonicalJson(persisted))) !== (yield* libSqlValue(() => canonicalJson(operation)))
1261
+ || persistedHead.operationSha256 !== operation.operationSha256
1262
+ || persistedHead.sequence !== operation.sequence
1263
+ || persistedHead.graphRevisionSha256 !== operation.graphRevisionSha256
1264
+ || persistedHead.recordsSha256 !== operation.recordsSha256) {
1265
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority did not persist the committed operation exactly.")));
1266
+ }
1267
+ return persisted;
1268
+ });
1269
+ }
1270
+
1271
+ exportDependencyClosure(input: Readonly<{
1272
+ head?: OhHeadRefV1;
1273
+ maximumRecords?: number;
1274
+ roots: readonly string[];
1275
+ }>): Effect.Effect<OhDependencyClosureV1, LibSqlFailure> {
1276
+ return Effect.gen(this, function* () {
1277
+ if (!this.binding.profile.capabilities.dependencyClosureExport) {
1278
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("This remote profile does not permit dependency-closure export.")));
1279
+ }
1280
+ const snapshot = yield* this.snapshot({
1281
+ ...(input.head === undefined ? {} : { head: input.head }),
1282
+ ...(input.maximumRecords === undefined ? {} : { maximumRecords: input.maximumRecords })
1283
+ });
1284
+ return (yield* libSqlValue(() => createOhDependencyClosureV1({
1285
+ binding: this.binding,
1286
+ ...(input.maximumRecords === undefined ? {} : { maximumRecords: input.maximumRecords }),
1287
+ roots: input.roots, snapshot
1288
+ })));
1289
+ });
1290
+ }
1291
+
1292
+ verify(): Effect.Effect<OhStoreVerificationV1, LibSqlFailure> {
1293
+ return Effect.gen(this, function* () {
1294
+ yield* libSqlValue(() => this.#assertOpen());
1295
+ const snapshot = yield* this.snapshot();
1296
+ yield* this.#assertMaterializedSnapshot(snapshot);
1297
+ return {
1298
+ head: snapshot.head, integrity: "verified", operations: snapshot.head.sequence,
1299
+ records: snapshot.records.length, v: 1
1300
+ };
1301
+ });
1302
+ }
1303
+
1304
+ #assertPurgeComplete(expected: OhSpacePurgeReceiptV1): Effect.Effect<void, LibSqlFailure> {
1305
+ return Effect.gen(this, function* () {
1306
+ yield* assertRemotePurgeComplete(this.#client, this.binding, expected);
1307
+ });
1308
+ }
1309
+
1310
+ purgeWorkingSpace(purgedAt: string): Effect.Effect<OhSpacePurgeReceiptV1, LibSqlFailure> {
1311
+ return Effect.gen(this, function* () {
1312
+ yield* libSqlValue(() => this.#assertOpen());
1313
+ if (this.binding.profile.profileKind !== "working"
1314
+ || !this.binding.profile.capabilities.wholeSpacePurge) {
1315
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("Whole-space purge requires a bound working profile.")));
1316
+ }
1317
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1318
+ const existing = yield* this.#readPurge();
1319
+ if (existing !== null) {
1320
+ yield* this.#assertPurgeComplete(existing);
1321
+ this.#purged = existing;
1322
+ return existing;
1323
+ }
1324
+ const head = yield* this.head();
1325
+ const receipt = (yield* libSqlValue(() => createOhSpacePurgeReceiptV1({ binding: this.binding, priorHead: head, purgedAt })));
1326
+ const receiptExists = "EXISTS (SELECT 1 FROM oh_authority_purges WHERE space_id = ? AND receipt_sha256 = ?)";
1327
+ const statements: OhLibSqlStatementV1[] = [{
1328
+ sql: `INSERT INTO oh_authority_purges(space_id,
1329
+ binding_sha256, prior_operation_sha256, prior_sequence, purged_at, receipt_sha256, receipt_json)
1330
+ SELECT ?, ?, ?, ?, ?, ?, ? WHERE EXISTS (SELECT 1 FROM oh_authority_spaces
1331
+ WHERE space_id = ? AND generation = ? AND head_operation_sha256 IS ?)
1332
+ AND EXISTS (SELECT 1 FROM oh_authority_bindings WHERE space_id = ? AND binding_sha256 = ?)`,
1333
+ args: [this.binding.spaceId, this.binding.bindingSha256, head.operationSha256, head.sequence,
1334
+ receipt.purgedAt, receipt.receiptSha256, (yield* libSqlValue(() => canonicalJson(receipt))), this.binding.spaceId,
1335
+ head.generation, head.operationSha256, this.binding.spaceId, this.binding.bindingSha256]
1336
+ }];
1337
+ const guardedDelete = (table: string): OhLibSqlStatementV1 => ({
1338
+ sql: `DELETE FROM ${table} WHERE space_id = ? AND ${receiptExists}`,
1339
+ args: [this.binding.spaceId, this.binding.spaceId, receipt.receiptSha256],
1340
+ });
1341
+ statements.push({
1342
+ sql: `DELETE FROM oh_authority_operation_records
1343
+ WHERE operation_sha256 IN (SELECT operation_sha256 FROM oh_authority_operations WHERE space_id = ?)
1344
+ AND ${receiptExists}`,
1345
+ args: [this.binding.spaceId, this.binding.spaceId, receipt.receiptSha256]
1346
+ });
1347
+ statements.push(guardedDelete("oh_authority_dependencies"));
1348
+ statements.push(guardedDelete("oh_authority_records"));
1349
+ statements.push(guardedDelete("oh_authority_operations"));
1350
+ statements.push(guardedDelete("oh_authority_bindings"));
1351
+ statements.push(guardedDelete("oh_authority_spaces"));
1352
+ statements.push({
1353
+ sql: `INSERT INTO oh_authority_commit_guards(value)
1354
+ SELECT 'invalid' WHERE EXISTS (SELECT 1 FROM oh_authority_spaces WHERE space_id = ?)
1355
+ OR EXISTS (SELECT 1 FROM oh_authority_bindings WHERE space_id = ?)
1356
+ OR EXISTS (SELECT 1 FROM oh_authority_operations WHERE space_id = ?)
1357
+ OR EXISTS (SELECT 1 FROM oh_authority_operation_records WHERE space_id = ?)
1358
+ OR EXISTS (SELECT 1 FROM oh_authority_operation_records AS materialized
1359
+ LEFT JOIN oh_authority_operations AS operation
1360
+ ON operation.operation_sha256 = materialized.operation_sha256
1361
+ WHERE operation.operation_sha256 IS NULL OR operation.space_id <> materialized.space_id)
1362
+ OR EXISTS (SELECT 1 FROM oh_authority_records WHERE space_id = ?)
1363
+ OR EXISTS (SELECT 1 FROM oh_authority_dependencies WHERE space_id = ?)
1364
+ OR NOT ${receiptExists}`,
1365
+ args: [this.binding.spaceId, this.binding.spaceId, this.binding.spaceId,
1366
+ this.binding.spaceId, this.binding.spaceId, this.binding.spaceId,
1367
+ this.binding.spaceId, receipt.receiptSha256]
1368
+ });
1369
+ const write = yield* Effect.either(this.#client.batch(statements, "write"));
1370
+ if (Either.isLeft(write)) {
1371
+ const raced = yield* this.#readPurge();
1372
+ if (raced !== null) {
1373
+ yield* this.#assertPurgeComplete(raced);
1374
+ this.#purged = raced;
1375
+ return raced;
1376
+ }
1377
+ continue;
1378
+ }
1379
+ const persisted = yield* this.#readPurge();
1380
+ if (persisted !== null) {
1381
+ yield* this.#assertPurgeComplete(persisted);
1382
+ this.#purged = persisted;
1383
+ return persisted;
1384
+ }
1385
+ }
1386
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The remote working space changed repeatedly while purging.")));
1387
+ });
1388
+ }
1389
+
1390
+ }
1391
+
1392
+ /** Opens a direct libSQL/Turso authority; this is not operation-log sync. */
1393
+ export function createAuthority(
1394
+ client: LibSqlAuthorityClientService,
1395
+ options: OhLibSqlStoreAuthorityOptionsV1 = {},
1396
+ ): Effect.Effect<LibSqlStoreProgram, LibSqlFailure> {
1397
+ return Effect.gen(function* () {
1398
+ const profile = (yield* libSqlValue(() => parseOhStoreProfileV1(options.profile ?? OH_CANONICAL_STORE_PROFILE_V1)));
1399
+ if (profile === null) return yield* Effect.fail(libSqlFailure(new TypeError("Invalid libSQL store profile.")));
1400
+ const spaceId = options.spaceId ?? "default";
1401
+ const binding = (yield* libSqlValue(() => createOhStoreBindingV1({
1402
+ profile,
1403
+ realmId: options.realmId ?? `realm:${spaceId}`, spaceId, v: 1
1404
+ })));
1405
+ yield* verifyAuthoritySchema(client);
1406
+ yield* initializeSpace(client, binding);
1407
+ return new LibSqlStoreProgram(client, binding);
1408
+ });
1409
+ }
1410
+
1411
+ /**
1412
+ * Opens an already-bound direct libSQL/Turso authority without creating or
1413
+ * updating data. This seam is for separately held read or purge custody that
1414
+ * must fail closed instead of acquiring space-creation authority.
1415
+ */
1416
+ export function openExistingAuthority(
1417
+ client: LibSqlAuthorityClientService,
1418
+ options: OhLibSqlStoreAuthorityOptionsV1 = {},
1419
+ ): Effect.Effect<LibSqlStoreProgram, LibSqlFailure> {
1420
+ return Effect.gen(function* () {
1421
+ const profile = (yield* libSqlValue(() => parseOhStoreProfileV1(options.profile ?? OH_CANONICAL_STORE_PROFILE_V1)));
1422
+ if (profile === null) return yield* Effect.fail(libSqlFailure(new TypeError("Invalid libSQL store profile.")));
1423
+ const spaceId = options.spaceId ?? "default";
1424
+ const binding = (yield* libSqlValue(() => createOhStoreBindingV1({
1425
+ profile,
1426
+ realmId: options.realmId ?? `realm:${spaceId}`, spaceId, v: 1
1427
+ })));
1428
+ yield* verifyAuthoritySchema(client);
1429
+ yield* requireExistingSpace(client, binding);
1430
+ return new LibSqlStoreProgram(client, binding);
1431
+ });
1432
+ }
1433
+
1434
+ /**
1435
+ * Purges an existing working authority or atomically fences its exact binding
1436
+ * when creation never completed. The empty-space receipt prevents a delayed
1437
+ * creator from resurrecting abandoned custody without granting the purge
1438
+ * credential permission to create a space or binding.
1439
+ */
1440
+ export function purgeWorkingSpace(
1441
+ client: LibSqlAuthorityClientService,
1442
+ options: OhLibSqlStoreAuthorityOptionsV1 & Readonly<{ purgedAt?: string }> = {},
1443
+ ): Effect.Effect<OhSpacePurgeReceiptV1, LibSqlFailure> {
1444
+ return Effect.gen(function* () {
1445
+
1446
+ const profile = (yield* libSqlValue(() => parseOhStoreProfileV1(options.profile ?? OH_WORKING_STORE_PROFILE_V1)));
1447
+ if (profile === null) return yield* Effect.fail(libSqlFailure(new TypeError("Invalid libSQL store profile.")));
1448
+ if (profile.profileKind !== "working" || !profile.capabilities.wholeSpacePurge) {
1449
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("Whole-space purge requires a bound working profile.")));
1450
+ }
1451
+ const spaceId = options.spaceId ?? "default";
1452
+ const binding = (yield* libSqlValue(() => createOhStoreBindingV1({
1453
+ profile,
1454
+ realmId: options.realmId ?? `realm:${spaceId}`, spaceId, v: 1
1455
+ })));
1456
+ const purgedAt = options.purgedAt ?? (yield* client.currentInstant);
1457
+ yield* verifyAuthoritySchema(client);
1458
+
1459
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1460
+ const proof = yield* client.batch([
1461
+ { sql: BINDING_ROW_SELECT, args: [binding.spaceId] },
1462
+ { sql: SPACE_PURGE_PROOF_SELECT, args: [binding.spaceId] },
1463
+ { sql: PURGE_ROW_SELECT, args: [binding.spaceId] },
1464
+ ], "read");
1465
+ if (proof.length !== 3) {
1466
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete purge proof.")));
1467
+ }
1468
+ const [bindingResult, spaceResult, purgeResult] = proof as
1469
+ [OhLibSqlResultV1, OhLibSqlResultV1, OhLibSqlResultV1];
1470
+ const existingPurge = purgeResult.rows[0];
1471
+ if (existingPurge !== undefined) {
1472
+ const receipt = (yield* libSqlValue(() => parsePurgeReceiptRow(existingPurge, binding.spaceId, binding.bindingSha256)));
1473
+ yield* assertRemotePurgeComplete(client, binding, receipt);
1474
+ return receipt;
1475
+ }
1476
+ const bindingRow = bindingResult.rows[0];
1477
+ const spaceRow = spaceResult.rows[0];
1478
+ if ((bindingRow === undefined) !== (spaceRow === undefined)) {
1479
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority has only half of its space binding.")));
1480
+ }
1481
+ if (bindingRow !== undefined && spaceRow !== undefined) {
1482
+ const persisted = (yield* libSqlValue(() => parseBindingRow(bindingRow, binding.spaceId)));
1483
+ if ((yield* libSqlValue(() => canonicalJson(persisted))) !== (yield* libSqlValue(() => canonicalJson(binding)))) {
1484
+ return yield* Effect.fail(libSqlFailure(new OhProfileError("The remote space is bound to a different realm or profile.")));
1485
+ }
1486
+ if (rowValue(spaceRow, "contract_id", 5) !== OH_CONTRACT_MANIFEST_V1.contractId) {
1487
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The existing remote space uses a different Oh contract.")));
1488
+ }
1489
+ (yield* libSqlValue(() => parseHeadRow(spaceRow)));
1490
+ const authority = new LibSqlStoreProgram(client, binding);
1491
+ return yield* authority.purgeWorkingSpace(purgedAt);
1492
+ }
1493
+
1494
+ const receipt = (yield* libSqlValue(() => createOhSpacePurgeReceiptV1({
1495
+ binding,
1496
+ priorHead: emptyOhHeadV1(),
1497
+ purgedAt,
1498
+ })));
1499
+ const receiptJson = (yield* libSqlValue(() => canonicalJson(receipt)));
1500
+ const write = yield* Effect.either(client.batch([
1501
+ {
1502
+ sql: `INSERT INTO oh_authority_purges(space_id, binding_sha256,
1503
+ prior_operation_sha256, prior_sequence, purged_at, receipt_sha256, receipt_json)
1504
+ SELECT ?, ?, NULL, 0, ?, ?, ?
1505
+ WHERE NOT EXISTS (SELECT 1 FROM oh_authority_spaces WHERE space_id = ?)
1506
+ AND NOT EXISTS (SELECT 1 FROM oh_authority_bindings WHERE space_id = ?)
1507
+ ON CONFLICT(space_id) DO NOTHING`,
1508
+ args: [binding.spaceId, binding.bindingSha256, receipt.purgedAt,
1509
+ receipt.receiptSha256, receiptJson, binding.spaceId, binding.spaceId]
1510
+ },
1511
+ {
1512
+ sql: `INSERT INTO oh_authority_commit_guards(value)
1513
+ SELECT 'invalid' WHERE EXISTS (SELECT 1 FROM oh_authority_spaces WHERE space_id = ?)
1514
+ OR EXISTS (SELECT 1 FROM oh_authority_bindings WHERE space_id = ?)
1515
+ OR EXISTS (SELECT 1 FROM oh_authority_operations WHERE space_id = ?)
1516
+ OR EXISTS (SELECT 1 FROM oh_authority_operation_records WHERE space_id = ?)
1517
+ OR EXISTS (SELECT 1 FROM oh_authority_records WHERE space_id = ?)
1518
+ OR EXISTS (SELECT 1 FROM oh_authority_dependencies WHERE space_id = ?)
1519
+ OR EXISTS (SELECT 1 FROM oh_authority_operation_records AS materialized
1520
+ LEFT JOIN oh_authority_operations AS operation
1521
+ ON operation.operation_sha256 = materialized.operation_sha256
1522
+ WHERE operation.operation_sha256 IS NULL
1523
+ OR operation.space_id <> materialized.space_id)
1524
+ OR NOT EXISTS (SELECT 1 FROM oh_authority_purges
1525
+ WHERE space_id = ? AND receipt_sha256 = ?)`,
1526
+ args: [binding.spaceId, binding.spaceId, binding.spaceId, binding.spaceId,
1527
+ binding.spaceId, binding.spaceId, binding.spaceId, receipt.receiptSha256]
1528
+ },
1529
+ ], "write"));
1530
+ if (Either.isLeft(write)) {
1531
+ const error = write.left;
1532
+ const recovery = yield* client.batch([
1533
+ { sql: BINDING_ROW_SELECT, args: [binding.spaceId] },
1534
+ { sql: SPACE_PURGE_PROOF_SELECT, args: [binding.spaceId] },
1535
+ { sql: PURGE_ROW_SELECT, args: [binding.spaceId] },
1536
+ ], "read");
1537
+ if (recovery.length !== 3) {
1538
+ return yield* Effect.fail(libSqlFailure(new OhIntegrityError("The remote authority returned an incomplete purge recovery proof.")));
1539
+ }
1540
+ const raced = recovery[2]?.rows[0];
1541
+ if (raced !== undefined) {
1542
+ const persisted = (yield* libSqlValue(() => parsePurgeReceiptRow(raced, binding.spaceId, binding.bindingSha256)));
1543
+ yield* assertRemotePurgeComplete(client, binding, persisted);
1544
+ return persisted;
1545
+ }
1546
+ if (recovery[0]?.rows[0] !== undefined || recovery[1]?.rows[0] !== undefined) continue;
1547
+ return yield* Effect.fail(error);
1548
+ }
1549
+ const persisted = yield* queryOne(client, { sql: PURGE_ROW_SELECT, args: [binding.spaceId] });
1550
+ if (persisted === null) continue;
1551
+ const exact = (yield* libSqlValue(() => parsePurgeReceiptRow(persisted, binding.spaceId, binding.bindingSha256)));
1552
+ yield* assertRemotePurgeComplete(client, binding, exact);
1553
+ return exact;
1554
+ }
1555
+ return yield* Effect.fail(libSqlFailure(new OhConflictError("The remote working space changed repeatedly while fencing purge.")));
1556
+ });
1557
+ }
1558
+ /** Admission and drain are one local authority lifetime. SQL still arbitrates
1559
+ * concurrent commits; this gate does not serialize independent operations. */
1560
+ export const makeLibSqlOwner = Effect.gen(function* () {
1561
+ const active = yield* Ref.make(0);
1562
+ const closing = yield* Ref.make(false);
1563
+ const drained = yield* Deferred.make<void>();
1564
+ const admit = Effect.gen(function* () {
1565
+ if (yield* Ref.get(closing)) return false;
1566
+ yield* Ref.update(active, count => count + 1);
1567
+ return true;
1568
+ });
1569
+ const release = Effect.gen(function* () {
1570
+ const remaining = yield* Ref.updateAndGet(active, count => count - 1);
1571
+ if (remaining === 0 && (yield* Ref.get(closing))) yield* Deferred.succeed(drained, undefined);
1572
+ });
1573
+ const beginClose = Effect.gen(function* () {
1574
+ yield* Ref.set(closing, true);
1575
+ if ((yield* Ref.get(active)) === 0) yield* Deferred.succeed(drained, undefined);
1576
+ });
1577
+ return {
1578
+ admit, beginClose,
1579
+ drained: Deferred.await(drained),
1580
+ complete: <A>(operation: Effect.Effect<A, LibSqlFailure>): Effect.Effect<A, LibSqlFailure> =>
1581
+ Effect.uninterruptible(operation.pipe(Effect.ensuring(release))),
1582
+ };
1583
+ });