@hraness/oh 0.3.0 → 0.3.1

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 CHANGED
@@ -40,16 +40,16 @@ direct libSQL authority also support Node 24 serverless runtimes. Install the
40
40
  exact current release from npm:
41
41
 
42
42
  ```sh
43
- bun add --global @hraness/oh@0.3.0
43
+ bun add --global @hraness/oh@0.3.1
44
44
  oh --help
45
45
  ```
46
46
 
47
47
  The identical package bytes and their checksum are available from the
48
- [immutable GitHub Release](https://github.com/hraness/oh/releases/tag/v0.3.0),
48
+ [immutable GitHub Release](https://github.com/hraness/oh/releases/tag/v0.3.1),
49
49
  including the mirrored
50
- [`hraness-oh-0.3.0.tgz`](https://github.com/hraness/oh/releases/download/v0.3.0/hraness-oh-0.3.0.tgz)
50
+ [`hraness-oh-0.3.1.tgz`](https://github.com/hraness/oh/releases/download/v0.3.1/hraness-oh-0.3.1.tgz)
51
51
  and
52
- [`SHA256SUMS`](https://github.com/hraness/oh/releases/download/v0.3.0/SHA256SUMS).
52
+ [`SHA256SUMS`](https://github.com/hraness/oh/releases/download/v0.3.1/SHA256SUMS).
53
53
 
54
54
  Oh writes to `.oh/oh.sqlite` and the `default` space unless you select another
55
55
  path or space. Keep `.oh/` out of source control.
@@ -107,7 +107,7 @@ For a project dependency, pin the same immutable release in `package.json`:
107
107
  ```json
108
108
  {
109
109
  "dependencies": {
110
- "@hraness/oh": "0.3.0"
110
+ "@hraness/oh": "0.3.1"
111
111
  }
112
112
  }
113
113
  ```
@@ -567,9 +567,12 @@ const staged = await cache.stage({
567
567
  embeddingClient: embedder,
568
568
  generation: snapshot.head.generation,
569
569
  });
570
+ const published = await cache.publishedHead({
571
+ authorityId: "thread:research/epoch:1",
572
+ });
570
573
  await cache.publish({
571
574
  authorityId: "thread:research/epoch:1",
572
- expectedPublishedGeneration: null,
575
+ expectedPublishedGeneration: published?.generation ?? null,
573
576
  generation: staged.generation,
574
577
  });
575
578
  ```
@@ -649,9 +652,9 @@ keep remote sync explicit.
649
652
  You can also give an agent this prompt:
650
653
 
651
654
  ```text
652
- Install @hraness/oh@0.3.0 from npm and use its packaged Oh Agent Skill. The
653
- exact npm tarball and SHA256SUMS are mirrored by the immutable v0.3.0 Release at
654
- https://github.com/hraness/oh/releases/tag/v0.3.0. Verify the CLI with
655
+ Install @hraness/oh@0.3.1 from npm and use its packaged Oh Agent Skill. The
656
+ exact npm tarball and SHA256SUMS are mirrored by the immutable v0.3.1 Release at
657
+ https://github.com/hraness/oh/releases/tag/v0.3.1. Verify the CLI with
655
658
  `oh --help` and `oh version`.
656
659
  Do not create or modify an Oh database until I name its path and ask you to.
657
660
  ```
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env bun
2
- export declare const OH_PACKAGE_VERSION: "0.3.0";
2
+ export declare const OH_PACKAGE_VERSION: "0.3.1";
3
3
  export declare function runOhCli(arguments_: readonly string[]): Promise<number>;
4
4
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -3144,7 +3144,7 @@ class Oh {
3144
3144
 
3145
3145
  // src/cli.ts
3146
3146
  import { readFile } from "fs/promises";
3147
- var OH_PACKAGE_VERSION = "0.3.0";
3147
+ var OH_PACKAGE_VERSION = "0.3.1";
3148
3148
  var KNOWN_OPTIONS = new Set([
3149
3149
  "actor",
3150
3150
  "after",
@@ -49,6 +49,18 @@ export type OhSemanticPublishResultV1 = Readonly<{
49
49
  published: boolean;
50
50
  v: 1;
51
51
  }>;
52
+ /** The exact, currently published cache pointer for one semantic authority. */
53
+ export type OhSemanticPublishedHeadV1 = Readonly<{
54
+ authorityId: string;
55
+ authoritySha256: Sha256Hex;
56
+ generation: number;
57
+ generationSha256: Sha256Hex;
58
+ membershipSha256: Sha256Hex;
59
+ profileSha256: Sha256Hex;
60
+ publishedAt: string;
61
+ rendererSha256: Sha256Hex;
62
+ v: 1;
63
+ }>;
52
64
  export type OhSemanticSearchResultV1 = Readonly<{
53
65
  chunkOrdinal: number;
54
66
  key: string;
@@ -77,6 +89,13 @@ export declare class OhLibSqlSemanticCacheV1 {
77
89
  /** @internal Public callers should use `openOhLibSqlSemanticCacheV1`. */
78
90
  static open(client: OhLibSqlClientV1, closeClient: boolean): Promise<OhLibSqlSemanticCacheV1>;
79
91
  close(): Promise<void>;
92
+ /**
93
+ * Reads the current compare-and-swap base without exposing cache rows or
94
+ * private source text. An absent or purged authority has no published head.
95
+ */
96
+ publishedHead(input: Readonly<{
97
+ authorityId: string;
98
+ }>): Promise<OhSemanticPublishedHeadV1 | null>;
80
99
  stage(input: Readonly<{
81
100
  authorityId: string;
82
101
  authoritySha256: Sha256Hex;
@@ -1 +1 @@
1
- {"version":3,"file":"libsql-semantic.d.ts","sourceRoot":"","sources":["../src/libsql-semantic.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"libsql-semantic.d.ts","sourceRoot":"","sources":["../src/libsql-semantic.ts"],"names":[],"mappings":"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"}
@@ -1456,6 +1456,33 @@ class OhLibSqlSemanticCacheV1 {
1456
1456
  if (this.#closeClient)
1457
1457
  this.#client.close?.();
1458
1458
  }
1459
+ async publishedHead(input) {
1460
+ this.#open();
1461
+ const authorityId = parseAuthorityId(input.authorityId);
1462
+ if (await readPurge(this.#client, authorityId) !== null)
1463
+ return null;
1464
+ const head = await readHead(this.#client, authorityId);
1465
+ if (head === null)
1466
+ return null;
1467
+ const generation = await readGeneration(this.#client, authorityId, head.generation);
1468
+ if (generation === null || !headMatchesGeneration(head, generation)) {
1469
+ if (await readPurge(this.#client, authorityId) !== null)
1470
+ return null;
1471
+ throw new OhLibSqlSemanticError("integrity", "The semantic published head does not match its immutable generation.");
1472
+ }
1473
+ if (await readPurge(this.#client, authorityId) !== null)
1474
+ return null;
1475
+ const finalHead = await readHead(this.#client, authorityId);
1476
+ if (finalHead === null) {
1477
+ if (await readPurge(this.#client, authorityId) !== null)
1478
+ return null;
1479
+ throw new OhLibSqlSemanticError("integrity", "The semantic published head disappeared during its read.");
1480
+ }
1481
+ if (canonicalJson(finalHead) !== canonicalJson(head)) {
1482
+ throw new OhLibSqlSemanticError("conflict", "The semantic published head changed during its read.");
1483
+ }
1484
+ return Object.freeze({ ...head, v: 1 });
1485
+ }
1459
1486
  async stage(input) {
1460
1487
  this.#open();
1461
1488
  validateEmbeddingClient(input.embeddingClient);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/oh",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "open-source tools for agentic research",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,8 +30,8 @@ oh --help
30
30
  oh version
31
31
  ```
32
32
 
33
- The supported CLI is the exact npm release `@hraness/oh@0.3.0`. Its identical
34
- tarball and checksum are mirrored by the immutable GitHub Release `v0.3.0`.
33
+ The supported CLI is the exact npm release `@hraness/oh@0.3.1`. Its identical
34
+ tarball and checksum are mirrored by the immutable GitHub Release `v0.3.1`.
35
35
  It requires Bun 1.3.14 or newer. The versioned contract is published at
36
36
  <https://oh.computer/spec/>.
37
37
 
@@ -68,6 +68,15 @@ hit is rejoined to a caller-supplied current authority record digest. A stale
68
68
  head, changed authority digest, changed record digest, concurrent publish, or
69
69
  purge returns no stale authority.
70
70
 
71
+ `publishedHead` returns the current compare-and-swap base or `null` for an
72
+ absent or purged authority. Its bounded projection contains only the authority,
73
+ generation, profile, renderer, membership, generation and publication
74
+ identities already held by the cache; it exposes no vector, record body or
75
+ formatted input. The read verifies that the pointer still matches its immutable
76
+ generation and fails with an integrity error on divergence. A host can read the
77
+ base, stage a later authoritative generation, and pass the returned generation
78
+ to `publish` without keeping a second cache pointer elsewhere.
79
+
71
80
  `purgeAuthority` writes a permanent tombstone, removes the head, memberships,
72
81
  and staged generations, then deletes vectors no remaining authority uses. The
73
82
  tombstone prevents the same authority ID from being staged or published again.
package/src/cli.ts CHANGED
@@ -9,7 +9,7 @@ import { Oh } from "./sdk";
9
9
  import { OH_SQLITE_SCHEMA_VERSION } from "./sqlite/migrations";
10
10
  import { createOhSyncBundleV1, parseOhSyncBundleV1 } from "./sync";
11
11
 
12
- export const OH_PACKAGE_VERSION = "0.3.0" as const;
12
+ export const OH_PACKAGE_VERSION = "0.3.1" as const;
13
13
 
14
14
  type ParsedArguments = { options: Map<string, string[]>; positionals: string[] };
15
15
  type ValidatedInvocation = Readonly<{
@@ -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";
@@ -197,6 +213,7 @@ describe("libSQL derived semantic cache", () => {
197
213
  document("memory:two", "needle-beta"),
198
214
  ] as const;
199
215
  const firstAuthoritySha256 = digest("authority:first");
216
+ expect(await cache.publishedHead({ authorityId })).toBeNull();
200
217
  const first = await cache.stage({
201
218
  authorityId,
202
219
  authoritySha256: firstAuthoritySha256,
@@ -207,6 +224,7 @@ describe("libSQL derived semantic cache", () => {
207
224
  });
208
225
  expect(first).toMatchObject({ chunks: 2, documents: 2, embedded: 2, reused: 0 });
209
226
  expect(calls).toHaveLength(1);
227
+ expect(await cache.publishedHead({ authorityId })).toBeNull();
210
228
  expect(await cache.stage({
211
229
  authorityId,
212
230
  authoritySha256: firstAuthoritySha256,
@@ -235,6 +253,17 @@ describe("libSQL derived semantic cache", () => {
235
253
  generation: 1,
236
254
  publishedAt: instant1,
237
255
  })).toMatchObject({ published: true });
256
+ expect(await cache.publishedHead({ authorityId })).toEqual({
257
+ authorityId,
258
+ authoritySha256: firstAuthoritySha256,
259
+ generation: 1,
260
+ generationSha256: first.generationSha256,
261
+ membershipSha256: first.membershipSha256,
262
+ profileSha256: OH_CLOUDFLARE_EMBEDDING_PROFILE_V1.profileSha256,
263
+ publishedAt: instant1,
264
+ rendererSha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
265
+ v: 1,
266
+ });
238
267
  expect(await cache.publish({
239
268
  authorityId,
240
269
  expectedPublishedGeneration: null,
@@ -302,6 +331,12 @@ describe("libSQL derived semantic cache", () => {
302
331
  generation: 2,
303
332
  publishedAt: instant2,
304
333
  })).toMatchObject({ published: true });
334
+ expect(await cache.publishedHead({ authorityId })).toMatchObject({
335
+ authoritySha256: secondAuthoritySha256,
336
+ generation: 2,
337
+ publishedAt: instant2,
338
+ v: 1,
339
+ });
305
340
  expect(await cache.search({
306
341
  authority: authority(authorityId, 1, firstAuthoritySha256, firstDocuments),
307
342
  embeddingClient: embedder,
@@ -361,6 +396,9 @@ describe("libSQL derived semantic cache", () => {
361
396
  embeddingClient: embedder,
362
397
  query: "needle-alpha",
363
398
  })).toEqual([]);
399
+ expect(await cache.publishedHead({
400
+ authorityId: "agent:session-a:epoch-1",
401
+ })).toBeNull();
364
402
 
365
403
  expect((await cache.purgeAuthority({
366
404
  authorityId: "agent:session-b:epoch-1",
@@ -472,6 +510,75 @@ describe("libSQL derived semantic cache", () => {
472
510
  embeddingClient: embedder,
473
511
  query: "needle-alpha",
474
512
  })).rejects.toBeInstanceOf(OhLibSqlSemanticError);
513
+ await expect(cache.publishedHead({ authorityId: "INVALID" }))
514
+ .rejects.toMatchObject({ code: "invalid-input" });
515
+ await cache.close();
516
+ client.close();
517
+ });
518
+
519
+ test("rejects a published pointer that diverges from its immutable generation", async () => {
520
+ const client = await bootstrapped();
521
+ const cache = await openOhLibSqlSemanticCacheV1(client);
522
+ const authorityId = "agent:published-head-integrity:epoch-1";
523
+ await cache.stage({
524
+ authorityId,
525
+ authoritySha256: digest("published-head-integrity"),
526
+ createdAt: instant1,
527
+ documents: [document("memory:one", "needle-alpha")],
528
+ embeddingClient: embeddingClient([]),
529
+ generation: 1,
530
+ });
531
+ await cache.publish({
532
+ authorityId,
533
+ expectedPublishedGeneration: null,
534
+ generation: 1,
535
+ publishedAt: instant1,
536
+ });
537
+ client.database.query(`UPDATE oh_semantic_heads SET authority_sha256 = ?
538
+ WHERE authority_id = ?`).run(digest("forged-head"), authorityId);
539
+ await expect(cache.publishedHead({ authorityId }))
540
+ .rejects.toMatchObject({ code: "integrity" });
541
+ await cache.close();
542
+ client.close();
543
+ });
544
+
545
+ test("does not return a head that changed during its bounded read", async () => {
546
+ const client = new PublishedHeadInterleavingLibSqlClient();
547
+ await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 });
548
+ const cache = await openOhLibSqlSemanticCacheV1(client);
549
+ const authorityId = "agent:published-head-race:epoch-1";
550
+ const embedder = embeddingClient([]);
551
+ for (const generation of [1, 2] as const) {
552
+ await cache.stage({
553
+ authorityId,
554
+ authoritySha256: digest(`published-head-race:${generation}`),
555
+ createdAt: generation === 1 ? instant1 : instant2,
556
+ documents: [document(
557
+ `memory:${generation}`,
558
+ generation === 1 ? "needle-alpha" : "needle-beta",
559
+ )],
560
+ embeddingClient: embedder,
561
+ generation,
562
+ });
563
+ }
564
+ await cache.publish({
565
+ authorityId,
566
+ expectedPublishedGeneration: null,
567
+ generation: 1,
568
+ publishedAt: instant1,
569
+ });
570
+ client.afterHeadRead = async () => {
571
+ await cache.publish({
572
+ authorityId,
573
+ expectedPublishedGeneration: 1,
574
+ generation: 2,
575
+ publishedAt: instant2,
576
+ });
577
+ };
578
+ await expect(cache.publishedHead({ authorityId }))
579
+ .rejects.toMatchObject({ code: "conflict" });
580
+ expect(await cache.publishedHead({ authorityId }))
581
+ .toMatchObject({ generation: 2, publishedAt: instant2 });
475
582
  await cache.close();
476
583
  client.close();
477
584
  });
@@ -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;