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