@hraness/oh 0.2.3

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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +598 -0
  3. package/dist/canonical.d.ts +32 -0
  4. package/dist/canonical.d.ts.map +1 -0
  5. package/dist/cli.d.ts +4 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +3419 -0
  8. package/dist/contract.d.ts +33 -0
  9. package/dist/contract.d.ts.map +1 -0
  10. package/dist/graph.d.ts +67 -0
  11. package/dist/graph.d.ts.map +1 -0
  12. package/dist/index.d.ts +9 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +1988 -0
  15. package/dist/libsql.d.ts +57 -0
  16. package/dist/libsql.d.ts.map +1 -0
  17. package/dist/libsql.js +2662 -0
  18. package/dist/memory.d.ts +366 -0
  19. package/dist/memory.d.ts.map +1 -0
  20. package/dist/memory.js +3650 -0
  21. package/dist/ontology.d.ts +242 -0
  22. package/dist/ontology.d.ts.map +1 -0
  23. package/dist/operation.d.ts +24 -0
  24. package/dist/operation.d.ts.map +1 -0
  25. package/dist/projection-public.d.ts +59 -0
  26. package/dist/projection-public.d.ts.map +1 -0
  27. package/dist/projection-public.js +1682 -0
  28. package/dist/projection-suss.d.ts +17 -0
  29. package/dist/projection-suss.d.ts.map +1 -0
  30. package/dist/projection-suss.js +1721 -0
  31. package/dist/projection.d.ts +315 -0
  32. package/dist/projection.d.ts.map +1 -0
  33. package/dist/schema.d.ts +45 -0
  34. package/dist/schema.d.ts.map +1 -0
  35. package/dist/sdk.d.ts +51 -0
  36. package/dist/sdk.d.ts.map +1 -0
  37. package/dist/sdk.js +3072 -0
  38. package/dist/search.d.ts +34 -0
  39. package/dist/search.d.ts.map +1 -0
  40. package/dist/semantic.d.ts +83 -0
  41. package/dist/semantic.d.ts.map +1 -0
  42. package/dist/semantic.js +706 -0
  43. package/dist/sqlite/driver.d.ts +6 -0
  44. package/dist/sqlite/driver.d.ts.map +1 -0
  45. package/dist/sqlite/index.d.ts +5 -0
  46. package/dist/sqlite/index.d.ts.map +1 -0
  47. package/dist/sqlite/index.js +2840 -0
  48. package/dist/sqlite/migrations.d.ts +10 -0
  49. package/dist/sqlite/migrations.d.ts.map +1 -0
  50. package/dist/sqlite/port.d.ts +40 -0
  51. package/dist/sqlite/port.d.ts.map +1 -0
  52. package/dist/sqlite/runtime.d.ts +23 -0
  53. package/dist/sqlite/runtime.d.ts.map +1 -0
  54. package/dist/sqlite/store.d.ts +85 -0
  55. package/dist/sqlite/store.d.ts.map +1 -0
  56. package/dist/store.d.ts +236 -0
  57. package/dist/store.d.ts.map +1 -0
  58. package/dist/store.js +996 -0
  59. package/dist/sync.d.ts +57 -0
  60. package/dist/sync.d.ts.map +1 -0
  61. package/dist/sync.js +1328 -0
  62. package/package.json +121 -0
  63. package/skills/oh/SKILL.md +206 -0
  64. package/skills/oh/agents/openai.yaml +4 -0
  65. package/spec/README.md +74 -0
  66. package/spec/manifest.json +41 -0
  67. package/spec/v1/canonical-json.md +59 -0
  68. package/spec/v1/contract.json +28 -0
  69. package/spec/v1/contract.schema.json +58 -0
  70. package/spec/v1/embedding-profile.json +11 -0
  71. package/spec/v1/embedding.md +56 -0
  72. package/spec/v1/graph.md +87 -0
  73. package/spec/v1/memory.md +193 -0
  74. package/spec/v1/migration.md +92 -0
  75. package/spec/v1/ontology.json +55 -0
  76. package/spec/v1/ontology.md +80 -0
  77. package/spec/v1/operation.schema.json +138 -0
  78. package/spec/v1/projection-identity.schema.json +58 -0
  79. package/spec/v1/projection-query.schema.json +60 -0
  80. package/spec/v1/projection-result.schema.json +452 -0
  81. package/spec/v1/projection-rule-pack.schema.json +182 -0
  82. package/spec/v1/projection.md +165 -0
  83. package/spec/v1/record.schema.json +95 -0
  84. package/spec/v1/schema-evolution.md +51 -0
  85. package/spec/v1/schema-revision.schema.json +178 -0
  86. package/spec/v1/storage.md +88 -0
  87. package/spec/v1/store.md +131 -0
  88. package/spec/v1/sync-bundle.schema.json +51 -0
  89. package/spec/v1/sync.md +67 -0
  90. package/src/canonical.test.ts +46 -0
  91. package/src/canonical.ts +203 -0
  92. package/src/cli.test.ts +103 -0
  93. package/src/cli.ts +308 -0
  94. package/src/contract.ts +87 -0
  95. package/src/contracts.test.ts +147 -0
  96. package/src/graph.ts +248 -0
  97. package/src/index.ts +8 -0
  98. package/src/libsql.test.ts +657 -0
  99. package/src/libsql.ts +1687 -0
  100. package/src/memory.test.ts +783 -0
  101. package/src/memory.ts +1684 -0
  102. package/src/ontology.ts +573 -0
  103. package/src/operation.ts +80 -0
  104. package/src/projection-public.ts +53 -0
  105. package/src/projection-suss.ts +129 -0
  106. package/src/projection.test.ts +418 -0
  107. package/src/projection.ts +1457 -0
  108. package/src/schema.ts +156 -0
  109. package/src/sdk.ts +96 -0
  110. package/src/search.ts +66 -0
  111. package/src/semantic.test.ts +480 -0
  112. package/src/semantic.ts +333 -0
  113. package/src/sqlite/driver.ts +47 -0
  114. package/src/sqlite/index.ts +4 -0
  115. package/src/sqlite/migrations.test.ts +44 -0
  116. package/src/sqlite/migrations.ts +178 -0
  117. package/src/sqlite/port.test.ts +127 -0
  118. package/src/sqlite/port.ts +120 -0
  119. package/src/sqlite/runtime.test.ts +68 -0
  120. package/src/sqlite/runtime.ts +53 -0
  121. package/src/sqlite/store.test.ts +295 -0
  122. package/src/sqlite/store.ts +988 -0
  123. package/src/store.test.ts +121 -0
  124. package/src/store.ts +701 -0
  125. package/src/sync.test.ts +117 -0
  126. package/src/sync.ts +227 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hraness contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,598 @@
1
+ # open-source tools for agentic research
2
+
3
+ [![skills.sh](https://skills.sh/b/hraness/oh)](https://skills.sh/hraness/oh)
4
+
5
+ Oh is a local-first ontology kernel, SQLite store, CLI, TypeScript SDK, and
6
+ Agent Skill for building durable, inspectable research graphs. It stores
7
+ content-addressed records and an append-only operation log, checks every
8
+ mutation against an explicit versioned contract, and keeps keyword and semantic
9
+ indexes derived and replaceable.
10
+
11
+ [Website](https://oh.computer) · [Versioned specification](spec/README.md) ·
12
+ [Agent Skill](skills/oh/SKILL.md)
13
+
14
+ ## Why Oh
15
+
16
+ - **Make meaning explicit.** Every record declares a kind, stable logical key,
17
+ ordered dependencies, and canonical JSON content under a versioned ontology
18
+ and schema contract.
19
+ - **Keep changes accountable.** Content digests, append-only operations,
20
+ compare-and-swap writes, and replay verification make accepted graph changes
21
+ inspectable and stale writes visible.
22
+ - **Keep local state authoritative.** Records and operations live in one SQLite
23
+ file you control. Sync is an explicit transport seam and accepts only
24
+ fast-forward histories after an exact contract handshake.
25
+ - **Treat search as a view.** FTS5 documents and optional local embeddings are
26
+ derived from current record digests, so either index can be rebuilt without
27
+ becoming graph authority.
28
+ - **Derive without silently asserting.** Positive recursive rules run against
29
+ one exact graph head and fact-pack digest. Their tuples and bounded proofs
30
+ are deterministic, disposable output rather than accepted graph records.
31
+ - **Remember without conflating authority.** An experimental facade composes a
32
+ purgeable working authority with one pinned canonical head while preserving
33
+ lane, conflict, record, and proof provenance.
34
+
35
+ ## Install and first run
36
+
37
+ [Bun 1.3.14 or newer](https://bun.sh/docs/installation) is required for the
38
+ CLI, local SDK, and SQLite authority. The runtime-neutral store contracts and
39
+ direct libSQL authority also support Node 24 serverless runtimes. Install the
40
+ current immutable release directly from GitHub:
41
+
42
+ ```sh
43
+ bun add --global github:hraness/oh#v0.2.3
44
+ oh --help
45
+ ```
46
+
47
+ Oh writes to `.oh/oh.sqlite` and the `default` space unless you select another
48
+ path or space. Keep `.oh/` out of source control.
49
+
50
+ ```sh
51
+ oh init
52
+ oh put \
53
+ --kind entity \
54
+ --key entity:ada-lovelace \
55
+ --json '{"name":"Ada Lovelace","role":"mathematician"}'
56
+ oh get entity:ada-lovelace
57
+ oh search "mathematician" --mode keyword
58
+ oh verify
59
+ ```
60
+
61
+ This first task creates one entity, reads it back, finds it through the derived
62
+ keyword index, and verifies the authoritative operation chain. It needs no
63
+ account, hosted model, remote database, or semantic-search dependency.
64
+
65
+ ## What becomes observable
66
+
67
+ Commands print canonical JSON, except `oh version` and help. A missing `oh get`
68
+ record exits with status 3. Invalid input, an integrity failure, or a concurrent
69
+ head conflict exits with status 1 and leaves the current log intact.
70
+
71
+ Run `oh contract` to inspect the ontology, graph, schema, and SQLite versions
72
+ compiled into the installed runtime. Opening an Oh database separately checks
73
+ that its stored contract manifest matches that runtime.
74
+
75
+ ## How Oh works
76
+
77
+ An Oh space has one current graph and one append-only operation chain:
78
+
79
+ - A record has a stable logical key, one declared kind, ordered dependencies,
80
+ arbitrary canonical JSON content, and a SHA-256 digest over its envelope.
81
+ - A mutation puts or tombstones records in one `BEGIN IMMEDIATE` transaction.
82
+ Compare-and-swap checks reject a stale generation before the head moves.
83
+ - Every operation binds the parent operation, graph revision, complete record
84
+ set, contract, actor, timestamp, and sequence to a digest.
85
+ - SQLite records and the operation log are authoritative. FTS5 documents and
86
+ local embedding files can be deleted and rebuilt.
87
+ - Sync exchanges bounded operation bundles after an exact contract handshake.
88
+ Only fast-forward histories settle automatically; divergence fails closed.
89
+
90
+ The V1 kernel distinguishes seven ideas: entity, statement, assertion,
91
+ evidence, context, inquiry, and projection. The generic graph envelope also
92
+ supports schema, vocabulary, review, rights, edition, and activity records.
93
+ Product-specific meaning belongs in registered codecs and versioned schema
94
+ records, not in hidden storage conventions.
95
+
96
+ ## Use the SDK
97
+
98
+ For a project dependency, pin the same immutable release in `package.json`:
99
+
100
+ ```json
101
+ {
102
+ "dependencies": {
103
+ "@hraness/oh": "github:hraness/oh#v0.2.3"
104
+ }
105
+ }
106
+ ```
107
+
108
+ The base package has no required runtime dependencies. Keyword search,
109
+ ontology parsing, SQLite storage, replay verification, and sync need no hosted
110
+ model.
111
+
112
+ ```ts
113
+ import { Oh } from "@hraness/oh/sdk";
114
+
115
+ const oh = Oh.open({
116
+ databasePath: ".oh/research.sqlite",
117
+ spaceId: "paper-one",
118
+ });
119
+
120
+ try {
121
+ const head = oh.head();
122
+ oh.put({
123
+ expectedHead: head,
124
+ key: "entity:ada-lovelace",
125
+ kind: "entity",
126
+ value: { name: "Ada Lovelace" },
127
+ });
128
+
129
+ const result = await oh.search("Ada", { mode: "keyword" });
130
+ console.log(result.results[0]?.record);
131
+ console.log(oh.verify());
132
+ } finally {
133
+ await oh.close();
134
+ }
135
+ ```
136
+
137
+ Pass the head you actually reviewed when concurrent writers matter. Do not
138
+ retry `OhConflictError` blindly. Read the new head and records, reconcile the
139
+ intended change, then submit a new operation.
140
+
141
+ The root entrypoint exports canonical JSON, ontology, schema, graph, operation,
142
+ store, and sync contracts. Use `@hraness/oh/store` for the runtime-neutral
143
+ promise interface, `@hraness/oh/libsql` for a direct Node 24 or serverless
144
+ authority, `@hraness/oh/sqlite` for the local Bun store, `@hraness/oh/sdk` for
145
+ the local `Oh` facade, `@hraness/oh/sync` for transport seams,
146
+ `@hraness/oh/projection` for recursive derived views, and
147
+ `@hraness/oh/semantic` for the optional local embedding backend. The
148
+ `@hraness/oh/experimental/memory` subpath composes host-bound working and
149
+ canonical stores behind a smaller agent-facing surface.
150
+
151
+ ## Open a scoped working store
152
+
153
+ Working memory uses the same V1 graph and operation bytes under a different
154
+ storage lifecycle. The host chooses and retains the realm binding. Application
155
+ code receives the promise-based store and keeps the host object that can purge
156
+ a working space out of agent tools. A model-facing adapter should expose strict
157
+ semantic ingress and bounded query methods, not generic commit or change-feed
158
+ access.
159
+
160
+ ```ts
161
+ import { createClient } from "@libsql/client";
162
+ import {
163
+ bootstrapOhLibSqlAuthorityV1,
164
+ createOhLibSqlStoreAuthorityV1,
165
+ purgeOhLibSqlWorkingSpaceV1,
166
+ } from "@hraness/oh/libsql";
167
+ import { OH_WORKING_STORE_PROFILE_V1 } from "@hraness/oh/store";
168
+
169
+ // Run once during deployment with a short-lived schema credential.
170
+ const schemaClient = createClient({
171
+ authToken: process.env.OH_SCHEMA_TOKEN!,
172
+ url: process.env.OH_DATABASE_URL!,
173
+ });
174
+ await bootstrapOhLibSqlAuthorityV1(schemaClient);
175
+ schemaClient.close();
176
+
177
+ // Runtime opens verify the schema and execute no DDL.
178
+ const runtimeClient = createClient({
179
+ authToken: process.env.OH_RUNTIME_TOKEN!,
180
+ url: process.env.OH_DATABASE_URL!,
181
+ });
182
+ const authority = await createOhLibSqlStoreAuthorityV1(runtimeClient, {
183
+ profile: OH_WORKING_STORE_PROFILE_V1,
184
+ realmId: "tenant:example/thread:research",
185
+ spaceId: "thread:research",
186
+ });
187
+
188
+ const store = authority.store;
189
+ console.log(await store.head());
190
+
191
+ // A separately held purge worker either purges the exact existing binding or
192
+ // writes an empty-head tombstone when creation never completed. It cannot
193
+ // create a space or binding, and a delayed creator cannot resurrect custody.
194
+ const purgeClient = createClient({
195
+ authToken: process.env.OH_PURGE_TOKEN!,
196
+ url: process.env.OH_DATABASE_URL!,
197
+ });
198
+ await purgeOhLibSqlWorkingSpaceV1(purgeClient, {
199
+ closeClient: true,
200
+ profile: OH_WORKING_STORE_PROFILE_V1,
201
+ realmId: "tenant:example/thread:research",
202
+ spaceId: "thread:research",
203
+ });
204
+ ```
205
+
206
+ The working profile disables operation replication. Dependency-closure export
207
+ remains available for explicit reviewed adoption. `purgeWorkingSpace` exists
208
+ only on `authority.host`; do not expose that object or raw database credentials
209
+ through a model tool. Read the [store-port specification](spec/v1/store.md) for
210
+ exact snapshot, change-feed, codec ingress, closure, and purge behavior.
211
+
212
+ ## Compose working and canonical memory
213
+
214
+ The experimental memory facade uses the same Oh kernel twice, not a separate
215
+ memory database model. Trusted host code supplies two distinct physical store
216
+ handles, their expected binding digests, one exact canonical head, sealed
217
+ working codecs, digest-identified fact extractors, and a closed registry of
218
+ named projection programs:
219
+
220
+ ```ts
221
+ import { createOhMemoryAgentV1 } from "@hraness/oh/experimental/memory";
222
+
223
+ const memory = await createOhMemoryAgentV1({
224
+ actorId: "research.memory-agent",
225
+ canonical: {
226
+ authorityId: "project-reviewed",
227
+ expectedBindingSha256: canonical.store.binding.bindingSha256,
228
+ expectedHead: await canonical.store.head(),
229
+ store: canonical.store,
230
+ },
231
+ nominationRoutes: [{
232
+ destinationPurpose: "kb.review",
233
+ nominationId: "knowledge-review",
234
+ }],
235
+ programs: [{
236
+ programId: "project.dependencies",
237
+ purpose: "answer.research",
238
+ query,
239
+ rulePack,
240
+ }],
241
+ working: {
242
+ authorityId: "thread-working",
243
+ codecs,
244
+ expectedBindingSha256: working.store.binding.bindingSha256,
245
+ store: working.store,
246
+ },
247
+ });
248
+
249
+ const result = await memory.query({
250
+ programId: "project.dependencies",
251
+ v: 1,
252
+ });
253
+ ```
254
+
255
+ When the model must bind a small set of values and traverse a larger result,
256
+ use the additive V2 factory. The host still owns the query and rules. It names
257
+ only query-body variables as parameters and fixes every evaluation, row, page,
258
+ and page-byte limit before exposing the agent object:
259
+
260
+ ```ts
261
+ import { createOhMemoryAgentV2 } from "@hraness/oh/experimental/memory";
262
+
263
+ const memoryV2 = await createOhMemoryAgentV2({
264
+ actorId: "research.memory-agent",
265
+ canonical: {
266
+ authorityId: "project-reviewed",
267
+ expectedBindingSha256: canonical.store.binding.bindingSha256,
268
+ expectedHead: await canonical.store.head(),
269
+ store: canonical.store,
270
+ },
271
+ extractors: [valueChunkExtractor],
272
+ programs: [{
273
+ evaluation: {
274
+ maximumDerivedTuples: 8_192,
275
+ maximumProofDepth: 32,
276
+ maximumProofNodes: 1_024,
277
+ maximumResultBytes: 8 * 1024 * 1024,
278
+ maximumRounds: 64,
279
+ maximumTotalProofNodes: 16_384,
280
+ maximumWorkUnits: 1_000_000,
281
+ },
282
+ maximumPageBytes: 1024 * 1024,
283
+ maximumRows: 4_096,
284
+ pageSize: 128,
285
+ parameters: ["key", "lane"],
286
+ programId: "memory.value-chunks",
287
+ purpose: "answer.memory",
288
+ query: valueChunkQuery,
289
+ rulePack: valueChunkRules,
290
+ v: 2,
291
+ }],
292
+ working: {
293
+ authorityId: "thread-working",
294
+ codecs,
295
+ expectedBindingSha256: working.store.binding.bindingSha256,
296
+ store: working.store,
297
+ },
298
+ });
299
+
300
+ let continuation: string | null = null;
301
+ do {
302
+ const page = await memoryV2.query({
303
+ bindings: { key: "entity:research", lane: "working" },
304
+ continuation,
305
+ programId: "memory.value-chunks",
306
+ v: 2,
307
+ });
308
+ continuation = page.continuation;
309
+ } while (continuation !== null);
310
+ ```
311
+
312
+ V2 evaluates one complete bounded result before paging it. Any projection row
313
+ or byte truncation returns no page. A continuation is an authenticated bearer
314
+ cursor that binds its offset to the exact physical heads, program, bindings,
315
+ projection result, page size, and row count, so a working-head change fails
316
+ instead of mixing snapshots. Pass it back only to the same exact named query;
317
+ do not synthesize, edit, or log it.
318
+
319
+ The factory generates a random continuation key by default, which makes a
320
+ cursor valid only for that agent instance. If the host reconstructs agents or
321
+ routes queries across replicas, pass the same host-owned 32 through 64 byte
322
+ `Uint8Array` as `continuationKey`. The factory clones it; key rotation
323
+ invalidates outstanding cursors. The result publishes a deterministic
324
+ `continuationSha256` separately, and `resultSha256` commits that digest rather
325
+ than the opaque key-dependent token.
326
+
327
+ The returned object has only `remember`, `query`, `explain`, and `nominate`.
328
+ The host fixes the working actor, each program purpose, and every nomination
329
+ destination before exposing those methods. `remember` accepts an idempotency
330
+ request plus semantic changes and returns a locator-free working-lane receipt;
331
+ it does not accept caller-supplied actor or time claims. The object cannot
332
+ select a store, install a rule, write canonical knowledge, sync, or purge.
333
+ Query identity binds both exact physical lanes and all projection policy;
334
+ conflicting same-key records remain visible. Explanation requires a bounded,
335
+ short-lived opaque capability bound to the exact result. Nomination chooses
336
+ only a host-registered route and creates a verified working dependency-closure
337
+ proposal; it never promotes it. Read the
338
+ [experimental memory specification](spec/v1/memory.md) for the complete
339
+ authority and lifecycle boundary.
340
+
341
+ ## Derive an exact projection
342
+
343
+ The projection subpath is pure TypeScript and runs in Node 24 serverless
344
+ functions without loading SQLite. A snapshot binds the current space head and
345
+ complete record-reference set. A fact pack binds the deterministic extractor
346
+ that translated those records into relations. Rules and queries are typed data,
347
+ not strings or executable callbacks.
348
+
349
+ ```ts
350
+ import {
351
+ OH_PROJECTION_RECORD_FACT_EXTRACTOR_V1,
352
+ createOhProjectionDatasetV1,
353
+ createOhProjectionLiteralV1,
354
+ createOhProjectionQueryV1,
355
+ createOhProjectionRecordFactsV1,
356
+ createOhProjectionRulePackV1,
357
+ createOhProjectionRuleV1,
358
+ createOhProjectionSnapshotV1,
359
+ evaluateOhProjectionV1,
360
+ ohProjectionVariableV1 as variable,
361
+ } from "@hraness/oh/projection";
362
+
363
+ const records = oh.store.snapshotRecords();
364
+ const snapshot = createOhProjectionSnapshotV1({
365
+ head: oh.head(),
366
+ records,
367
+ spaceId: oh.store.spaceId,
368
+ });
369
+ const dataset = createOhProjectionDatasetV1({
370
+ extractorSha256: OH_PROJECTION_RECORD_FACT_EXTRACTOR_V1.extractorSha256,
371
+ factPackId: OH_PROJECTION_RECORD_FACT_EXTRACTOR_V1.factPackId,
372
+ factPackRevision: OH_PROJECTION_RECORD_FACT_EXTRACTOR_V1.factPackRevision,
373
+ facts: createOhProjectionRecordFactsV1(records),
374
+ snapshot,
375
+ });
376
+
377
+ const x = variable("x");
378
+ const y = variable("y");
379
+ const z = variable("z");
380
+ const literal = (relation: string, ...terms: ReturnType<typeof variable>[]) =>
381
+ createOhProjectionLiteralV1({ relation, terms });
382
+ const rulePack = createOhProjectionRulePackV1({
383
+ rulePackId: "example.dependencies",
384
+ rulePackRevision: 1,
385
+ rules: [
386
+ createOhProjectionRuleV1({
387
+ body: [literal("oh.dependency", x, y)],
388
+ head: literal("depends", x, y),
389
+ ruleId: "depends.direct",
390
+ }),
391
+ createOhProjectionRuleV1({
392
+ body: [literal("depends", x, y), literal("oh.dependency", y, z)],
393
+ head: literal("depends", x, z),
394
+ ruleId: "depends.transitive",
395
+ }),
396
+ ],
397
+ });
398
+ const query = createOhProjectionQueryV1({
399
+ find: ["x", "z"],
400
+ queryId: "all.dependencies",
401
+ where: [literal("depends", x, z)],
402
+ });
403
+
404
+ const result = evaluateOhProjectionV1({ dataset, query, rulePack, snapshot });
405
+ console.log(result.rows);
406
+ ```
407
+
408
+ `result.authority` is always `derived`. Oh does not commit a result, elevate an
409
+ agent assertion, or make a proof authoritative. Changing the snapshot,
410
+ extracted fact set, rule pack, or query produces a new identity and requires a
411
+ full rebuild.
412
+
413
+ The reference evaluator favors bounded, transparent correctness. It supports
414
+ positive recursion and set semantics; it does not yet support negation,
415
+ aggregation, arithmetic, or incremental invalidation. An optional compatibility
416
+ lane evaluates the same rules with exactly `@suss/datalog@0.20.0` and returns a
417
+ result only after every relation agrees with the reference evaluator:
418
+
419
+ ```sh
420
+ bun add @suss/datalog@0.20.0
421
+ ```
422
+
423
+ ```ts
424
+ import { evaluateOhProjectionWithSussV1 } from "@hraness/oh/experimental/projection-suss";
425
+
426
+ const checked = evaluateOhProjectionWithSussV1({
427
+ dataset,
428
+ query,
429
+ rulePack,
430
+ snapshot,
431
+ });
432
+ ```
433
+
434
+ Suss does not expose an execution-budget hook. Its adapter therefore applies a
435
+ conservative finite-domain admission bound and refuses programs it cannot prove
436
+ will stay inside the requested tuple ceiling. The built-in evaluator remains
437
+ available for those programs. The compatibility lane deliberately runs both
438
+ engines; it is an equivalence check, not a performance backend.
439
+
440
+ ## Add local semantic search
441
+
442
+ Semantic state is a cache. Each QMD result is rejoined to the current SQLite
443
+ record by exact record digest before Oh returns it.
444
+
445
+ ```sh
446
+ bun add @tobilu/qmd@2.5.3
447
+ ```
448
+
449
+ ```ts
450
+ import { Oh } from "@hraness/oh/sdk";
451
+ import { OhQmdSemanticBackendV1 } from "@hraness/oh/semantic";
452
+
453
+ const backend = new OhQmdSemanticBackendV1({
454
+ cacheDirectory: ".oh/semantic",
455
+ });
456
+ const oh = Oh.open({ semanticBackend: backend });
457
+
458
+ try {
459
+ await oh.indexSemantic();
460
+ const result = await oh.search("early programmable machines", {
461
+ mode: "hybrid",
462
+ });
463
+ console.log(result.results);
464
+ } finally {
465
+ await oh.close();
466
+ }
467
+ ```
468
+
469
+ The exact V1 profile is documented in
470
+ [the embedding specification](spec/v1/embedding.md). The model download and
471
+ all inference stay local. Keyword mode remains available when QMD or the model
472
+ is absent.
473
+
474
+ ## Sync through libSQL or Turso
475
+
476
+ `createLibSqlOperationSyncTransportV1` accepts the `execute` and `batch` shape
477
+ implemented by libSQL clients. Oh creates two remote tables for the contract
478
+ manifest and immutable operation chain. It does not send semantic cache files.
479
+
480
+ ```sh
481
+ bun add @libsql/client@^0.17.4
482
+ ```
483
+
484
+ ```ts
485
+ import { createClient } from "@libsql/client";
486
+ import { Oh } from "@hraness/oh/sdk";
487
+ import { createLibSqlOperationSyncTransportV1 } from "@hraness/oh/sync";
488
+
489
+ const client = createClient({ url: process.env.TURSO_DATABASE_URL! });
490
+ const oh = Oh.open();
491
+
492
+ try {
493
+ const transport = createLibSqlOperationSyncTransportV1(client);
494
+ const result = await oh.sync(transport, { remoteId: "research-cloud" });
495
+ console.log(result);
496
+ } finally {
497
+ await oh.close();
498
+ client.close();
499
+ }
500
+ ```
501
+
502
+ The consumer owns credentials, client construction, retry policy, and remote
503
+ availability. The transport handshakes before exchanging data and refuses a
504
+ different contract or a non-fast-forward history.
505
+
506
+ For offline transfer, `oh sync export` writes a bounded bundle to stdout and
507
+ `oh sync import --file <path>` verifies and imports it idempotently.
508
+
509
+ ## Boundaries and limitations
510
+
511
+ - Digests detect changed contract, record, operation, and bundle bytes. They do
512
+ not encrypt data, authenticate an actor, authorize a write, or prove that a
513
+ research statement is true.
514
+ - Oh does not redact record values. Protect the database, filesystem, backups,
515
+ and any sync destination according to the sensitivity of the research graph.
516
+ - The optional QMD cache contains derived record text. Its pinned model and
517
+ inference stay local, but the cache still needs the same deliberate handling
518
+ as its source data.
519
+ - The libSQL seam validates exact contracts and fast-forward history. The
520
+ consumer remains responsible for credentials, transport security, access
521
+ control, tenant isolation, backup, retry, and remote availability.
522
+ - Divergent histories do not merge automatically. Oh returns an explicit
523
+ conflict and leaves reconciliation policy to the consumer.
524
+ - Projection tuples and proofs are derived cache output. Persisting or
525
+ publishing them as knowledge requires an explicit application-level review
526
+ and a new authoritative graph operation.
527
+
528
+ Read [SECURITY.md](SECURITY.md) for the complete public threat model.
529
+
530
+ ## Give Oh to a coding agent
531
+
532
+ The repository includes an installable Agent Skill at
533
+ [`skills/oh`](skills/oh/SKILL.md). Copy or link that directory into the skill
534
+ location used by your agent runner. The skill teaches an agent to inspect the
535
+ contract and current head, use generation-checked writes, verify replay, and
536
+ keep remote sync explicit.
537
+
538
+ You can also give an agent this prompt:
539
+
540
+ ```text
541
+ Install hraness/oh and its Oh Agent Skill from the immutable v0.2.3 tag at
542
+ https://github.com/hraness/oh. Verify the CLI with `oh --help` and `oh version`.
543
+ Do not create or modify an Oh database until I name its path and ask you to.
544
+ ```
545
+
546
+ ## Find the right documentation
547
+
548
+ - **Install and prove the local path:** follow
549
+ [Install and first run](#install-and-first-run).
550
+ - **Embed Oh in a tool:** use [the SDK](#use-the-sdk), then select the narrow
551
+ package subpath for SQLite, sync, projection, or optional semantics.
552
+ - **Give Oh to an agent:** install the [Oh Agent Skill](skills/oh/SKILL.md) and
553
+ keep its database, space, sync target, and mutation authority explicit.
554
+ - **Implement or change a contract:** begin with the
555
+ [specification map](spec/README.md), then read the applicable V1 narrative and
556
+ machine-readable schema together.
557
+ - **Contribute or report a vulnerability:** follow
558
+ [CONTRIBUTING.md](CONTRIBUTING.md) or the private process in
559
+ [SECURITY.md](SECURITY.md).
560
+
561
+ ## Specification
562
+
563
+ [`spec/manifest.json`](spec/manifest.json) is the machine-readable discovery
564
+ document. The current contract is V1:
565
+
566
+ - [Canonical JSON and digests](spec/v1/canonical-json.md)
567
+ - [Ontology](spec/v1/ontology.md)
568
+ - [Schema evolution](spec/v1/schema-evolution.md)
569
+ - [Graph and operations](spec/v1/graph.md)
570
+ - [SQLite storage](spec/v1/storage.md)
571
+ - [Store ports, profiles, and direct libSQL authority](spec/v1/store.md)
572
+ - [Sync protocol](spec/v1/sync.md)
573
+ - [Local embedding profile](spec/v1/embedding.md)
574
+ - [Derived projections](spec/v1/projection.md)
575
+ - [Compatibility and migration](spec/v1/migration.md)
576
+
577
+ The JSON Schemas describe exchange envelopes. Runtime parsers additionally
578
+ enforce canonical ordering, byte limits, referential integrity, and digest
579
+ preimages that JSON Schema cannot express.
580
+
581
+ ## Verify a checkout
582
+
583
+ ```sh
584
+ bun install --frozen-lockfile --ignore-scripts
585
+ bun run check
586
+ ```
587
+
588
+ The complete gate type-checks the package, runs the complete test suite,
589
+ rebuilds the committed `dist/` entrypoints, and must leave tracked files
590
+ unchanged.
591
+
592
+ ## Contribute
593
+
594
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before changing a wire contract or
595
+ migration. Report security issues through the private process in
596
+ [SECURITY.md](SECURITY.md).
597
+
598
+ Oh is available under the [MIT License](LICENSE).
@@ -0,0 +1,32 @@
1
+ export type JsonPrimitive = boolean | null | number | string;
2
+ export type JsonValue = JsonPrimitive | readonly JsonValue[] | JsonObject;
3
+ export type JsonObject = Readonly<{
4
+ [key: string]: JsonValue;
5
+ }>;
6
+ declare const sha256HexBrand: unique symbol;
7
+ export type Sha256Hex = string & {
8
+ readonly [sha256HexBrand]: "Sha256Hex";
9
+ };
10
+ export declare class OhValidationError extends Error {
11
+ readonly code: string;
12
+ readonly path: string;
13
+ constructor(code: string, path: string, message: string);
14
+ }
15
+ export declare function isPlainRecord(value: unknown): value is Record<string, unknown>;
16
+ export declare function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean;
17
+ /** RFC 8785-style canonical JSON for the JSON subset accepted by Oh. */
18
+ export declare function canonicalJson(value: unknown): string;
19
+ export declare function parseCanonicalJson(text: string, maximumBytes?: number): JsonValue;
20
+ export declare function utf8ByteLength(value: string): number;
21
+ export declare function sha256Hex(value: string | Uint8Array): Sha256Hex;
22
+ export declare function canonicalSha256(value: unknown): Sha256Hex;
23
+ export declare function parseSha256Hex(value: unknown): Sha256Hex | null;
24
+ export declare function parseCanonicalInstantV1(value: unknown): string | null;
25
+ export declare function canonicalNow(): string;
26
+ export declare function opaqueId(prefix: string): string;
27
+ export declare function safeCode(value: unknown, maximumLength?: number): string | null;
28
+ export declare function boundedText(value: unknown, maximumBytes?: number): string | null;
29
+ export declare function orderedUnique<T>(values: readonly T[], key: (value: T) => string): boolean;
30
+ export declare function sortUnique<T>(values: readonly T[], key: (value: T) => string): readonly T[];
31
+ export {};
32
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,SAAS,EAAE,GAAG,UAAU,CAAC;AAC1E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,CAAC;AAEhE,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE5E,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAMxD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG7F;AAgFD,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,SAAmB,GAAG,SAAS,CAc3F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE/D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAEzD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAI/D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOrE;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,SAAM,GAAG,MAAM,GAAG,IAAI,CAK3E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,SAAY,GAAG,MAAM,GAAG,IAAI,CAUnF;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,OAAO,CAEzF;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC,EAAE,CAU3F"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ export declare const OH_PACKAGE_VERSION: "0.2.3";
3
+ export declare function runOhCli(arguments_: readonly string[]): Promise<number>;
4
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAWA,eAAO,MAAM,kBAAkB,EAAG,OAAgB,CAAC;AA0MnD,wBAAsB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuF7E"}