@hraness/oh 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -127
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +811 -97
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.js +676 -61
- package/dist/libsql-semantic-v2.d.ts +160 -0
- package/dist/libsql-semantic-v2.d.ts.map +1 -0
- package/dist/libsql.d.ts.map +1 -1
- package/dist/libsql.js +162 -35
- package/dist/memory-page.js +2 -2
- package/dist/memory.d.ts +87 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +1106 -148
- package/dist/operation.d.ts +3 -1
- package/dist/operation.d.ts.map +1 -1
- package/dist/projection-public.js +2 -2
- package/dist/projection-suss.js +2 -2
- package/dist/sdk.js +780 -88
- package/dist/semantic-cloud.d.ts +1 -0
- package/dist/semantic-cloud.d.ts.map +1 -1
- package/dist/semantic-cloud.js +1545 -2
- package/dist/semantic.js +2 -2
- package/dist/sqlite/index.js +1251 -306
- package/dist/sqlite/port.d.ts +31 -3
- package/dist/sqlite/port.d.ts.map +1 -1
- package/dist/sqlite/store.d.ts +18 -2
- package/dist/sqlite/store.d.ts.map +1 -1
- package/dist/store.d.ts +3 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +154 -32
- package/dist/sync.d.ts +7 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +668 -35
- package/package.json +5 -1
- package/skills/oh/SKILL.md +42 -16
- package/spec/README.md +10 -3
- package/spec/manifest.json +5 -1
- package/spec/v1/libsql-semantic-cache-schema-v1.sql +114 -0
- package/spec/v1/libsql-semantic-digest-fixture-v1.json +13 -0
- package/spec/v1/memory.md +134 -16
- package/spec/v1/storage.md +8 -5
- package/spec/v1/store.md +20 -0
- package/spec/v1/sync.md +77 -8
- package/spec/v2/libsql-semantic-cache-schema-v2.sql +175 -0
- package/spec/v2/libsql-semantic-digest-fixture-v2.json +14 -0
- package/spec/v2/manifest.json +11 -0
- package/spec/v2/semantic-cloud.md +106 -0
- package/src/cli.test.ts +53 -1
- package/src/cli.ts +34 -8
- package/src/errors.test.ts +87 -0
- package/src/errors.ts +185 -0
- package/src/graph.ts +2 -2
- package/src/libsql-semantic-v2.test.ts +1114 -0
- package/src/libsql-semantic-v2.ts +1891 -0
- package/src/libsql-semantic.test.ts +50 -1
- package/src/libsql.test.ts +36 -0
- package/src/libsql.ts +26 -5
- package/src/memory.test.ts +1488 -18
- package/src/memory.ts +1199 -122
- package/src/operation.ts +13 -3
- package/src/semantic-cloud.ts +1 -0
- package/src/sqlite/port.test.ts +209 -0
- package/src/sqlite/port.ts +118 -4
- package/src/sqlite/store.test.ts +106 -1
- package/src/sqlite/store.ts +168 -30
- package/src/store.test.ts +12 -0
- package/src/store.ts +30 -20
- package/src/sync.test.ts +570 -2
- package/src/sync.ts +586 -36
|
@@ -147,7 +147,11 @@ function document(
|
|
|
147
147
|
async function bootstrapped(): Promise<SqliteCompatibleLibSqlClient> {
|
|
148
148
|
const client = new SqliteCompatibleLibSqlClient();
|
|
149
149
|
expect(await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 }))
|
|
150
|
-
.
|
|
150
|
+
.toEqual({
|
|
151
|
+
schemaSha256: "d9903735e900e08a8c49c9ad36ea8f277b1689117ef88b9a2c6b906456dfbace" as Sha256Hex,
|
|
152
|
+
schemaVersion: 1,
|
|
153
|
+
v: 1,
|
|
154
|
+
});
|
|
151
155
|
return client;
|
|
152
156
|
}
|
|
153
157
|
|
|
@@ -167,6 +171,51 @@ function authority(
|
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
describe("libSQL derived semantic cache", () => {
|
|
174
|
+
test("reproduces the fixed released V1 digest fixture", async () => {
|
|
175
|
+
const fixture = await Bun.file(new URL(
|
|
176
|
+
"../spec/v1/libsql-semantic-digest-fixture-v1.json",
|
|
177
|
+
import.meta.url,
|
|
178
|
+
)).json() as Readonly<{
|
|
179
|
+
authorityId: string;
|
|
180
|
+
authoritySha256: Sha256Hex;
|
|
181
|
+
content: string;
|
|
182
|
+
generation: number;
|
|
183
|
+
generationSha256: Sha256Hex;
|
|
184
|
+
inputSha256: Sha256Hex;
|
|
185
|
+
membershipSha256: Sha256Hex;
|
|
186
|
+
recordKey: string;
|
|
187
|
+
recordSha256: Sha256Hex;
|
|
188
|
+
title: string;
|
|
189
|
+
v: 1;
|
|
190
|
+
}>;
|
|
191
|
+
const client = await bootstrapped();
|
|
192
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
193
|
+
const calls: string[][] = [];
|
|
194
|
+
const staged = await cache.stage({
|
|
195
|
+
authorityId: fixture.authorityId,
|
|
196
|
+
authoritySha256: fixture.authoritySha256,
|
|
197
|
+
createdAt: instant1,
|
|
198
|
+
documents: [{
|
|
199
|
+
content: fixture.content,
|
|
200
|
+
key: fixture.recordKey,
|
|
201
|
+
recordSha256: fixture.recordSha256,
|
|
202
|
+
title: fixture.title,
|
|
203
|
+
v: fixture.v,
|
|
204
|
+
}],
|
|
205
|
+
embeddingClient: embeddingClient(calls),
|
|
206
|
+
generation: fixture.generation,
|
|
207
|
+
});
|
|
208
|
+
expect(staged).toMatchObject({
|
|
209
|
+
generationSha256: fixture.generationSha256,
|
|
210
|
+
membershipSha256: fixture.membershipSha256,
|
|
211
|
+
v: 1,
|
|
212
|
+
});
|
|
213
|
+
expect(calls).toHaveLength(1);
|
|
214
|
+
expect(sha256Hex(calls[0]?.[0] ?? "")).toBe(fixture.inputSha256);
|
|
215
|
+
await cache.close();
|
|
216
|
+
client.close();
|
|
217
|
+
});
|
|
218
|
+
|
|
170
219
|
test("requires explicit bootstrap and refuses a partial or drifted schema", async () => {
|
|
171
220
|
const empty = new SqliteCompatibleLibSqlClient();
|
|
172
221
|
await expect(openOhLibSqlSemanticCacheV1(empty)).rejects.toMatchObject({
|
package/src/libsql.test.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
OH_WORKING_STORE_PROFILE_V1,
|
|
19
19
|
OhConflictError,
|
|
20
20
|
OhIntegrityError,
|
|
21
|
+
OhOperationSizeError,
|
|
21
22
|
OhProfileError,
|
|
22
23
|
OhPurgedSpaceError,
|
|
23
24
|
} from "./store";
|
|
@@ -341,6 +342,41 @@ describe("direct libSQL Oh authority", () => {
|
|
|
341
342
|
client.close();
|
|
342
343
|
});
|
|
343
344
|
|
|
345
|
+
test("honors the host-declared operation byte bound before replay or persistence", async () => {
|
|
346
|
+
const client = await bootstrappedClient();
|
|
347
|
+
const authority = await createOhLibSqlStoreAuthorityV1(client, {
|
|
348
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:operation-bound", spaceId: "operation-bound",
|
|
349
|
+
});
|
|
350
|
+
const empty = await authority.store.head();
|
|
351
|
+
const firstChanges = [{ kind: "put" as const,
|
|
352
|
+
record: entity("entity:bounded", "Bounded"), v: 1 as const }];
|
|
353
|
+
const first = await authority.store.commit({ actorId: "agent.bound", changes: firstChanges,
|
|
354
|
+
expectedHead: empty, operationId: "op_bounded_first" });
|
|
355
|
+
|
|
356
|
+
const replayError: unknown = await authority.store.commit({ actorId: "agent.bound", changes: firstChanges,
|
|
357
|
+
expectedHead: empty, maximumOperationBytes: 1,
|
|
358
|
+
operationId: "op_bounded_first" }).catch((error: unknown) => error);
|
|
359
|
+
const commitError: unknown = await authority.store.commit({ actorId: "agent.bound", changes: [{ kind: "put",
|
|
360
|
+
record: entity("entity:rejected", "Rejected"), v: 1 }], expectedHead: await authority.store.head(),
|
|
361
|
+
maximumOperationBytes: 1, operationId: "op_bounded_rejected" }).catch((error: unknown) => error);
|
|
362
|
+
for (const error of [replayError, commitError]) {
|
|
363
|
+
expect(error).toBeInstanceOf(OhOperationSizeError);
|
|
364
|
+
expect(error).toBeInstanceOf(RangeError);
|
|
365
|
+
expect(error).toMatchObject({ maximumOperationBytes: 1,
|
|
366
|
+
operationBytes: expect.any(Number) });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
expect(await authority.store.head()).toMatchObject({
|
|
370
|
+
operationSha256: first.operationSha256,
|
|
371
|
+
sequence: 1,
|
|
372
|
+
});
|
|
373
|
+
expect(client.database.query<{ count: number }, [string]>(`SELECT count(*) AS count
|
|
374
|
+
FROM oh_authority_operations WHERE space_id = ?`).get("operation-bound")?.count).toBe(1);
|
|
375
|
+
expect((await authority.store.snapshot()).records.map(({ key }) => key)).toEqual(["entity:bounded"]);
|
|
376
|
+
await authority.store.close();
|
|
377
|
+
client.close();
|
|
378
|
+
});
|
|
379
|
+
|
|
344
380
|
test("uses compare-and-swap guards to leave no partial remote mutation", async () => {
|
|
345
381
|
const client = await bootstrappedClient();
|
|
346
382
|
const first = await createOhLibSqlStoreAuthorityV1(client, {
|
package/src/libsql.ts
CHANGED
|
@@ -11,7 +11,11 @@ import { OH_CONTRACT_MANIFEST_V1 } from "./contract";
|
|
|
11
11
|
import { canonicalKnowledgeGraphChangesV1, knowledgeGraphRecordRefV1, OH_GRAPH_LIMITS_V1,
|
|
12
12
|
parseKnowledgeGraphRecordV1,
|
|
13
13
|
type KnowledgeGraphRecordV1 } from "./graph";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
OH_OPERATION_MAX_BYTES_V1,
|
|
16
|
+
parseOhOperationV1,
|
|
17
|
+
type OhOperationV1,
|
|
18
|
+
} from "./operation";
|
|
15
19
|
import {
|
|
16
20
|
createOhDependencyClosureV1,
|
|
17
21
|
createOhSpacePurgeReceiptV1,
|
|
@@ -21,6 +25,7 @@ import {
|
|
|
21
25
|
OH_WORKING_STORE_PROFILE_V1,
|
|
22
26
|
OhConflictError,
|
|
23
27
|
OhIntegrityError,
|
|
28
|
+
OhOperationSizeError,
|
|
24
29
|
OhProfileError,
|
|
25
30
|
OhPurgedSpaceError,
|
|
26
31
|
parseOhHeadRefV1,
|
|
@@ -1261,8 +1266,14 @@ class OhLibSqlStoreV1 implements OhStoreV1 {
|
|
|
1261
1266
|
this.#assertOpen();
|
|
1262
1267
|
const actorId = safeCode(input.actorId);
|
|
1263
1268
|
const operationId = safeCode(input.operationId);
|
|
1269
|
+
const maximumOperationBytes = input.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
|
|
1264
1270
|
const changes = canonicalKnowledgeGraphChangesV1(input.changes);
|
|
1265
|
-
if (actorId === null || operationId === null || changes.length === 0
|
|
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
|
+
}
|
|
1266
1277
|
if (changes.length > OH_LIBSQL_STORE_LIMITS_V1.changesPerCommit) {
|
|
1267
1278
|
throw new RangeError("A direct libSQL commit exceeds its change-count bound.");
|
|
1268
1279
|
}
|
|
@@ -1277,6 +1288,10 @@ class OhLibSqlStoreV1 implements OhStoreV1 {
|
|
|
1277
1288
|
if (duplicate.actorId !== actorId || canonicalJson(duplicate.changes) !== canonicalJson(changes)) {
|
|
1278
1289
|
throw new OhConflictError("The operation ID is already bound to different content.");
|
|
1279
1290
|
}
|
|
1291
|
+
const operationBytes = utf8ByteLength(canonicalJson(duplicate));
|
|
1292
|
+
if (operationBytes > maximumOperationBytes) {
|
|
1293
|
+
throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
|
|
1294
|
+
}
|
|
1280
1295
|
return duplicate;
|
|
1281
1296
|
}
|
|
1282
1297
|
if (!Number.isSafeInteger(input.expectedHead.generation) || input.expectedHead.generation < 0
|
|
@@ -1286,9 +1301,15 @@ class OhLibSqlStoreV1 implements OhStoreV1 {
|
|
|
1286
1301
|
}
|
|
1287
1302
|
const snapshot = await this.#currentMaterializedSnapshot(current, OH_GRAPH_LIMITS_V1.recordsPerSnapshot);
|
|
1288
1303
|
const transition = transitionOhSnapshotV1({ actorId, changes,
|
|
1289
|
-
instant: input.instant ?? canonicalNow(),
|
|
1304
|
+
instant: input.instant ?? canonicalNow(), maximumOperationBytes,
|
|
1305
|
+
operationId, snapshot, spaceId: this.binding.spaceId });
|
|
1290
1306
|
const operation = transition.operation;
|
|
1291
|
-
|
|
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) {
|
|
1292
1313
|
throw new RangeError("A direct libSQL operation exceeds its canonical byte bound.");
|
|
1293
1314
|
}
|
|
1294
1315
|
const existsOperation = "EXISTS (SELECT 1 FROM oh_authority_operations WHERE operation_sha256 = ?)";
|
|
@@ -1301,7 +1322,7 @@ class OhLibSqlStoreV1 implements OhStoreV1 {
|
|
|
1301
1322
|
WHERE space_id = ? AND generation = ? AND head_operation_sha256 IS ?)`,
|
|
1302
1323
|
args: [operation.operationSha256, this.binding.spaceId, operation.sequence, operation.operationId,
|
|
1303
1324
|
operation.parentOperationSha256, operation.graphRevisionSha256, operation.recordsSha256,
|
|
1304
|
-
|
|
1325
|
+
operationJson, operation.instant, this.binding.spaceId, current.generation,
|
|
1305
1326
|
current.operationSha256],
|
|
1306
1327
|
}];
|
|
1307
1328
|
for (const [ordinal, change] of operation.changes.entries()) {
|