@hraness/oh 0.3.0 → 0.3.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.
- package/README.md +41 -21
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/libsql-semantic-v2.d.ts +160 -0
- package/dist/libsql-semantic-v2.d.ts.map +1 -0
- package/dist/libsql-semantic.d.ts +19 -0
- package/dist/libsql-semantic.d.ts.map +1 -1
- package/dist/semantic-cloud.d.ts +1 -0
- package/dist/semantic-cloud.d.ts.map +1 -1
- package/dist/semantic-cloud.js +1570 -0
- package/package.json +1 -1
- package/skills/oh/SKILL.md +2 -2
- package/spec/README.md +8 -1
- 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/semantic-cloud.md +9 -0
- 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.ts +1 -1
- package/src/libsql-semantic-v2.test.ts +1114 -0
- package/src/libsql-semantic-v2.ts +1891 -0
- package/src/libsql-semantic.test.ts +157 -1
- package/src/libsql-semantic.ts +51 -0
- package/src/semantic-cloud.ts +1 -0
|
@@ -85,6 +85,22 @@ class InterleavingLibSqlClient extends SqliteCompatibleLibSqlClient {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
class PublishedHeadInterleavingLibSqlClient extends SqliteCompatibleLibSqlClient {
|
|
89
|
+
afterHeadRead: (() => Promise<void>) | null = null;
|
|
90
|
+
|
|
91
|
+
override async execute(statement: OhLibSqlStatementV1 | string): Promise<OhLibSqlResultV1> {
|
|
92
|
+
const result = await super.execute(statement);
|
|
93
|
+
const sql = typeof statement === "string" ? statement : statement.sql;
|
|
94
|
+
if (/FROM\s+oh_semantic_heads\s+WHERE\s+authority_id\s*=\s*\?/iu.test(sql)
|
|
95
|
+
&& this.afterHeadRead !== null) {
|
|
96
|
+
const hook = this.afterHeadRead;
|
|
97
|
+
this.afterHeadRead = null;
|
|
98
|
+
await hook();
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
const instant1 = "2026-08-31T12:00:00.000Z";
|
|
89
105
|
const instant2 = "2026-08-31T12:01:00.000Z";
|
|
90
106
|
const instant3 = "2026-08-31T12:02:00.000Z";
|
|
@@ -131,7 +147,11 @@ function document(
|
|
|
131
147
|
async function bootstrapped(): Promise<SqliteCompatibleLibSqlClient> {
|
|
132
148
|
const client = new SqliteCompatibleLibSqlClient();
|
|
133
149
|
expect(await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 }))
|
|
134
|
-
.
|
|
150
|
+
.toEqual({
|
|
151
|
+
schemaSha256: "d9903735e900e08a8c49c9ad36ea8f277b1689117ef88b9a2c6b906456dfbace" as Sha256Hex,
|
|
152
|
+
schemaVersion: 1,
|
|
153
|
+
v: 1,
|
|
154
|
+
});
|
|
135
155
|
return client;
|
|
136
156
|
}
|
|
137
157
|
|
|
@@ -151,6 +171,51 @@ function authority(
|
|
|
151
171
|
}
|
|
152
172
|
|
|
153
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
|
+
|
|
154
219
|
test("requires explicit bootstrap and refuses a partial or drifted schema", async () => {
|
|
155
220
|
const empty = new SqliteCompatibleLibSqlClient();
|
|
156
221
|
await expect(openOhLibSqlSemanticCacheV1(empty)).rejects.toMatchObject({
|
|
@@ -197,6 +262,7 @@ describe("libSQL derived semantic cache", () => {
|
|
|
197
262
|
document("memory:two", "needle-beta"),
|
|
198
263
|
] as const;
|
|
199
264
|
const firstAuthoritySha256 = digest("authority:first");
|
|
265
|
+
expect(await cache.publishedHead({ authorityId })).toBeNull();
|
|
200
266
|
const first = await cache.stage({
|
|
201
267
|
authorityId,
|
|
202
268
|
authoritySha256: firstAuthoritySha256,
|
|
@@ -207,6 +273,7 @@ describe("libSQL derived semantic cache", () => {
|
|
|
207
273
|
});
|
|
208
274
|
expect(first).toMatchObject({ chunks: 2, documents: 2, embedded: 2, reused: 0 });
|
|
209
275
|
expect(calls).toHaveLength(1);
|
|
276
|
+
expect(await cache.publishedHead({ authorityId })).toBeNull();
|
|
210
277
|
expect(await cache.stage({
|
|
211
278
|
authorityId,
|
|
212
279
|
authoritySha256: firstAuthoritySha256,
|
|
@@ -235,6 +302,17 @@ describe("libSQL derived semantic cache", () => {
|
|
|
235
302
|
generation: 1,
|
|
236
303
|
publishedAt: instant1,
|
|
237
304
|
})).toMatchObject({ published: true });
|
|
305
|
+
expect(await cache.publishedHead({ authorityId })).toEqual({
|
|
306
|
+
authorityId,
|
|
307
|
+
authoritySha256: firstAuthoritySha256,
|
|
308
|
+
generation: 1,
|
|
309
|
+
generationSha256: first.generationSha256,
|
|
310
|
+
membershipSha256: first.membershipSha256,
|
|
311
|
+
profileSha256: OH_CLOUDFLARE_EMBEDDING_PROFILE_V1.profileSha256,
|
|
312
|
+
publishedAt: instant1,
|
|
313
|
+
rendererSha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
|
|
314
|
+
v: 1,
|
|
315
|
+
});
|
|
238
316
|
expect(await cache.publish({
|
|
239
317
|
authorityId,
|
|
240
318
|
expectedPublishedGeneration: null,
|
|
@@ -302,6 +380,12 @@ describe("libSQL derived semantic cache", () => {
|
|
|
302
380
|
generation: 2,
|
|
303
381
|
publishedAt: instant2,
|
|
304
382
|
})).toMatchObject({ published: true });
|
|
383
|
+
expect(await cache.publishedHead({ authorityId })).toMatchObject({
|
|
384
|
+
authoritySha256: secondAuthoritySha256,
|
|
385
|
+
generation: 2,
|
|
386
|
+
publishedAt: instant2,
|
|
387
|
+
v: 1,
|
|
388
|
+
});
|
|
305
389
|
expect(await cache.search({
|
|
306
390
|
authority: authority(authorityId, 1, firstAuthoritySha256, firstDocuments),
|
|
307
391
|
embeddingClient: embedder,
|
|
@@ -361,6 +445,9 @@ describe("libSQL derived semantic cache", () => {
|
|
|
361
445
|
embeddingClient: embedder,
|
|
362
446
|
query: "needle-alpha",
|
|
363
447
|
})).toEqual([]);
|
|
448
|
+
expect(await cache.publishedHead({
|
|
449
|
+
authorityId: "agent:session-a:epoch-1",
|
|
450
|
+
})).toBeNull();
|
|
364
451
|
|
|
365
452
|
expect((await cache.purgeAuthority({
|
|
366
453
|
authorityId: "agent:session-b:epoch-1",
|
|
@@ -472,6 +559,75 @@ describe("libSQL derived semantic cache", () => {
|
|
|
472
559
|
embeddingClient: embedder,
|
|
473
560
|
query: "needle-alpha",
|
|
474
561
|
})).rejects.toBeInstanceOf(OhLibSqlSemanticError);
|
|
562
|
+
await expect(cache.publishedHead({ authorityId: "INVALID" }))
|
|
563
|
+
.rejects.toMatchObject({ code: "invalid-input" });
|
|
564
|
+
await cache.close();
|
|
565
|
+
client.close();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("rejects a published pointer that diverges from its immutable generation", async () => {
|
|
569
|
+
const client = await bootstrapped();
|
|
570
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
571
|
+
const authorityId = "agent:published-head-integrity:epoch-1";
|
|
572
|
+
await cache.stage({
|
|
573
|
+
authorityId,
|
|
574
|
+
authoritySha256: digest("published-head-integrity"),
|
|
575
|
+
createdAt: instant1,
|
|
576
|
+
documents: [document("memory:one", "needle-alpha")],
|
|
577
|
+
embeddingClient: embeddingClient([]),
|
|
578
|
+
generation: 1,
|
|
579
|
+
});
|
|
580
|
+
await cache.publish({
|
|
581
|
+
authorityId,
|
|
582
|
+
expectedPublishedGeneration: null,
|
|
583
|
+
generation: 1,
|
|
584
|
+
publishedAt: instant1,
|
|
585
|
+
});
|
|
586
|
+
client.database.query(`UPDATE oh_semantic_heads SET authority_sha256 = ?
|
|
587
|
+
WHERE authority_id = ?`).run(digest("forged-head"), authorityId);
|
|
588
|
+
await expect(cache.publishedHead({ authorityId }))
|
|
589
|
+
.rejects.toMatchObject({ code: "integrity" });
|
|
590
|
+
await cache.close();
|
|
591
|
+
client.close();
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test("does not return a head that changed during its bounded read", async () => {
|
|
595
|
+
const client = new PublishedHeadInterleavingLibSqlClient();
|
|
596
|
+
await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 });
|
|
597
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
598
|
+
const authorityId = "agent:published-head-race:epoch-1";
|
|
599
|
+
const embedder = embeddingClient([]);
|
|
600
|
+
for (const generation of [1, 2] as const) {
|
|
601
|
+
await cache.stage({
|
|
602
|
+
authorityId,
|
|
603
|
+
authoritySha256: digest(`published-head-race:${generation}`),
|
|
604
|
+
createdAt: generation === 1 ? instant1 : instant2,
|
|
605
|
+
documents: [document(
|
|
606
|
+
`memory:${generation}`,
|
|
607
|
+
generation === 1 ? "needle-alpha" : "needle-beta",
|
|
608
|
+
)],
|
|
609
|
+
embeddingClient: embedder,
|
|
610
|
+
generation,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
await cache.publish({
|
|
614
|
+
authorityId,
|
|
615
|
+
expectedPublishedGeneration: null,
|
|
616
|
+
generation: 1,
|
|
617
|
+
publishedAt: instant1,
|
|
618
|
+
});
|
|
619
|
+
client.afterHeadRead = async () => {
|
|
620
|
+
await cache.publish({
|
|
621
|
+
authorityId,
|
|
622
|
+
expectedPublishedGeneration: 1,
|
|
623
|
+
generation: 2,
|
|
624
|
+
publishedAt: instant2,
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
await expect(cache.publishedHead({ authorityId }))
|
|
628
|
+
.rejects.toMatchObject({ code: "conflict" });
|
|
629
|
+
expect(await cache.publishedHead({ authorityId }))
|
|
630
|
+
.toMatchObject({ generation: 2, publishedAt: instant2 });
|
|
475
631
|
await cache.close();
|
|
476
632
|
client.close();
|
|
477
633
|
});
|
package/src/libsql-semantic.ts
CHANGED
|
@@ -79,6 +79,19 @@ export type OhSemanticPublishResultV1 = Readonly<{
|
|
|
79
79
|
v: 1;
|
|
80
80
|
}>;
|
|
81
81
|
|
|
82
|
+
/** The exact, currently published cache pointer for one semantic authority. */
|
|
83
|
+
export type OhSemanticPublishedHeadV1 = Readonly<{
|
|
84
|
+
authorityId: string;
|
|
85
|
+
authoritySha256: Sha256Hex;
|
|
86
|
+
generation: number;
|
|
87
|
+
generationSha256: Sha256Hex;
|
|
88
|
+
membershipSha256: Sha256Hex;
|
|
89
|
+
profileSha256: Sha256Hex;
|
|
90
|
+
publishedAt: string;
|
|
91
|
+
rendererSha256: Sha256Hex;
|
|
92
|
+
v: 1;
|
|
93
|
+
}>;
|
|
94
|
+
|
|
82
95
|
export type OhSemanticSearchResultV1 = Readonly<{
|
|
83
96
|
chunkOrdinal: number;
|
|
84
97
|
key: string;
|
|
@@ -749,6 +762,44 @@ export class OhLibSqlSemanticCacheV1 {
|
|
|
749
762
|
if (this.#closeClient) this.#client.close?.();
|
|
750
763
|
}
|
|
751
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Reads the current compare-and-swap base without exposing cache rows or
|
|
767
|
+
* private source text. An absent or purged authority has no published head.
|
|
768
|
+
*/
|
|
769
|
+
async publishedHead(input: Readonly<{
|
|
770
|
+
authorityId: string;
|
|
771
|
+
}>): Promise<OhSemanticPublishedHeadV1 | null> {
|
|
772
|
+
this.#open();
|
|
773
|
+
const authorityId = parseAuthorityId(input.authorityId);
|
|
774
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
775
|
+
const head = await readHead(this.#client, authorityId);
|
|
776
|
+
if (head === null) return null;
|
|
777
|
+
const generation = await readGeneration(this.#client, authorityId, head.generation);
|
|
778
|
+
if (generation === null || !headMatchesGeneration(head, generation)) {
|
|
779
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
780
|
+
throw new OhLibSqlSemanticError(
|
|
781
|
+
"integrity",
|
|
782
|
+
"The semantic published head does not match its immutable generation.",
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
786
|
+
const finalHead = await readHead(this.#client, authorityId);
|
|
787
|
+
if (finalHead === null) {
|
|
788
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
789
|
+
throw new OhLibSqlSemanticError(
|
|
790
|
+
"integrity",
|
|
791
|
+
"The semantic published head disappeared during its read.",
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (canonicalJson(finalHead) !== canonicalJson(head)) {
|
|
795
|
+
throw new OhLibSqlSemanticError(
|
|
796
|
+
"conflict",
|
|
797
|
+
"The semantic published head changed during its read.",
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
return Object.freeze({ ...head, v: 1 });
|
|
801
|
+
}
|
|
802
|
+
|
|
752
803
|
async stage(input: Readonly<{
|
|
753
804
|
authorityId: string;
|
|
754
805
|
authoritySha256: Sha256Hex;
|
package/src/semantic-cloud.ts
CHANGED