@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/src/store.ts ADDED
@@ -0,0 +1,701 @@
1
+ import {
2
+ canonicalJson,
3
+ canonicalSha256,
4
+ hasExactKeys,
5
+ isPlainRecord,
6
+ parseCanonicalInstantV1,
7
+ parseSha256Hex,
8
+ safeCode,
9
+ type JsonValue,
10
+ type Sha256Hex,
11
+ } from "./canonical";
12
+ import {
13
+ OH_CONTRACT_MANIFEST_V1,
14
+ OhRecordCodecRegistry,
15
+ } from "./contract";
16
+ export { OhRecordCodecRegistry } from "./contract";
17
+ import {
18
+ canonicalKnowledgeGraphChangesV1,
19
+ createKnowledgeGraphRecordV1,
20
+ graphRevisionSha256V1,
21
+ knowledgeGraphRecordRefV1,
22
+ OH_GRAPH_LIMITS_V1,
23
+ OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1,
24
+ parseKnowledgeGraphRecordV1,
25
+ type KnowledgeGraphChangeV1,
26
+ type KnowledgeGraphRecordKindV1,
27
+ type KnowledgeGraphRecordV1,
28
+ } from "./graph";
29
+ import {
30
+ createOhOperationV1,
31
+ parseOhOperationV1,
32
+ type OhOperationV1,
33
+ } from "./operation";
34
+
35
+ export class OhConflictError extends Error {
36
+ constructor(message: string) { super(message); this.name = "OhConflictError"; }
37
+ }
38
+
39
+ export class OhIntegrityError extends Error {
40
+ constructor(message: string) { super(message); this.name = "OhIntegrityError"; }
41
+ }
42
+
43
+ export class OhDependencyError extends Error {
44
+ constructor(message: string) { super(message); this.name = "OhDependencyError"; }
45
+ }
46
+
47
+ export class OhProfileError extends Error {
48
+ constructor(message: string) { super(message); this.name = "OhProfileError"; }
49
+ }
50
+
51
+ export type OhHeadV1 = Readonly<{
52
+ generation: number;
53
+ graphRevisionSha256: Sha256Hex | null;
54
+ operationSha256: Sha256Hex | null;
55
+ recordsSha256: Sha256Hex;
56
+ sequence: number;
57
+ v: 1;
58
+ }>;
59
+
60
+ export type OhHeadRefV1 = Pick<OhHeadV1, "operationSha256" | "sequence">;
61
+
62
+ export type OhCommitInputV1 = Readonly<{
63
+ actorId: string;
64
+ changes: readonly KnowledgeGraphChangeV1[];
65
+ expectedHead: Pick<OhHeadV1, "generation" | "operationSha256">;
66
+ instant?: string;
67
+ operationId: string;
68
+ }>;
69
+
70
+ export type OhSnapshotV1 = Readonly<{
71
+ head: OhHeadV1;
72
+ records: readonly KnowledgeGraphRecordV1[];
73
+ v: 1;
74
+ }>;
75
+
76
+ export type OhChangesPageV1 = Readonly<{
77
+ from: OhHeadRefV1;
78
+ hasMore: boolean;
79
+ operations: readonly OhOperationV1[];
80
+ through: OhHeadV1;
81
+ to: OhHeadRefV1;
82
+ v: 1;
83
+ }>;
84
+
85
+ export type OhStoreVerificationV1 = Readonly<{
86
+ head: OhHeadV1;
87
+ integrity: "verified";
88
+ operations: number;
89
+ records: number;
90
+ v: 1;
91
+ }>;
92
+
93
+ export type OhStoreCapabilitiesV1 = Readonly<{
94
+ changesSince: true;
95
+ dependencyClosureExport: true;
96
+ exactSnapshots: true;
97
+ operationReplication: boolean;
98
+ semanticBundleCommit: true;
99
+ v: 1;
100
+ wholeSpacePurge: boolean;
101
+ }>;
102
+
103
+ export type OhStoreProfileV1 = Readonly<{
104
+ applicationProfileSha256: Sha256Hex | null;
105
+ capabilities: OhStoreCapabilitiesV1;
106
+ profileId: string;
107
+ profileKind: "canonical" | "working";
108
+ profileSha256: Sha256Hex;
109
+ v: 1;
110
+ }>;
111
+
112
+ export type OhStoreBindingV1 = Readonly<{
113
+ bindingSha256: Sha256Hex;
114
+ contractSha256: Sha256Hex;
115
+ profile: OhStoreProfileV1;
116
+ realmId: string;
117
+ spaceId: string;
118
+ v: 1;
119
+ }>;
120
+
121
+ export const OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
122
+ applicationProfileSha256: null,
123
+ capabilities: {
124
+ changesSince: true,
125
+ dependencyClosureExport: true,
126
+ exactSnapshots: true,
127
+ operationReplication: true,
128
+ semanticBundleCommit: true,
129
+ v: 1,
130
+ wholeSpacePurge: false,
131
+ },
132
+ profileId: "oh.store.canonical.v1",
133
+ profileKind: "canonical",
134
+ v: 1,
135
+ });
136
+
137
+ export const OH_WORKING_STORE_PROFILE_V1 = createOhStoreProfileV1({
138
+ applicationProfileSha256: null,
139
+ capabilities: {
140
+ changesSince: true,
141
+ dependencyClosureExport: true,
142
+ exactSnapshots: true,
143
+ operationReplication: false,
144
+ semanticBundleCommit: true,
145
+ v: 1,
146
+ wholeSpacePurge: true,
147
+ },
148
+ profileId: "oh.store.working.v1",
149
+ profileKind: "working",
150
+ v: 1,
151
+ });
152
+
153
+ export type OhDependencyClosureV1 = Readonly<{
154
+ binding: OhStoreBindingV1;
155
+ closureSha256: Sha256Hex;
156
+ head: OhHeadV1;
157
+ records: readonly KnowledgeGraphRecordV1[];
158
+ roots: readonly string[];
159
+ v: 1;
160
+ }>;
161
+
162
+ export const OH_DEPENDENCY_CLOSURE_LIMITS_V1 = Object.freeze({
163
+ bytes: 64 * 1024 * 1024,
164
+ records: 8_192,
165
+ roots: 1_024,
166
+ });
167
+
168
+ export type OhSpacePurgeReceiptV1 = Readonly<{
169
+ bindingSha256: Sha256Hex;
170
+ priorHead: OhHeadV1;
171
+ purgedAt: string;
172
+ receiptSha256: Sha256Hex;
173
+ spaceId: string;
174
+ v: 1;
175
+ }>;
176
+
177
+ export class OhPurgedSpaceError extends Error {
178
+ readonly receipt: OhSpacePurgeReceiptV1;
179
+
180
+ constructor(receipt: OhSpacePurgeReceiptV1) {
181
+ super(`Oh space ${receipt.spaceId} was purged at ${receipt.purgedAt}.`);
182
+ this.name = "OhPurgedSpaceError";
183
+ this.receipt = receipt;
184
+ }
185
+ }
186
+
187
+ export interface OhStoreV1 {
188
+ readonly binding: OhStoreBindingV1;
189
+ changesSince(
190
+ from: OhHeadRefV1,
191
+ options?: Readonly<{ limit?: number; through?: OhHeadRefV1 }>,
192
+ ): Promise<OhChangesPageV1>;
193
+ close(): Promise<void>;
194
+ commit(input: OhCommitInputV1): Promise<OhOperationV1>;
195
+ exportDependencyClosure(input: Readonly<{
196
+ head?: OhHeadRefV1;
197
+ maximumRecords?: number;
198
+ roots: readonly string[];
199
+ }>): Promise<OhDependencyClosureV1>;
200
+ head(): Promise<OhHeadV1>;
201
+ snapshot(options?: Readonly<{
202
+ head?: OhHeadRefV1;
203
+ maximumRecords?: number;
204
+ }>): Promise<OhSnapshotV1>;
205
+ verify(): Promise<OhStoreVerificationV1>;
206
+ }
207
+
208
+ /** Kept separate so an agent-facing store object never carries deletion authority. */
209
+ export interface OhStoreHostControlV1 {
210
+ readonly binding: OhStoreBindingV1;
211
+ purgeWorkingSpace(input: Readonly<{ purgedAt?: string }>): Promise<OhSpacePurgeReceiptV1>;
212
+ }
213
+
214
+ export type OhStoreAuthorityV1 = Readonly<{
215
+ host: OhStoreHostControlV1;
216
+ store: OhStoreV1;
217
+ }>;
218
+
219
+ const EMPTY_RECORDS_SHA256 = canonicalSha256([]);
220
+
221
+ export function emptyOhHeadV1(): OhHeadV1 {
222
+ return {
223
+ generation: 0,
224
+ graphRevisionSha256: null,
225
+ operationSha256: null,
226
+ recordsSha256: EMPTY_RECORDS_SHA256,
227
+ sequence: 0,
228
+ v: 1,
229
+ };
230
+ }
231
+
232
+ export function parseOhHeadV1(value: unknown): OhHeadV1 | null {
233
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["generation", "graphRevisionSha256",
234
+ "operationSha256", "recordsSha256", "sequence", "v"]) || value.v !== 1) return null;
235
+ const graphRevisionSha256 = value.graphRevisionSha256 === null
236
+ ? null : parseSha256Hex(value.graphRevisionSha256);
237
+ const operationSha256 = value.operationSha256 === null
238
+ ? null : parseSha256Hex(value.operationSha256);
239
+ const recordsSha256 = parseSha256Hex(value.recordsSha256);
240
+ const generation = Number.isSafeInteger(value.generation) && (value.generation as number) >= 0
241
+ ? value.generation as number : null;
242
+ const sequence = Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0
243
+ ? value.sequence as number : null;
244
+ return generation !== null && sequence !== null && generation === sequence
245
+ && recordsSha256 !== null
246
+ && (value.graphRevisionSha256 === null || graphRevisionSha256 !== null)
247
+ && (value.operationSha256 === null || operationSha256 !== null)
248
+ && ((sequence === 0) === (operationSha256 === null))
249
+ && ((sequence === 0) === (graphRevisionSha256 === null))
250
+ ? { generation, graphRevisionSha256, operationSha256, recordsSha256, sequence, v: 1 }
251
+ : null;
252
+ }
253
+
254
+ export function parseOhHeadRefV1(value: unknown): OhHeadRefV1 | null {
255
+ const complete = parseOhHeadV1(value);
256
+ if (complete !== null) {
257
+ return { operationSha256: complete.operationSha256, sequence: complete.sequence };
258
+ }
259
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["operationSha256", "sequence"])) return null;
260
+ const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
261
+ const sequence = Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0
262
+ ? value.sequence as number : null;
263
+ return sequence !== null && (value.operationSha256 === null || operationSha256 !== null)
264
+ && ((sequence === 0) === (operationSha256 === null))
265
+ ? { operationSha256, sequence } : null;
266
+ }
267
+
268
+ function parseCapabilities(value: unknown): OhStoreCapabilitiesV1 | null {
269
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["changesSince", "dependencyClosureExport",
270
+ "exactSnapshots", "operationReplication", "semanticBundleCommit", "v", "wholeSpacePurge"])
271
+ || value.changesSince !== true || value.dependencyClosureExport !== true
272
+ || value.exactSnapshots !== true || typeof value.operationReplication !== "boolean"
273
+ || value.semanticBundleCommit !== true || value.v !== 1
274
+ || typeof value.wholeSpacePurge !== "boolean") return null;
275
+ return { changesSince: true, dependencyClosureExport: true, exactSnapshots: true,
276
+ operationReplication: value.operationReplication, semanticBundleCommit: true, v: 1,
277
+ wholeSpacePurge: value.wholeSpacePurge };
278
+ }
279
+
280
+ type OhStoreProfileInputV1 = Omit<OhStoreProfileV1, "profileSha256">;
281
+
282
+ export function createOhStoreProfileV1(input: OhStoreProfileInputV1): OhStoreProfileV1 {
283
+ if (!isPlainRecord(input) || !hasExactKeys(input, ["applicationProfileSha256", "capabilities",
284
+ "profileId", "profileKind", "v"]) || input.v !== 1) throw new TypeError("Invalid Oh store profile input.");
285
+ const profileId = safeCode(input.profileId);
286
+ const applicationProfileSha256 = input.applicationProfileSha256 === null
287
+ ? null : parseSha256Hex(input.applicationProfileSha256);
288
+ const capabilities = parseCapabilities(input.capabilities);
289
+ if (profileId === null || capabilities === null
290
+ || (input.applicationProfileSha256 !== null && applicationProfileSha256 === null)
291
+ || (input.profileKind !== "canonical" && input.profileKind !== "working")) {
292
+ throw new TypeError("Invalid Oh store profile input.");
293
+ }
294
+ if (input.profileKind === "working"
295
+ && (capabilities.operationReplication || !capabilities.wholeSpacePurge)) {
296
+ throw new OhProfileError("A working profile must disable operation replication and permit whole-space purge.");
297
+ }
298
+ if (input.profileKind === "canonical" && capabilities.wholeSpacePurge) {
299
+ throw new OhProfileError("A canonical profile cannot permit whole-space purge.");
300
+ }
301
+ const payload = { applicationProfileSha256, capabilities: Object.freeze(capabilities), profileId,
302
+ profileKind: input.profileKind, v: 1 as const };
303
+ return Object.freeze({ ...payload, profileSha256: canonicalSha256(payload) });
304
+ }
305
+
306
+ export function parseOhStoreProfileV1(value: unknown): OhStoreProfileV1 | null {
307
+ if (!isPlainRecord(value) || !Object.hasOwn(value, "profileSha256")) return null;
308
+ const digest = parseSha256Hex(value.profileSha256);
309
+ const { profileSha256: _profileSha256, ...input } = value;
310
+ try {
311
+ const created = createOhStoreProfileV1(input as unknown as OhStoreProfileInputV1);
312
+ return digest !== null && created.profileSha256 === digest ? created : null;
313
+ } catch { return null; }
314
+ }
315
+
316
+ export function createOhStoreBindingV1(input: Readonly<{
317
+ profile: OhStoreProfileV1;
318
+ realmId: string;
319
+ spaceId: string;
320
+ v: 1;
321
+ }>): OhStoreBindingV1 {
322
+ const profile = parseOhStoreProfileV1(input.profile);
323
+ const realmId = safeCode(input.realmId);
324
+ const spaceId = safeCode(input.spaceId);
325
+ if (input.v !== 1 || profile === null || realmId === null || spaceId === null) {
326
+ throw new TypeError("Invalid Oh store binding input.");
327
+ }
328
+ const payload = { contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
329
+ profile, realmId, spaceId, v: 1 as const };
330
+ return Object.freeze({ ...payload, bindingSha256: canonicalSha256(payload) });
331
+ }
332
+
333
+ export function parseOhStoreBindingV1(value: unknown): OhStoreBindingV1 | null {
334
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["bindingSha256", "contractSha256",
335
+ "profile", "realmId", "spaceId", "v"]) || value.v !== 1
336
+ || value.contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256) return null;
337
+ const bindingSha256 = parseSha256Hex(value.bindingSha256);
338
+ const profile = parseOhStoreProfileV1(value.profile);
339
+ try {
340
+ if (bindingSha256 === null || profile === null) return null;
341
+ const created = createOhStoreBindingV1({ profile, realmId: value.realmId as string,
342
+ spaceId: value.spaceId as string, v: 1 });
343
+ return created.bindingSha256 === bindingSha256 ? created : null;
344
+ } catch { return null; }
345
+ }
346
+
347
+ function sortedRecords(records: Iterable<KnowledgeGraphRecordV1>): readonly KnowledgeGraphRecordV1[] {
348
+ return [...records].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
349
+ }
350
+
351
+ function verifyDependencies(records: ReadonlyMap<string, KnowledgeGraphRecordV1>): void {
352
+ for (const record of records.values()) {
353
+ for (const dependency of record.dependencies) {
354
+ if (!records.has(dependency)) throw new OhDependencyError(`Missing dependency ${dependency} for ${record.key}.`);
355
+ }
356
+ }
357
+ }
358
+
359
+ export function replayOhOperationsV1(
360
+ spaceId: string,
361
+ values: readonly OhOperationV1[],
362
+ maximumRecords: number = OH_GRAPH_LIMITS_V1.recordsPerSnapshot,
363
+ ): OhSnapshotV1 {
364
+ const parsedSpaceId = safeCode(spaceId);
365
+ if (parsedSpaceId === null || !Number.isSafeInteger(maximumRecords) || maximumRecords < 1
366
+ || maximumRecords > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
367
+ throw new TypeError("Invalid operation replay input.");
368
+ }
369
+ const records = new Map<string, KnowledgeGraphRecordV1>();
370
+ const operationIds = new Set<string>();
371
+ let head = emptyOhHeadV1();
372
+ for (const value of values) {
373
+ const operation = parseOhOperationV1(value);
374
+ if (operation === null || operation.spaceId !== parsedSpaceId
375
+ || operation.sequence !== head.sequence + 1
376
+ || operation.parentOperationSha256 !== head.operationSha256
377
+ || operationIds.has(operation.operationId)) {
378
+ throw new OhIntegrityError("Operation replay chain is broken.");
379
+ }
380
+ operationIds.add(operation.operationId);
381
+ for (const change of operation.changes) {
382
+ if (change.kind === "put") records.set(change.record.key, change.record);
383
+ else {
384
+ const prior = records.get(change.key);
385
+ if (prior?.recordSha256 !== change.priorSha256) {
386
+ throw new OhIntegrityError("Replay tombstone does not match its prior record.");
387
+ }
388
+ records.delete(change.key);
389
+ }
390
+ }
391
+ if (records.size > maximumRecords) throw new RangeError("Operation replay exceeds its record bound.");
392
+ verifyDependencies(records);
393
+ const refs = sortedRecords(records.values()).map(knowledgeGraphRecordRefV1);
394
+ const recordsSha256 = canonicalSha256(refs);
395
+ const graphRevisionSha256 = graphRevisionSha256V1({ changes: operation.changes,
396
+ operationId: operation.operationId, parentGraphRevisionSha256: head.graphRevisionSha256,
397
+ recordsSha256, revision: operation.sequence });
398
+ if (recordsSha256 !== operation.recordsSha256
399
+ || graphRevisionSha256 !== operation.graphRevisionSha256) {
400
+ throw new OhIntegrityError("Replay does not reproduce an operation head.");
401
+ }
402
+ head = { generation: operation.sequence, graphRevisionSha256,
403
+ operationSha256: operation.operationSha256, recordsSha256,
404
+ sequence: operation.sequence, v: 1 };
405
+ }
406
+ return { head, records: sortedRecords(records.values()), v: 1 };
407
+ }
408
+
409
+ export function transitionOhSnapshotV1(input: Readonly<{
410
+ actorId: string;
411
+ changes: readonly KnowledgeGraphChangeV1[];
412
+ instant: string;
413
+ operationId: string;
414
+ snapshot: OhSnapshotV1;
415
+ spaceId: string;
416
+ }>): Readonly<{ operation: OhOperationV1; snapshot: OhSnapshotV1 }> {
417
+ const actorId = safeCode(input.actorId);
418
+ const operationId = safeCode(input.operationId);
419
+ const spaceId = safeCode(input.spaceId);
420
+ const instant = parseCanonicalInstantV1(input.instant);
421
+ const changes = canonicalKnowledgeGraphChangesV1(input.changes);
422
+ if (actorId === null || operationId === null || spaceId === null || instant === null
423
+ || changes.length === 0 || changes.length > OH_GRAPH_LIMITS_V1.changesPerOperation) {
424
+ throw new TypeError("Invalid graph transition input.");
425
+ }
426
+ const head = parseOhHeadV1(input.snapshot.head);
427
+ if (input.snapshot.v !== 1 || head === null || !Array.isArray(input.snapshot.records)) {
428
+ throw new OhIntegrityError("The transition snapshot is invalid.");
429
+ }
430
+ const records = new Map<string, KnowledgeGraphRecordV1>();
431
+ for (const value of input.snapshot.records) {
432
+ const record = parseKnowledgeGraphRecordV1(value);
433
+ if (record === null || records.has(record.key)) {
434
+ throw new OhIntegrityError("The transition snapshot contains an invalid record.");
435
+ }
436
+ records.set(record.key, record);
437
+ }
438
+ verifyDependencies(records);
439
+ const priorRecordsSha256 = canonicalSha256(sortedRecords(records.values()).map(knowledgeGraphRecordRefV1));
440
+ if (priorRecordsSha256 !== head.recordsSha256) {
441
+ throw new OhIntegrityError("The transition snapshot does not reproduce its head.");
442
+ }
443
+ for (const change of changes) {
444
+ if (change.kind === "put") records.set(change.record.key, change.record);
445
+ else {
446
+ const prior = records.get(change.key);
447
+ if (prior === undefined || prior.recordSha256 !== change.priorSha256) {
448
+ throw new OhConflictError(`The prior digest for ${change.key} does not match the snapshot.`);
449
+ }
450
+ records.delete(change.key);
451
+ }
452
+ }
453
+ if (records.size > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
454
+ throw new RangeError("Graph transition exceeds its record snapshot limit.");
455
+ }
456
+ verifyDependencies(records);
457
+ const nextRecords = sortedRecords(records.values());
458
+ const recordsSha256 = canonicalSha256(nextRecords.map(knowledgeGraphRecordRefV1));
459
+ const graphRevisionSha256 = graphRevisionSha256V1({ changes, operationId,
460
+ parentGraphRevisionSha256: head.graphRevisionSha256, recordsSha256,
461
+ revision: head.sequence + 1 });
462
+ const operation = createOhOperationV1({ actorId, changes,
463
+ contractId: OH_CONTRACT_MANIFEST_V1.contractId, graphRevisionSha256, instant,
464
+ operationId, parentOperationSha256: head.operationSha256,
465
+ recordsSha256, sequence: head.sequence + 1, spaceId, v: 1 });
466
+ const nextHead: OhHeadV1 = { generation: operation.sequence, graphRevisionSha256,
467
+ operationSha256: operation.operationSha256, recordsSha256,
468
+ sequence: operation.sequence, v: 1 };
469
+ return { operation, snapshot: { head: nextHead, records: nextRecords, v: 1 } };
470
+ }
471
+
472
+ function normalizeRoots(values: readonly string[]): readonly string[] {
473
+ if (!Array.isArray(values) || values.length < 1 || values.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
474
+ throw new RangeError(`A dependency closure needs 1 through ${OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots} roots.`);
475
+ }
476
+ const roots = values.map((value) => safeCode(value, 512));
477
+ if (roots.some((value) => value === null)) throw new TypeError("Invalid dependency closure root.");
478
+ const sorted = [...roots as string[]].sort();
479
+ if (sorted.some((value, index) => index > 0 && sorted[index - 1] === value)) {
480
+ throw new TypeError("Dependency closure roots must be unique.");
481
+ }
482
+ return sorted;
483
+ }
484
+
485
+ function closureRecords(
486
+ available: ReadonlyMap<string, KnowledgeGraphRecordV1>,
487
+ roots: readonly string[],
488
+ maximumRecords: number,
489
+ maximumBytes: number = OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes - 64 * 1024,
490
+ ): readonly KnowledgeGraphRecordV1[] {
491
+ const selected = new Map<string, KnowledgeGraphRecordV1>();
492
+ const pending = [...roots];
493
+ let selectedBytes = 0;
494
+ while (pending.length > 0) {
495
+ const key = pending.pop() as string;
496
+ if (selected.has(key)) continue;
497
+ const record = available.get(key);
498
+ if (record === undefined) throw new OhDependencyError(`Dependency closure record ${key} is missing.`);
499
+ selectedBytes += Buffer.byteLength(canonicalJson(record), "utf8") + 1;
500
+ if (selectedBytes > maximumBytes) throw new RangeError("Dependency closure exceeds its canonical byte bound.");
501
+ selected.set(key, record);
502
+ if (selected.size > maximumRecords) throw new RangeError("Dependency closure exceeds its record bound.");
503
+ pending.push(...record.dependencies);
504
+ }
505
+ return sortedRecords(selected.values());
506
+ }
507
+
508
+ export function createOhDependencyClosureV1(input: Readonly<{
509
+ binding: OhStoreBindingV1;
510
+ maximumRecords?: number;
511
+ roots: readonly string[];
512
+ snapshot: OhSnapshotV1;
513
+ }>): OhDependencyClosureV1 {
514
+ const binding = parseOhStoreBindingV1(input.binding);
515
+ const head = parseOhHeadV1(input.snapshot.head);
516
+ const maximumRecords = input.maximumRecords ?? OH_DEPENDENCY_CLOSURE_LIMITS_V1.records;
517
+ if (binding === null || head === null || !Number.isSafeInteger(maximumRecords)
518
+ || maximumRecords < 1 || maximumRecords > OH_DEPENDENCY_CLOSURE_LIMITS_V1.records) {
519
+ throw new TypeError("Invalid dependency closure input.");
520
+ }
521
+ const roots = normalizeRoots(input.roots);
522
+ const available = new Map<string, KnowledgeGraphRecordV1>();
523
+ for (const value of input.snapshot.records) {
524
+ const record = parseKnowledgeGraphRecordV1(value);
525
+ if (record === null || available.has(record.key)) throw new OhIntegrityError("Snapshot contains an invalid record.");
526
+ available.set(record.key, record);
527
+ }
528
+ const recordsSha256 = canonicalSha256(sortedRecords(available.values()).map(knowledgeGraphRecordRefV1));
529
+ if (recordsSha256 !== head.recordsSha256) throw new OhIntegrityError("Snapshot records do not reproduce its head.");
530
+ const records = closureRecords(available, roots, maximumRecords);
531
+ const payload = { binding, head, records, roots, v: 1 as const };
532
+ const closure = { ...payload, closureSha256: canonicalSha256(payload) };
533
+ if (Buffer.byteLength(canonicalJson(closure), "utf8") > OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes) {
534
+ throw new RangeError("Dependency closure exceeds its canonical byte bound.");
535
+ }
536
+ return Object.freeze(closure);
537
+ }
538
+
539
+ export function parseOhDependencyClosureV1(value: unknown): OhDependencyClosureV1 | null {
540
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["binding", "closureSha256", "head",
541
+ "records", "roots", "v"]) || value.v !== 1 || !Array.isArray(value.records)
542
+ || !Array.isArray(value.roots) || value.records.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.records) return null;
543
+ const binding = parseOhStoreBindingV1(value.binding);
544
+ const head = parseOhHeadV1(value.head);
545
+ const closureSha256 = parseSha256Hex(value.closureSha256);
546
+ if (binding === null || head === null || closureSha256 === null) return null;
547
+ const records = new Map<string, KnowledgeGraphRecordV1>();
548
+ for (const item of value.records) {
549
+ const record = parseKnowledgeGraphRecordV1(item);
550
+ if (record === null || records.has(record.key)) return null;
551
+ records.set(record.key, record);
552
+ }
553
+ try {
554
+ const roots = normalizeRoots(value.roots as string[]);
555
+ if (canonicalJson(roots) !== canonicalJson(value.roots)) return null;
556
+ const exact = closureRecords(records, roots, OH_DEPENDENCY_CLOSURE_LIMITS_V1.records);
557
+ if (canonicalJson(exact) !== canonicalJson(value.records)) return null;
558
+ const payload = { binding, head, records: exact, roots, v: 1 as const };
559
+ const parsed = { ...payload, closureSha256 };
560
+ return canonicalSha256(payload) === closureSha256
561
+ && Buffer.byteLength(canonicalJson(parsed), "utf8") <= OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes
562
+ ? Object.freeze(parsed) : null;
563
+ } catch { return null; }
564
+ }
565
+
566
+ export function verifyOhDependencyClosureV1(value: unknown):
567
+ | Readonly<{ closure: OhDependencyClosureV1; ok: true }>
568
+ | Readonly<{ ok: false; reason: "invalid-closure" }> {
569
+ const closure = parseOhDependencyClosureV1(value);
570
+ return closure === null ? { ok: false, reason: "invalid-closure" } : { closure, ok: true };
571
+ }
572
+
573
+ /**
574
+ * Strong adoption check. Unlike structural self-verification, this also binds
575
+ * the capsule to the exact store binding and head selected by trusted host code.
576
+ */
577
+ export function verifyOhDependencyClosureAgainstV1(value: unknown, expected: Readonly<{
578
+ binding: OhStoreBindingV1;
579
+ head: OhHeadV1;
580
+ }>):
581
+ | Readonly<{ closure: OhDependencyClosureV1; ok: true; verification: "expected-authority-and-head" }>
582
+ | Readonly<{ ok: false; reason: "binding-mismatch" | "head-mismatch" | "invalid-closure" | "invalid-expectation" }> {
583
+ const binding = parseOhStoreBindingV1(expected.binding);
584
+ const head = parseOhHeadV1(expected.head);
585
+ if (binding === null || head === null) return { ok: false, reason: "invalid-expectation" };
586
+ const closure = parseOhDependencyClosureV1(value);
587
+ if (closure === null) return { ok: false, reason: "invalid-closure" };
588
+ if (closure.binding.bindingSha256 !== binding.bindingSha256) return { ok: false, reason: "binding-mismatch" };
589
+ if (canonicalJson(closure.head) !== canonicalJson(head)) return { ok: false, reason: "head-mismatch" };
590
+ return { closure, ok: true, verification: "expected-authority-and-head" };
591
+ }
592
+
593
+ export function createOhSpacePurgeReceiptV1(input: Readonly<{
594
+ binding: OhStoreBindingV1;
595
+ priorHead: OhHeadV1;
596
+ purgedAt: string;
597
+ }>): OhSpacePurgeReceiptV1 {
598
+ const binding = parseOhStoreBindingV1(input.binding);
599
+ const priorHead = parseOhHeadV1(input.priorHead);
600
+ const purgedAt = parseCanonicalInstantV1(input.purgedAt);
601
+ if (binding === null || priorHead === null || purgedAt === null
602
+ || binding.profile.profileKind !== "working"
603
+ || !binding.profile.capabilities.wholeSpacePurge) {
604
+ throw new OhProfileError("Only a bound working realm can produce a purge receipt.");
605
+ }
606
+ const payload = { bindingSha256: binding.bindingSha256, priorHead, purgedAt,
607
+ spaceId: binding.spaceId, v: 1 as const };
608
+ return Object.freeze({ ...payload, receiptSha256: canonicalSha256(payload) });
609
+ }
610
+
611
+ export function parseOhSpacePurgeReceiptV1(value: unknown): OhSpacePurgeReceiptV1 | null {
612
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["bindingSha256", "priorHead", "purgedAt",
613
+ "receiptSha256", "spaceId", "v"]) || value.v !== 1) return null;
614
+ const bindingSha256 = parseSha256Hex(value.bindingSha256);
615
+ const priorHead = parseOhHeadV1(value.priorHead);
616
+ const purgedAt = parseCanonicalInstantV1(value.purgedAt);
617
+ const receiptSha256 = parseSha256Hex(value.receiptSha256);
618
+ const spaceId = safeCode(value.spaceId);
619
+ if (bindingSha256 === null || priorHead === null || purgedAt === null
620
+ || receiptSha256 === null || spaceId === null) return null;
621
+ const payload = { bindingSha256, priorHead, purgedAt, spaceId, v: 1 as const };
622
+ return canonicalSha256(payload) === receiptSha256
623
+ ? Object.freeze({ ...payload, receiptSha256 }) : null;
624
+ }
625
+
626
+ export type OhSemanticBundleV1 = Readonly<{
627
+ actorId: string;
628
+ expectedHead: Pick<OhHeadV1, "generation" | "operationSha256">;
629
+ instant: string | null;
630
+ operationId: string;
631
+ puts: readonly Readonly<{
632
+ dependencies: readonly string[];
633
+ key: string;
634
+ kind: KnowledgeGraphRecordKindV1;
635
+ v: 1;
636
+ value: unknown;
637
+ }>[];
638
+ tombstones: readonly Readonly<{
639
+ key: string;
640
+ priorSha256: Sha256Hex;
641
+ v: 1;
642
+ }>[];
643
+ v: 1;
644
+ }>;
645
+
646
+ /** A strict model-facing ingress: every put must have a registered codec. */
647
+ export class OhSemanticBundleIngressV1 {
648
+ readonly #codecs: OhRecordCodecRegistry;
649
+ readonly #store: OhStoreV1;
650
+
651
+ constructor(store: OhStoreV1, codecs: OhRecordCodecRegistry) {
652
+ this.#store = store;
653
+ this.#codecs = codecs.seal();
654
+ }
655
+
656
+ async commit(value: unknown): Promise<OhOperationV1> {
657
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["actorId", "expectedHead", "instant",
658
+ "operationId", "puts", "tombstones", "v"]) || value.v !== 1
659
+ || !Array.isArray(value.puts) || !Array.isArray(value.tombstones)
660
+ || value.puts.length + value.tombstones.length < 1
661
+ || value.puts.length + value.tombstones.length > OH_GRAPH_LIMITS_V1.changesPerOperation) {
662
+ throw new TypeError("Invalid semantic bundle.");
663
+ }
664
+ const actorId = safeCode(value.actorId);
665
+ const operationId = safeCode(value.operationId);
666
+ const expected = value.expectedHead;
667
+ const instant = value.instant === null ? undefined : parseCanonicalInstantV1(value.instant);
668
+ if (actorId === null || operationId === null || !isPlainRecord(expected)
669
+ || !hasExactKeys(expected, ["generation", "operationSha256"])
670
+ || !Number.isSafeInteger(expected.generation) || (expected.generation as number) < 0
671
+ || (expected.operationSha256 !== null && parseSha256Hex(expected.operationSha256) === null)
672
+ || (((expected.generation as number) === 0) !== (expected.operationSha256 === null))
673
+ || (value.instant !== null && instant === null)) throw new TypeError("Invalid semantic bundle identity.");
674
+ const changes: KnowledgeGraphChangeV1[] = [];
675
+ for (const item of value.puts) {
676
+ if (!isPlainRecord(item) || !hasExactKeys(item, ["dependencies", "key", "kind", "v", "value"])
677
+ || item.v !== 1 || !Array.isArray(item.dependencies)
678
+ || !OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.some((kind) => kind === item.kind)) {
679
+ throw new TypeError("Invalid semantic bundle put.");
680
+ }
681
+ const parsed = this.#codecs.parseRequired(item.kind as KnowledgeGraphRecordKindV1, item.value);
682
+ if (parsed === null) throw new TypeError(`The ${String(item.kind)} codec rejected a semantic value.`);
683
+ const record = createKnowledgeGraphRecordV1({ dependencies: item.dependencies as string[],
684
+ key: item.key as string, kind: item.kind as KnowledgeGraphRecordKindV1, v: 1, value: parsed });
685
+ changes.push({ kind: "put", record, v: 1 });
686
+ }
687
+ for (const item of value.tombstones) {
688
+ if (!isPlainRecord(item) || !hasExactKeys(item, ["key", "priorSha256", "v"]) || item.v !== 1) {
689
+ throw new TypeError("Invalid semantic bundle tombstone.");
690
+ }
691
+ const priorSha256 = parseSha256Hex(item.priorSha256);
692
+ if (typeof item.key !== "string" || priorSha256 === null) throw new TypeError("Invalid semantic bundle tombstone.");
693
+ changes.push({ key: item.key, kind: "tombstone", priorSha256, v: 1 });
694
+ }
695
+ const canonical = canonicalKnowledgeGraphChangesV1(changes);
696
+ return await this.#store.commit({ actorId, changes: canonical,
697
+ expectedHead: { generation: expected.generation as number,
698
+ operationSha256: expected.operationSha256 as Sha256Hex | null },
699
+ ...(typeof instant === "string" ? { instant } : {}), operationId });
700
+ }
701
+ }