@hraness/oh 0.2.6 → 0.3.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 +116 -12
- package/dist/canonical.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +91 -19
- package/dist/cloudflare-embedding.d.ts +104 -0
- package/dist/cloudflare-embedding.d.ts.map +1 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.js +90 -18
- package/dist/libsql-semantic.d.ts +111 -0
- package/dist/libsql-semantic.d.ts.map +1 -0
- package/dist/libsql.js +105 -18
- package/dist/memory-page.d.ts +2 -0
- package/dist/memory-page.d.ts.map +1 -0
- package/dist/memory-page.js +725 -0
- package/dist/memory-pages.d.ts +76 -0
- package/dist/memory-pages.d.ts.map +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +478 -18
- package/dist/projection-public.js +105 -18
- package/dist/projection-suss.js +105 -18
- package/dist/sdk.js +90 -18
- package/dist/semantic-cloud.d.ts +3 -0
- package/dist/semantic-cloud.d.ts.map +1 -0
- package/dist/semantic-cloud.js +1843 -0
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +104 -21
- package/dist/sqlite/index.js +90 -18
- package/dist/store.js +105 -18
- package/dist/sync.js +90 -18
- package/package.json +10 -2
- package/skills/oh/SKILL.md +28 -2
- package/spec/README.md +11 -3
- package/spec/manifest.json +9 -1
- package/spec/v1/cloudflare-embedding-profile.json +13 -0
- package/spec/v1/cloudflare-embedding-renderer.json +8 -0
- package/spec/v1/memory-page.md +153 -0
- package/spec/v1/memory-page.schema.json +154 -0
- package/spec/v1/memory.md +18 -0
- package/spec/v1/migration.md +13 -0
- package/spec/v1/semantic-cloud.md +87 -0
- package/src/canonical.ts +28 -13
- package/src/cli.ts +1 -1
- package/src/cloudflare-embedding.test.ts +306 -0
- package/src/cloudflare-embedding.ts +385 -0
- package/src/contracts.test.ts +20 -0
- package/src/graph.ts +63 -6
- package/src/libsql-semantic.test.ts +478 -0
- package/src/libsql-semantic.ts +1117 -0
- package/src/memory-page.ts +1 -0
- package/src/memory-pages.test.ts +277 -0
- package/src/memory-pages.ts +440 -0
- package/src/memory.ts +2 -0
- package/src/semantic-cloud.ts +2 -0
- package/src/semantic.ts +14 -3
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Database, type SQLQueryBindings } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
import { sha256Hex, type Sha256Hex } from "./canonical";
|
|
5
|
+
import {
|
|
6
|
+
OH_CLOUDFLARE_EMBEDDING_PROFILE_V1,
|
|
7
|
+
OhCloudflareEmbeddingClientV1,
|
|
8
|
+
} from "./cloudflare-embedding";
|
|
9
|
+
import type {
|
|
10
|
+
OhLibSqlClientV1,
|
|
11
|
+
OhLibSqlResultV1,
|
|
12
|
+
OhLibSqlStatementV1,
|
|
13
|
+
} from "./libsql";
|
|
14
|
+
import {
|
|
15
|
+
bootstrapOhLibSqlSemanticCacheV1,
|
|
16
|
+
OhLibSqlSemanticError,
|
|
17
|
+
openOhLibSqlSemanticCacheV1,
|
|
18
|
+
type OhSemanticAuthorityRefV1,
|
|
19
|
+
type OhSemanticDocumentV1,
|
|
20
|
+
} from "./libsql-semantic";
|
|
21
|
+
|
|
22
|
+
class SqliteCompatibleLibSqlClient implements OhLibSqlClientV1 {
|
|
23
|
+
readonly database = new Database(":memory:", { strict: true });
|
|
24
|
+
|
|
25
|
+
#execute(statement: OhLibSqlStatementV1 | string): OhLibSqlResultV1 {
|
|
26
|
+
const sql = typeof statement === "string" ? statement : statement.sql;
|
|
27
|
+
const args = typeof statement === "string" ? [] : statement.args ?? [];
|
|
28
|
+
const bindings: SQLQueryBindings[] = args.map((value) => value instanceof Date
|
|
29
|
+
? value.toISOString() : value instanceof ArrayBuffer ? new Uint8Array(value) : value);
|
|
30
|
+
if (/^\s*(?:SELECT|PRAGMA)\b/iu.test(sql)) {
|
|
31
|
+
return {
|
|
32
|
+
rows: this.database.query<Record<string, unknown>, SQLQueryBindings[]>(sql).all(...bindings),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const result = this.database.query<never, SQLQueryBindings[]>(sql).run(...bindings);
|
|
36
|
+
return { rows: [], rowsAffected: result.changes };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async execute(statement: OhLibSqlStatementV1 | string): Promise<OhLibSqlResultV1> {
|
|
40
|
+
return this.#execute(statement);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async batch(
|
|
44
|
+
statements: readonly OhLibSqlStatementV1[],
|
|
45
|
+
_mode?: "deferred" | "read" | "write",
|
|
46
|
+
): Promise<readonly OhLibSqlResultV1[]> {
|
|
47
|
+
return this.database.transaction((items: readonly OhLibSqlStatementV1[]) =>
|
|
48
|
+
items.map((statement) => this.#execute(statement)))(statements);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
close(): void { this.database.close(); }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class InterleavingLibSqlClient extends SqliteCompatibleLibSqlClient {
|
|
55
|
+
beforeVectorWrite: (() => Promise<void>) | null = null;
|
|
56
|
+
afterSecondVectorRead: (() => Promise<void>) | null = null;
|
|
57
|
+
#vectorReads = 0;
|
|
58
|
+
|
|
59
|
+
override async execute(statement: OhLibSqlStatementV1 | string): Promise<OhLibSqlResultV1> {
|
|
60
|
+
const result = await super.execute(statement);
|
|
61
|
+
const sql = typeof statement === "string" ? statement : statement.sql;
|
|
62
|
+
if (/SELECT\s+input_sha256,\s*vector_sha256,\s*vector\s+FROM\s+oh_semantic_vectors/iu
|
|
63
|
+
.test(sql)) {
|
|
64
|
+
this.#vectorReads += 1;
|
|
65
|
+
if (this.#vectorReads === 2 && this.afterSecondVectorRead !== null) {
|
|
66
|
+
const hook = this.afterSecondVectorRead;
|
|
67
|
+
this.afterSecondVectorRead = null;
|
|
68
|
+
await hook();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override async batch(
|
|
75
|
+
statements: readonly OhLibSqlStatementV1[],
|
|
76
|
+
mode?: "deferred" | "read" | "write",
|
|
77
|
+
): Promise<readonly OhLibSqlResultV1[]> {
|
|
78
|
+
if (this.beforeVectorWrite !== null
|
|
79
|
+
&& statements.some(({ sql }) => /INSERT\s+INTO\s+oh_semantic_vectors/iu.test(sql))) {
|
|
80
|
+
const hook = this.beforeVectorWrite;
|
|
81
|
+
this.beforeVectorWrite = null;
|
|
82
|
+
await hook();
|
|
83
|
+
}
|
|
84
|
+
return await super.batch(statements, mode);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const instant1 = "2026-08-31T12:00:00.000Z";
|
|
89
|
+
const instant2 = "2026-08-31T12:01:00.000Z";
|
|
90
|
+
const instant3 = "2026-08-31T12:02:00.000Z";
|
|
91
|
+
const digest = (value: string): Sha256Hex => sha256Hex(value);
|
|
92
|
+
|
|
93
|
+
function unitVector(index: number): number[] {
|
|
94
|
+
return Array.from(
|
|
95
|
+
{ length: OH_CLOUDFLARE_EMBEDDING_PROFILE_V1.dimensions },
|
|
96
|
+
(_, ordinal) => ordinal === index ? 1 : 0,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function embeddingClient(calls: string[][]): OhCloudflareEmbeddingClientV1 {
|
|
101
|
+
return new OhCloudflareEmbeddingClientV1({
|
|
102
|
+
accountId: "0123456789abcdef0123456789abcdef",
|
|
103
|
+
apiToken: "test-token-with-no-provider-authority",
|
|
104
|
+
fetch: async (_input, init) => {
|
|
105
|
+
const body = JSON.parse(String(init?.body)) as { text: string[] };
|
|
106
|
+
calls.push(body.text);
|
|
107
|
+
const vectors = body.text.map((text) => unitVector(
|
|
108
|
+
text.includes("needle-beta") ? 1 : text.includes("needle-gamma") ? 2 : 0,
|
|
109
|
+
));
|
|
110
|
+
return Response.json({
|
|
111
|
+
result: { data: vectors, shape: [vectors.length, 768] },
|
|
112
|
+
success: true,
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function document(
|
|
119
|
+
key: string,
|
|
120
|
+
marker: "needle-alpha" | "needle-beta" | "needle-gamma",
|
|
121
|
+
): OhSemanticDocumentV1 {
|
|
122
|
+
return {
|
|
123
|
+
content: `private body ${marker}`,
|
|
124
|
+
key,
|
|
125
|
+
recordSha256: digest(`record:${key}:${marker}`),
|
|
126
|
+
title: `private title ${marker}`,
|
|
127
|
+
v: 1,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function bootstrapped(): Promise<SqliteCompatibleLibSqlClient> {
|
|
132
|
+
const client = new SqliteCompatibleLibSqlClient();
|
|
133
|
+
expect(await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 }))
|
|
134
|
+
.toMatchObject({ schemaVersion: 1, v: 1 });
|
|
135
|
+
return client;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function authority(
|
|
139
|
+
authorityId: string,
|
|
140
|
+
generation: number,
|
|
141
|
+
authoritySha256: Sha256Hex,
|
|
142
|
+
documents: readonly OhSemanticDocumentV1[],
|
|
143
|
+
): OhSemanticAuthorityRefV1 {
|
|
144
|
+
return {
|
|
145
|
+
authorityId,
|
|
146
|
+
authoritySha256,
|
|
147
|
+
generation,
|
|
148
|
+
records: documents.map(({ key, recordSha256 }) => ({ key, recordSha256 })),
|
|
149
|
+
v: 1,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
describe("libSQL derived semantic cache", () => {
|
|
154
|
+
test("requires explicit bootstrap and refuses a partial or drifted schema", async () => {
|
|
155
|
+
const empty = new SqliteCompatibleLibSqlClient();
|
|
156
|
+
await expect(openOhLibSqlSemanticCacheV1(empty)).rejects.toMatchObject({
|
|
157
|
+
code: "schema-unavailable",
|
|
158
|
+
});
|
|
159
|
+
expect(empty.database.query<{ count: number }, []>(`SELECT count(*) AS count
|
|
160
|
+
FROM sqlite_schema WHERE name GLOB 'oh_semantic_*'`).get()?.count).toBe(0);
|
|
161
|
+
empty.close();
|
|
162
|
+
|
|
163
|
+
const coTenant = new SqliteCompatibleLibSqlClient();
|
|
164
|
+
coTenant.database.exec("CREATE TABLE ohXsemanticYforeign(value TEXT) STRICT");
|
|
165
|
+
await expect(bootstrapOhLibSqlSemanticCacheV1(coTenant, { appliedAt: instant1 }))
|
|
166
|
+
.resolves.toMatchObject({ schemaVersion: 1, v: 1 });
|
|
167
|
+
await expect(openOhLibSqlSemanticCacheV1(coTenant)).resolves.toBeDefined();
|
|
168
|
+
coTenant.close();
|
|
169
|
+
|
|
170
|
+
const partial = new SqliteCompatibleLibSqlClient();
|
|
171
|
+
partial.database.exec("CREATE TABLE oh_semantic_foreign(value TEXT) STRICT");
|
|
172
|
+
await expect(bootstrapOhLibSqlSemanticCacheV1(partial, { appliedAt: instant1 }))
|
|
173
|
+
.rejects.toMatchObject({ code: "integrity" });
|
|
174
|
+
partial.close();
|
|
175
|
+
|
|
176
|
+
const drifted = await bootstrapped();
|
|
177
|
+
drifted.database.exec("DROP INDEX oh_semantic_memberships_input");
|
|
178
|
+
await expect(openOhLibSqlSemanticCacheV1(drifted)).rejects.toMatchObject({ code: "integrity" });
|
|
179
|
+
drifted.close();
|
|
180
|
+
|
|
181
|
+
const ownerDrift = await bootstrapped();
|
|
182
|
+
ownerDrift.database.exec(`CREATE TRIGGER foreign_named_semantic_trigger
|
|
183
|
+
AFTER INSERT ON oh_semantic_vectors BEGIN SELECT 1; END`);
|
|
184
|
+
await expect(openOhLibSqlSemanticCacheV1(ownerDrift))
|
|
185
|
+
.rejects.toMatchObject({ code: "integrity" });
|
|
186
|
+
ownerDrift.close();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("stages immutable generations, reuses vectors, publishes by CAS, and searches exactly", async () => {
|
|
190
|
+
const client = await bootstrapped();
|
|
191
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
192
|
+
const calls: string[][] = [];
|
|
193
|
+
const embedder = embeddingClient(calls);
|
|
194
|
+
const authorityId = "agent:session-1:epoch-1";
|
|
195
|
+
const firstDocuments = [
|
|
196
|
+
document("memory:one", "needle-alpha"),
|
|
197
|
+
document("memory:two", "needle-beta"),
|
|
198
|
+
] as const;
|
|
199
|
+
const firstAuthoritySha256 = digest("authority:first");
|
|
200
|
+
const first = await cache.stage({
|
|
201
|
+
authorityId,
|
|
202
|
+
authoritySha256: firstAuthoritySha256,
|
|
203
|
+
createdAt: instant1,
|
|
204
|
+
documents: firstDocuments,
|
|
205
|
+
embeddingClient: embedder,
|
|
206
|
+
generation: 1,
|
|
207
|
+
});
|
|
208
|
+
expect(first).toMatchObject({ chunks: 2, documents: 2, embedded: 2, reused: 0 });
|
|
209
|
+
expect(calls).toHaveLength(1);
|
|
210
|
+
expect(await cache.stage({
|
|
211
|
+
authorityId,
|
|
212
|
+
authoritySha256: firstAuthoritySha256,
|
|
213
|
+
createdAt: instant2,
|
|
214
|
+
documents: firstDocuments,
|
|
215
|
+
embeddingClient: embedder,
|
|
216
|
+
generation: 1,
|
|
217
|
+
})).toMatchObject({ embedded: 0, generationSha256: first.generationSha256, reused: 2 });
|
|
218
|
+
expect(calls).toHaveLength(1);
|
|
219
|
+
|
|
220
|
+
const storedVectors = client.database.query<{
|
|
221
|
+
bytes: number;
|
|
222
|
+
count: number;
|
|
223
|
+
}, []>("SELECT count(*) AS count, min(length(vector)) AS bytes FROM oh_semantic_vectors").get();
|
|
224
|
+
expect(storedVectors).toEqual({ bytes: 3_072, count: 2 });
|
|
225
|
+
const storedText = JSON.stringify(client.database.query<Record<string, unknown>, []>(`
|
|
226
|
+
SELECT authority_id, authority_sha256, profile_sha256, renderer_sha256,
|
|
227
|
+
membership_sha256, generation_sha256 FROM oh_semantic_generations`).all());
|
|
228
|
+
expect(storedText).not.toContain("private title");
|
|
229
|
+
expect(storedText).not.toContain("private body");
|
|
230
|
+
expect(storedText).not.toContain("needle-alpha");
|
|
231
|
+
|
|
232
|
+
expect(await cache.publish({
|
|
233
|
+
authorityId,
|
|
234
|
+
expectedPublishedGeneration: null,
|
|
235
|
+
generation: 1,
|
|
236
|
+
publishedAt: instant1,
|
|
237
|
+
})).toMatchObject({ published: true });
|
|
238
|
+
expect(await cache.publish({
|
|
239
|
+
authorityId,
|
|
240
|
+
expectedPublishedGeneration: null,
|
|
241
|
+
generation: 1,
|
|
242
|
+
publishedAt: instant2,
|
|
243
|
+
})).toMatchObject({ published: false });
|
|
244
|
+
expect(await cache.stage({
|
|
245
|
+
authorityId,
|
|
246
|
+
authoritySha256: firstAuthoritySha256,
|
|
247
|
+
createdAt: instant2,
|
|
248
|
+
documents: firstDocuments,
|
|
249
|
+
embeddingClient: embedder,
|
|
250
|
+
generation: 1,
|
|
251
|
+
})).toMatchObject({ embedded: 0, generationSha256: first.generationSha256, reused: 2 });
|
|
252
|
+
expect(calls).toHaveLength(1);
|
|
253
|
+
|
|
254
|
+
const hits = await cache.search({
|
|
255
|
+
authority: authority(authorityId, 1, firstAuthoritySha256, firstDocuments),
|
|
256
|
+
embeddingClient: embedder,
|
|
257
|
+
limit: 2,
|
|
258
|
+
query: "needle-alpha",
|
|
259
|
+
});
|
|
260
|
+
expect(hits.map(({ key, score }) => ({ key, score }))).toEqual([
|
|
261
|
+
{ key: "memory:one", score: 1 },
|
|
262
|
+
{ key: "memory:two", score: 0 },
|
|
263
|
+
]);
|
|
264
|
+
expect(await cache.search({
|
|
265
|
+
authority: authority(authorityId, 1, digest("stale"), firstDocuments),
|
|
266
|
+
embeddingClient: embedder,
|
|
267
|
+
query: "needle-alpha",
|
|
268
|
+
})).toEqual([]);
|
|
269
|
+
expect((await cache.search({
|
|
270
|
+
authority: authority(authorityId, 1, firstAuthoritySha256, [
|
|
271
|
+
{ ...firstDocuments[0], recordSha256: digest("changed") },
|
|
272
|
+
firstDocuments[1],
|
|
273
|
+
]),
|
|
274
|
+
embeddingClient: embedder,
|
|
275
|
+
query: "needle-alpha",
|
|
276
|
+
})).map(({ key, score }) => ({ key, score }))).toEqual([
|
|
277
|
+
{ key: "memory:two", score: 0 },
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
const secondDocuments = [
|
|
281
|
+
firstDocuments[0],
|
|
282
|
+
document("memory:three", "needle-gamma"),
|
|
283
|
+
] as const;
|
|
284
|
+
const secondAuthoritySha256 = digest("authority:second");
|
|
285
|
+
expect(await cache.stage({
|
|
286
|
+
authorityId,
|
|
287
|
+
authoritySha256: secondAuthoritySha256,
|
|
288
|
+
createdAt: instant2,
|
|
289
|
+
documents: secondDocuments,
|
|
290
|
+
embeddingClient: embedder,
|
|
291
|
+
generation: 2,
|
|
292
|
+
})).toMatchObject({ embedded: 1, reused: 1 });
|
|
293
|
+
await expect(cache.publish({
|
|
294
|
+
authorityId,
|
|
295
|
+
expectedPublishedGeneration: 0,
|
|
296
|
+
generation: 2,
|
|
297
|
+
publishedAt: instant2,
|
|
298
|
+
})).rejects.toMatchObject({ code: "conflict" });
|
|
299
|
+
expect(await cache.publish({
|
|
300
|
+
authorityId,
|
|
301
|
+
expectedPublishedGeneration: 1,
|
|
302
|
+
generation: 2,
|
|
303
|
+
publishedAt: instant2,
|
|
304
|
+
})).toMatchObject({ published: true });
|
|
305
|
+
expect(await cache.search({
|
|
306
|
+
authority: authority(authorityId, 1, firstAuthoritySha256, firstDocuments),
|
|
307
|
+
embeddingClient: embedder,
|
|
308
|
+
query: "needle-alpha",
|
|
309
|
+
})).toEqual([]);
|
|
310
|
+
|
|
311
|
+
expect(() => client.database.query(`INSERT INTO oh_semantic_memberships(
|
|
312
|
+
authority_id, generation, generation_sha256, record_key, record_sha256,
|
|
313
|
+
ordinal, input_sha256) VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
314
|
+
.run(authorityId, 2, digest("generation"), "memory:late", digest("late"), 0, digest("late")))
|
|
315
|
+
.toThrow("published");
|
|
316
|
+
await cache.close();
|
|
317
|
+
client.close();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("tombstones a purged authority, preserves shared vectors, and prevents reuse", async () => {
|
|
321
|
+
const client = await bootstrapped();
|
|
322
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
323
|
+
const calls: string[][] = [];
|
|
324
|
+
const embedder = embeddingClient(calls);
|
|
325
|
+
const shared = [document("memory:shared", "needle-alpha")] as const;
|
|
326
|
+
for (const authorityId of ["agent:session-a:epoch-1", "agent:session-b:epoch-1"] as const) {
|
|
327
|
+
await cache.stage({
|
|
328
|
+
authorityId,
|
|
329
|
+
authoritySha256: digest(authorityId),
|
|
330
|
+
createdAt: instant1,
|
|
331
|
+
documents: shared,
|
|
332
|
+
embeddingClient: embedder,
|
|
333
|
+
generation: 1,
|
|
334
|
+
});
|
|
335
|
+
await cache.publish({
|
|
336
|
+
authorityId,
|
|
337
|
+
expectedPublishedGeneration: null,
|
|
338
|
+
generation: 1,
|
|
339
|
+
publishedAt: instant1,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
expect(calls).toHaveLength(1);
|
|
343
|
+
expect((await cache.purgeAuthority({
|
|
344
|
+
authorityId: "agent:session-a:epoch-1",
|
|
345
|
+
purgedAt: instant2,
|
|
346
|
+
})).orphanVectors).toBe(0);
|
|
347
|
+
expect(client.database.query<{ count: number }, []>(
|
|
348
|
+
"SELECT count(*) AS count FROM oh_semantic_vectors",
|
|
349
|
+
).get()?.count).toBe(1);
|
|
350
|
+
await expect(cache.stage({
|
|
351
|
+
authorityId: "agent:session-a:epoch-1",
|
|
352
|
+
authoritySha256: digest("replacement"),
|
|
353
|
+
createdAt: instant3,
|
|
354
|
+
documents: shared,
|
|
355
|
+
embeddingClient: embedder,
|
|
356
|
+
generation: 2,
|
|
357
|
+
})).rejects.toMatchObject({ code: "purged" });
|
|
358
|
+
expect(await cache.search({
|
|
359
|
+
authority: authority("agent:session-a:epoch-1", 1,
|
|
360
|
+
digest("agent:session-a:epoch-1"), shared),
|
|
361
|
+
embeddingClient: embedder,
|
|
362
|
+
query: "needle-alpha",
|
|
363
|
+
})).toEqual([]);
|
|
364
|
+
|
|
365
|
+
expect((await cache.purgeAuthority({
|
|
366
|
+
authorityId: "agent:session-b:epoch-1",
|
|
367
|
+
purgedAt: instant3,
|
|
368
|
+
})).orphanVectors).toBe(1);
|
|
369
|
+
expect((await cache.purgeAuthority({
|
|
370
|
+
authorityId: "agent:session-b:epoch-1",
|
|
371
|
+
purgedAt: "2026-08-31T12:03:00.000Z",
|
|
372
|
+
})).purgedAt).toBe(instant3);
|
|
373
|
+
expect(client.database.query<{ count: number }, []>(
|
|
374
|
+
"SELECT count(*) AS count FROM oh_semantic_vectors",
|
|
375
|
+
).get()?.count).toBe(0);
|
|
376
|
+
await cache.close();
|
|
377
|
+
client.close();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("keeps an unrelated in-flight stage intact across global orphan collection", async () => {
|
|
381
|
+
const client = new InterleavingLibSqlClient();
|
|
382
|
+
await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 });
|
|
383
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
384
|
+
const documents = [document("memory:in-flight", "needle-alpha")] as const;
|
|
385
|
+
let concurrentPurge: Awaited<ReturnType<typeof cache.purgeAuthority>> | null = null;
|
|
386
|
+
client.afterSecondVectorRead = async () => {
|
|
387
|
+
concurrentPurge = await cache.purgeAuthority({
|
|
388
|
+
authorityId: "agent:unrelated:epoch-1",
|
|
389
|
+
purgedAt: instant2,
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
await cache.stage({
|
|
394
|
+
authorityId: "agent:in-flight:epoch-1",
|
|
395
|
+
authoritySha256: digest("authority:in-flight"),
|
|
396
|
+
createdAt: instant1,
|
|
397
|
+
documents,
|
|
398
|
+
embeddingClient: embeddingClient([]),
|
|
399
|
+
generation: 1,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(concurrentPurge).toMatchObject({ orphanVectors: 0 });
|
|
403
|
+
expect(client.database.query<{ count: number }, []>(`SELECT count(*) AS count
|
|
404
|
+
FROM oh_semantic_memberships AS membership
|
|
405
|
+
LEFT JOIN oh_semantic_vectors AS vector
|
|
406
|
+
ON vector.profile_sha256 = '${OH_CLOUDFLARE_EMBEDDING_PROFILE_V1.profileSha256}'
|
|
407
|
+
AND vector.renderer_sha256 = (SELECT renderer_sha256 FROM oh_semantic_generations
|
|
408
|
+
WHERE authority_id = membership.authority_id AND generation = membership.generation)
|
|
409
|
+
AND vector.input_sha256 = membership.input_sha256
|
|
410
|
+
WHERE membership.authority_id = 'agent:in-flight:epoch-1' AND vector.input_sha256 IS NULL`)
|
|
411
|
+
.get()?.count).toBe(0);
|
|
412
|
+
await cache.close();
|
|
413
|
+
client.close();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("does not write a vector after the same authority purge has completed", async () => {
|
|
417
|
+
const client = new InterleavingLibSqlClient();
|
|
418
|
+
await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 });
|
|
419
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
420
|
+
const authorityId = "agent:purge-race:epoch-1";
|
|
421
|
+
client.beforeVectorWrite = async () => {
|
|
422
|
+
await cache.purgeAuthority({ authorityId, purgedAt: instant2 });
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
await expect(cache.stage({
|
|
426
|
+
authorityId,
|
|
427
|
+
authoritySha256: digest("authority:purge-race"),
|
|
428
|
+
createdAt: instant1,
|
|
429
|
+
documents: [document("memory:purge-race", "needle-alpha")],
|
|
430
|
+
embeddingClient: embeddingClient([]),
|
|
431
|
+
generation: 1,
|
|
432
|
+
})).rejects.toMatchObject({ code: "purged" });
|
|
433
|
+
expect(client.database.query<{ count: number }, []>(
|
|
434
|
+
"SELECT count(*) AS count FROM oh_semantic_vectors",
|
|
435
|
+
).get()?.count).toBe(0);
|
|
436
|
+
expect(client.database.query<{ count: number }, []>(
|
|
437
|
+
"SELECT count(*) AS count FROM oh_semantic_generations",
|
|
438
|
+
).get()?.count).toBe(0);
|
|
439
|
+
await cache.close();
|
|
440
|
+
client.close();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("fails closed on changed generation identity and invalid authority inputs", async () => {
|
|
444
|
+
const client = await bootstrapped();
|
|
445
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
446
|
+
const embedder = embeddingClient([]);
|
|
447
|
+
const documents = [document("memory:one", "needle-alpha")] as const;
|
|
448
|
+
await cache.stage({
|
|
449
|
+
authorityId: "agent:session-1:epoch-1",
|
|
450
|
+
authoritySha256: digest("first"),
|
|
451
|
+
createdAt: instant1,
|
|
452
|
+
documents,
|
|
453
|
+
embeddingClient: embedder,
|
|
454
|
+
generation: 1,
|
|
455
|
+
});
|
|
456
|
+
await expect(cache.stage({
|
|
457
|
+
authorityId: "agent:session-1:epoch-1",
|
|
458
|
+
authoritySha256: digest("different"),
|
|
459
|
+
createdAt: instant2,
|
|
460
|
+
documents,
|
|
461
|
+
embeddingClient: embedder,
|
|
462
|
+
generation: 1,
|
|
463
|
+
})).rejects.toMatchObject({ code: "conflict" });
|
|
464
|
+
await expect(cache.search({
|
|
465
|
+
authority: {
|
|
466
|
+
authorityId: "INVALID",
|
|
467
|
+
authoritySha256: digest("first"),
|
|
468
|
+
generation: 1,
|
|
469
|
+
records: [],
|
|
470
|
+
v: 1,
|
|
471
|
+
},
|
|
472
|
+
embeddingClient: embedder,
|
|
473
|
+
query: "needle-alpha",
|
|
474
|
+
})).rejects.toBeInstanceOf(OhLibSqlSemanticError);
|
|
475
|
+
await cache.close();
|
|
476
|
+
client.close();
|
|
477
|
+
});
|
|
478
|
+
});
|