Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ direct libSQL authority also support Node 24 serverless runtimes. Install the
current immutable release directly from GitHub:

```sh
bun add --global github:hraness/oh#v0.2.0
bun add --global github:hraness/oh#v0.2.1
oh --help
```

Expand Down Expand Up @@ -100,7 +100,7 @@ For a project dependency, pin the same immutable release in `package.json`:
```json
{
"dependencies": {
"@hraness/oh": "github:hraness/oh#v0.2.0"
"@hraness/oh": "github:hraness/oh#v0.2.1"
}
}
```
Expand Down Expand Up @@ -162,6 +162,7 @@ import { createClient } from "@libsql/client";
import {
bootstrapOhLibSqlAuthorityV1,
createOhLibSqlStoreAuthorityV1,
openExistingOhLibSqlStoreAuthorityV1,
} from "@hraness/oh/libsql";
import { OH_WORKING_STORE_PROFILE_V1 } from "@hraness/oh/store";

Expand All @@ -186,6 +187,21 @@ const authority = await createOhLibSqlStoreAuthorityV1(runtimeClient, {

const store = authority.store;
console.log(await store.head());

// A separately held purge worker can fail closed unless the exact binding
// already exists. Opening performs reads only; it cannot create a space.
const purgeClient = createClient({
authToken: process.env.OH_PURGE_TOKEN!,
url: process.env.OH_DATABASE_URL!,
});
const existing = await openExistingOhLibSqlStoreAuthorityV1(purgeClient, {
profile: OH_WORKING_STORE_PROFILE_V1,
realmId: "tenant:example/thread:research",
spaceId: "thread:research",
});
await existing.host.purgeWorkingSpace({});
await existing.store.close();
purgeClient.close();
```

The working profile disables operation replication. Dependency-closure export
Expand Down Expand Up @@ -451,7 +467,7 @@ keep remote sync explicit.
You can also give an agent this prompt:

```text
Install hraness/oh and its Oh Agent Skill from the immutable v0.2.0 tag at
Install hraness/oh and its Oh Agent Skill from the immutable v0.2.1 tag at
https://github.com/hraness/oh. Verify the CLI with `oh --help` and `oh version`.
Do not create or modify an Oh database until I name its path and ask you to.
```
Expand Down
2 changes: 1 addition & 1 deletion dist/cli.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bun
export declare const OH_PACKAGE_VERSION: "0.2.0";
export declare const OH_PACKAGE_VERSION: "0.2.1";
export declare function runOhCli(arguments_: readonly string[]): Promise<number>;
//# sourceMappingURL=cli.d.ts.map
2 changes: 1 addition & 1 deletion dist/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3072,7 +3072,7 @@ class Oh {

// src/cli.ts
import { readFile } from "fs/promises";
var OH_PACKAGE_VERSION = "0.2.0";
var OH_PACKAGE_VERSION = "0.2.1";
var KNOWN_OPTIONS = new Set([
"actor",
"after",
Expand Down
6 changes: 6 additions & 0 deletions dist/libsql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,10 @@ export declare function bootstrapOhLibSqlAuthorityV1(client: OhLibSqlClientV1):
}>>;
/** Opens a direct libSQL/Turso authority; this is not operation-log sync. */
export declare function createOhLibSqlStoreAuthorityV1(client: OhLibSqlClientV1, options?: OhLibSqlStoreAuthorityOptionsV1): Promise<OhStoreAuthorityV1>;
/**
* Opens an already-bound direct libSQL/Turso authority without creating or
* updating data. This seam is for separately held read or purge custody that
* must fail closed instead of acquiring space-creation authority.
*/
export declare function openExistingOhLibSqlStoreAuthorityV1(client: OhLibSqlClientV1, options?: OhLibSqlStoreAuthorityOptionsV1): Promise<OhStoreAuthorityV1>;
//# sourceMappingURL=libsql.d.ts.map
2 changes: 1 addition & 1 deletion dist/libsql.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 65 additions & 14 deletions dist/libsql.js
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,38 @@ async function initializeSpace(client, binding) {
throw new OhProfileError("The remote space is already bound to a different realm or profile.");
}
}
async function requireExistingSpace(client, binding) {
const results = await client.batch([
{ sql: BINDING_ROW_SELECT, args: [binding.spaceId] },
{
sql: `SELECT generation, graph_revision_sha256, head_operation_sha256,
records_sha256, sequence, contract_id FROM oh_authority_spaces WHERE space_id = ?`,
args: [binding.spaceId]
},
{ sql: PURGE_ROW_SELECT, args: [binding.spaceId] }
], "read");
if (results.length !== 3) {
throw new OhIntegrityError("The remote authority returned an incomplete existing-space proof.");
}
const [bindingResult, spaceResult, purgeResult] = results;
const purgeRow = purgeResult.rows[0];
if (purgeRow !== undefined) {
throw new OhPurgedSpaceError(parsePurgeReceiptRow(purgeRow, binding.spaceId, binding.bindingSha256));
}
const bindingRow = bindingResult.rows[0];
const spaceRow = spaceResult.rows[0];
if (bindingRow === undefined || spaceRow === undefined) {
throw new OhIntegrityError("The requested remote Oh space does not already exist.");
}
const persisted = parseBindingRow(bindingRow, binding.spaceId);
if (canonicalJson(persisted) !== canonicalJson(binding)) {
throw new OhProfileError("The remote space is bound to a different realm or profile.");
}
if (rowValue(spaceRow, "contract_id", 5) !== OH_CONTRACT_MANIFEST_V1.contractId) {
throw new OhIntegrityError("The existing remote space uses a different Oh contract.");
}
parseHeadRow(spaceRow);
}

class OhLibSqlStoreV1 {
binding;
Expand Down Expand Up @@ -2427,20 +2459,8 @@ class OhLibSqlStoreV1 {
this.#client.close?.();
}
}
async function createOhLibSqlStoreAuthorityV1(client, options = {}) {
const profile = parseOhStoreProfileV1(options.profile ?? OH_CANONICAL_STORE_PROFILE_V1);
if (profile === null)
throw new TypeError("Invalid libSQL store profile.");
const spaceId = options.spaceId ?? "default";
const binding = createOhStoreBindingV1({
profile,
realmId: options.realmId ?? `realm:${spaceId}`,
spaceId,
v: 1
});
await verifyAuthoritySchema(client);
await initializeSpace(client, binding);
const authority = new OhLibSqlStoreV1(client, binding, options.closeClient ?? false);
function bindOhLibSqlStoreAuthorityV1(client, binding, profile, closeClient) {
const authority = new OhLibSqlStoreV1(client, binding, closeClient);
const store = Object.freeze({
binding,
changesSince: (from, changeOptions) => authority.changesSince(from, changeOptions),
Expand All @@ -2466,7 +2486,38 @@ async function createOhLibSqlStoreAuthorityV1(client, options = {}) {
});
return Object.freeze({ host, store });
}
async function createOhLibSqlStoreAuthorityV1(client, options = {}) {
const profile = parseOhStoreProfileV1(options.profile ?? OH_CANONICAL_STORE_PROFILE_V1);
if (profile === null)
throw new TypeError("Invalid libSQL store profile.");
const spaceId = options.spaceId ?? "default";
const binding = createOhStoreBindingV1({
profile,
realmId: options.realmId ?? `realm:${spaceId}`,
spaceId,
v: 1
});
await verifyAuthoritySchema(client);
await initializeSpace(client, binding);
return bindOhLibSqlStoreAuthorityV1(client, binding, profile, options.closeClient ?? false);
}
async function openExistingOhLibSqlStoreAuthorityV1(client, options = {}) {
const profile = parseOhStoreProfileV1(options.profile ?? OH_CANONICAL_STORE_PROFILE_V1);
if (profile === null)
throw new TypeError("Invalid libSQL store profile.");
const spaceId = options.spaceId ?? "default";
const binding = createOhStoreBindingV1({
profile,
realmId: options.realmId ?? `realm:${spaceId}`,
spaceId,
v: 1
});
await verifyAuthoritySchema(client);
await requireExistingSpace(client, binding);
return bindOhLibSqlStoreAuthorityV1(client, binding, profile, options.closeClient ?? false);
}
export {
openExistingOhLibSqlStoreAuthorityV1,
createOhLibSqlStoreAuthorityV1,
bootstrapOhLibSqlAuthorityV1,
OH_LIBSQL_STORE_LIMITS_V1
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hraness/oh",
"version": "0.2.0",
"version": "0.2.1",
"description": "open-source tools for agentic research",
"type": "module",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion site/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oh-site",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"packageManager": "bun@1.3.14",
"engines": {
Expand Down
7 changes: 7 additions & 0 deletions site/public/spec/v1/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ Run it in a deployment or migration step with a short-lived schema credential.
installed schema and contract before reading or creating a bound data space,
so a runtime token does not need schema-change permission.

`openExistingOhLibSqlStoreAuthorityV1` is the least-privilege open for a
separately held reader or purge worker. It verifies the same exact schema,
contract, space head, and binding using reads only. It rejects a missing,
purged, or differently bound space and never inserts, updates, or deletes data
during open. A provider credential can therefore omit space and binding
creation while retaining only the data actions required by its later task.

Runtime open verifies the exact installed table, index, and trigger set, not
only a schema marker. Every operation, binding, and purge receipt read parses
its canonical JSON and cross-checks each duplicated SQL column. Current reads
Expand Down
2 changes: 1 addition & 1 deletion skills/oh/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ oh --help
oh version
```

The supported CLI is `@hraness/oh@0.2.0` from the immutable `v0.2.0` GitHub
The supported CLI is `@hraness/oh@0.2.1` from the immutable `v0.2.1` GitHub
tag. It requires Bun 1.3.14 or newer. The versioned contract is published at
<https://oh.computer/spec/>.

Expand Down
7 changes: 7 additions & 0 deletions spec/v1/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ Run it in a deployment or migration step with a short-lived schema credential.
installed schema and contract before reading or creating a bound data space,
so a runtime token does not need schema-change permission.

`openExistingOhLibSqlStoreAuthorityV1` is the least-privilege open for a
separately held reader or purge worker. It verifies the same exact schema,
contract, space head, and binding using reads only. It rejects a missing,
purged, or differently bound space and never inserts, updates, or deletes data
during open. A provider credential can therefore omit space and binding
creation while retaining only the data actions required by its later task.

Runtime open verifies the exact installed table, index, and trigger set, not
only a schema marker. Every operation, binding, and purge receipt read parses
its canonical JSON and cross-checks each duplicated SQL column. Current reads
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Oh } from "./sdk";
import { OH_SQLITE_SCHEMA_VERSION } from "./sqlite/migrations";
import { createOhSyncBundleV1, parseOhSyncBundleV1 } from "./sync";

export const OH_PACKAGE_VERSION = "0.2.0" as const;
export const OH_PACKAGE_VERSION = "0.2.1" as const;

type ParsedArguments = { options: Map<string, string[]>; positionals: string[] };
type ValidatedInvocation = Readonly<{
Expand Down
63 changes: 63 additions & 0 deletions src/libsql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
bootstrapOhLibSqlAuthorityV1,
createOhLibSqlStoreAuthorityV1,
OH_LIBSQL_STORE_LIMITS_V1,
openExistingOhLibSqlStoreAuthorityV1,
type OhLibSqlClientV1,
type OhLibSqlResultV1,
type OhLibSqlStatementV1,
Expand Down Expand Up @@ -114,6 +115,68 @@ describe("direct libSQL Oh authority", () => {
schemaClient.close();
});

test("opens existing purge custody without acquiring space-creation authority", async () => {
const provider = await bootstrappedClient();
const created = await createOhLibSqlStoreAuthorityV1(provider, {
profile: OH_WORKING_STORE_PROFILE_V1,
realmId: "realm:existing-only",
spaceId: "existing-only",
});
await created.store.commit({
actorId: "host.existing-only",
changes: [{ kind: "put", record: entity("entity:private", "Private"), v: 1 }],
expectedHead: await created.store.head(),
operationId: "op_existing_only",
});
await created.store.close();

const observed: string[] = [];
const forbidCreation = (statement: OhLibSqlStatementV1 | string): void => {
const sql = typeof statement === "string" ? statement : statement.sql;
observed.push(sql);
if (/\bINSERT\s+INTO\s+oh_authority_(?:spaces|bindings)\b/iu.test(sql)) {
throw new Error("existing-only credential cannot create a space or binding");
}
};
const existingOnlyClient: OhLibSqlClientV1 = {
execute: async (statement) => {
forbidCreation(statement);
return await provider.execute(statement);
},
batch: async (statements, mode) => {
statements.forEach(forbidCreation);
return await provider.batch(statements, mode);
},
};

await expect(openExistingOhLibSqlStoreAuthorityV1(existingOnlyClient, {
profile: OH_WORKING_STORE_PROFILE_V1,
realmId: "realm:missing-existing-only",
spaceId: "missing-existing-only",
})).rejects.toThrow(OhIntegrityError);
await expect(openExistingOhLibSqlStoreAuthorityV1(existingOnlyClient, {
profile: OH_WORKING_STORE_PROFILE_V1,
realmId: "realm:different",
spaceId: "existing-only",
})).rejects.toThrow(OhProfileError);

observed.length = 0;
const authority = await openExistingOhLibSqlStoreAuthorityV1(existingOnlyClient, {
profile: OH_WORKING_STORE_PROFILE_V1,
realmId: "realm:existing-only",
spaceId: "existing-only",
});
expect((await authority.store.head()).sequence).toBe(1);
expect(observed.every((sql) => /^\s*SELECT\b/iu.test(sql))).toBe(true);
await authority.host.purgeWorkingSpace({ purgedAt: "2026-08-30T01:00:00.000Z" });
expect(observed.some((sql) => /\bINSERT\s+INTO\s+oh_authority_purges\b/iu.test(sql)))
.toBe(true);
expect(observed.some((sql) =>
/\bINSERT\s+INTO\s+oh_authority_(?:spaces|bindings)\b/iu.test(sql))).toBe(false);
await authority.store.close();
provider.close();
});

test("is an async authoritative store rather than an operation-sync cache", async () => {
const client = await bootstrappedClient();
const authority = await createOhLibSqlStoreAuthorityV1(client, {
Expand Down
Loading