@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
@@ -0,0 +1,573 @@
1
+ import {
2
+ boundedText,
3
+ canonicalJson,
4
+ canonicalSha256,
5
+ hasExactKeys,
6
+ isPlainRecord,
7
+ orderedUnique,
8
+ parseCanonicalInstantV1,
9
+ parseSha256Hex,
10
+ safeCode,
11
+ sha256Hex,
12
+ sortUnique,
13
+ type Sha256Hex,
14
+ } from "./canonical";
15
+
16
+ export const OH_ONTOLOGY_VERSION_V1 = "1.0.0" as const;
17
+ export const OH_CONTRACT_ID_V1 = "oh.ontology.v1" as const;
18
+
19
+ export const OH_KNOWLEDGE_LIMITS_V1 = Object.freeze({
20
+ dimensions: 64,
21
+ listValues: 256,
22
+ qualifiers: 128,
23
+ statementBytes: 256 * 1024,
24
+ textBytes: 64 * 1024,
25
+ });
26
+
27
+ export const OH_KNOWLEDGE_KERNEL_CONCEPTS_V1 = [
28
+ { code: "entity", description: "A stable identity anchor for something that can be referred to.", label: "Entity" },
29
+ { code: "statement", description: "An immutable proposition with a subject, predicate, object, and qualifiers.", label: "Statement" },
30
+ { code: "assertion", description: "An attributable stance toward a statement.", label: "Assertion" },
31
+ { code: "evidence", description: "A typed account of how an observation bears on an assertion.", label: "Evidence" },
32
+ { code: "context", description: "The scenario and dimensions in which knowledge applies.", label: "Context" },
33
+ { code: "inquiry", description: "A question and its durable investigation trail.", label: "Inquiry" },
34
+ { code: "projection", description: "A reproducible view derived from exact knowledge.", label: "Projection" },
35
+ ] as const;
36
+
37
+ export type KnowledgeOntologyIssueCode =
38
+ | "dependency-missing"
39
+ | "digest-mismatch"
40
+ | "invalid-input"
41
+ | "limit-exceeded"
42
+ | "noncanonical-input";
43
+
44
+ export type KnowledgeOntologyResult<T> =
45
+ | Readonly<{ ok: true; value: T }>
46
+ | Readonly<{ error: Readonly<{ code: KnowledgeOntologyIssueCode; field: string }>; ok: false }>;
47
+
48
+ function success<T>(value: T): KnowledgeOntologyResult<T> { return { ok: true, value }; }
49
+ function failure<T>(field: string, code: KnowledgeOntologyIssueCode = "invalid-input"): KnowledgeOntologyResult<T> {
50
+ return { error: { code, field }, ok: false };
51
+ }
52
+
53
+ declare const entityIdBrand: unique symbol;
54
+ declare const assertionIdBrand: unique symbol;
55
+ declare const evidenceIdBrand: unique symbol;
56
+ declare const inquiryIdBrand: unique symbol;
57
+ export type KnowledgeEntityId = string & { readonly [entityIdBrand]: "KnowledgeEntityId" };
58
+ export type KnowledgeAssertionId = string & { readonly [assertionIdBrand]: "KnowledgeAssertionId" };
59
+ export type KnowledgeEvidenceId = string & { readonly [evidenceIdBrand]: "KnowledgeEvidenceId" };
60
+ export type KnowledgeInquiryId = string & { readonly [inquiryIdBrand]: "KnowledgeInquiryId" };
61
+
62
+ function parseOpaqueId<T extends string>(value: unknown, prefix: string): T | null {
63
+ return typeof value === "string" && new RegExp(`^${prefix}[a-z0-9]{24}$`, "u").test(value)
64
+ ? value as T : null;
65
+ }
66
+ export function parseKnowledgeEntityId(value: unknown): KnowledgeEntityId | null {
67
+ return parseOpaqueId<KnowledgeEntityId>(value, "kent_");
68
+ }
69
+ export function parseKnowledgeAssertionId(value: unknown): KnowledgeAssertionId | null {
70
+ return parseOpaqueId<KnowledgeAssertionId>(value, "kast_");
71
+ }
72
+ export function parseKnowledgeEvidenceId(value: unknown): KnowledgeEvidenceId | null {
73
+ return parseOpaqueId<KnowledgeEvidenceId>(value, "kevd_");
74
+ }
75
+ export function parseKnowledgeInquiryId(value: unknown): KnowledgeInquiryId | null {
76
+ return parseOpaqueId<KnowledgeInquiryId>(value, "kinq_");
77
+ }
78
+
79
+ export const OH_KNOWLEDGE_ENTITY_STATES_V1 = ["active", "quarantined", "redirected", "tombstoned"] as const;
80
+ export type KnowledgeEntityStateV1 = (typeof OH_KNOWLEDGE_ENTITY_STATES_V1)[number];
81
+ export type KnowledgeEntityV1 = Readonly<{
82
+ entityId: KnowledgeEntityId;
83
+ identityOperationId: string;
84
+ identityRevision: number;
85
+ redirectEntityId: KnowledgeEntityId | null;
86
+ state: KnowledgeEntityStateV1;
87
+ v: 1;
88
+ }>;
89
+
90
+ export function parseKnowledgeEntityV1(value: unknown): KnowledgeOntologyResult<KnowledgeEntityV1> {
91
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["entityId", "identityOperationId", "identityRevision",
92
+ "redirectEntityId", "state", "v"]) || value.v !== 1) return failure("entity");
93
+ const entityId = parseKnowledgeEntityId(value.entityId);
94
+ const identityOperationId = safeCode(value.identityOperationId);
95
+ const identityRevision = Number.isSafeInteger(value.identityRevision) && (value.identityRevision as number) > 0
96
+ ? value.identityRevision as number : null;
97
+ const redirectEntityId = value.redirectEntityId === null ? null : parseKnowledgeEntityId(value.redirectEntityId);
98
+ const state = OH_KNOWLEDGE_ENTITY_STATES_V1.find((candidate) => candidate === value.state);
99
+ return entityId !== null && identityOperationId !== null && identityRevision !== null
100
+ && (value.redirectEntityId === null || redirectEntityId !== null) && state !== undefined
101
+ && ((state === "redirected") === (redirectEntityId !== null)) && redirectEntityId !== entityId
102
+ ? success({ entityId, identityOperationId, identityRevision, redirectEntityId, state, v: 1 })
103
+ : failure("entity");
104
+ }
105
+
106
+ export type KnowledgeSchemaRefV1 = Readonly<{
107
+ code: string;
108
+ namespace: string;
109
+ revision: number;
110
+ schemaSha256: Sha256Hex;
111
+ v: 1;
112
+ }>;
113
+
114
+ export function parseKnowledgeSchemaRefV1(value: unknown): KnowledgeOntologyResult<KnowledgeSchemaRefV1> {
115
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["code", "namespace", "revision", "schemaSha256", "v"])
116
+ || value.v !== 1) return failure("schemaRef");
117
+ const code = safeCode(value.code);
118
+ const namespace = safeCode(value.namespace);
119
+ const revision = Number.isSafeInteger(value.revision) && (value.revision as number) > 0
120
+ ? value.revision as number : null;
121
+ const schemaSha256 = parseSha256Hex(value.schemaSha256);
122
+ return code !== null && namespace !== null && revision !== null && schemaSha256 !== null
123
+ ? success({ code, namespace, revision, schemaSha256, v: 1 }) : failure("schemaRef");
124
+ }
125
+
126
+ export type KnowledgeValueV1 =
127
+ | Readonly<{ entityId: KnowledgeEntityId; kind: "entity"; v: 1 }>
128
+ | Readonly<{ kind: "text"; language: string; text: string; v: 1 }>
129
+ | Readonly<{ kind: "string"; v: 1; value: string }>
130
+ | Readonly<{ kind: "boolean"; v: 1; value: boolean }>
131
+ | Readonly<{ kind: "integer" | "decimal"; v: 1; value: string }>
132
+ | Readonly<{ kind: "uri"; uri: string; v: 1 }>
133
+ | Readonly<{ kind: "list" | "set"; v: 1; values: readonly KnowledgeValueV1[] }>
134
+ | Readonly<{
135
+ canonicalizerSha256: Sha256Hex;
136
+ canonicalValue: string;
137
+ kind: "extension";
138
+ mediaType: string;
139
+ schema: KnowledgeSchemaRefV1;
140
+ v: 1;
141
+ valueSha256: Sha256Hex;
142
+ }>;
143
+
144
+ const INTEGER = /^(?:0|-[1-9][0-9]*|[1-9][0-9]*)$/u;
145
+ const DECIMAL = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*[1-9])?$/u;
146
+
147
+ function parseKnowledgeValueInternal(value: unknown, depth: number): KnowledgeValueV1 | null {
148
+ if (!isPlainRecord(value) || value.v !== 1 || depth > 8) return null;
149
+ switch (value.kind) {
150
+ case "entity": {
151
+ if (!hasExactKeys(value, ["entityId", "kind", "v"])) return null;
152
+ const entityId = parseKnowledgeEntityId(value.entityId);
153
+ return entityId === null ? null : { entityId, kind: "entity", v: 1 };
154
+ }
155
+ case "text": {
156
+ if (!hasExactKeys(value, ["kind", "language", "text", "v"])) return null;
157
+ const language = typeof value.language === "string"
158
+ && /^(?:und|[a-z]{2,3}(?:-[a-z0-9]{2,8})*)$/u.test(value.language)
159
+ ? value.language : null;
160
+ const text = boundedText(value.text);
161
+ return language !== null && text !== null ? { kind: "text", language, text, v: 1 } : null;
162
+ }
163
+ case "string": {
164
+ const parsed = boundedText(value.value);
165
+ return hasExactKeys(value, ["kind", "v", "value"]) && parsed !== null
166
+ ? { kind: "string", v: 1, value: parsed } : null;
167
+ }
168
+ case "boolean":
169
+ return hasExactKeys(value, ["kind", "v", "value"]) && typeof value.value === "boolean"
170
+ ? { kind: "boolean", v: 1, value: value.value } : null;
171
+ case "integer":
172
+ case "decimal": {
173
+ const valid = typeof value.value === "string" && value.value.length <= 1024
174
+ && (value.kind === "integer" ? INTEGER.test(value.value) : DECIMAL.test(value.value) && value.value !== "-0");
175
+ return hasExactKeys(value, ["kind", "v", "value"]) && valid
176
+ ? { kind: value.kind, v: 1, value: value.value as string } : null;
177
+ }
178
+ case "uri": {
179
+ if (!hasExactKeys(value, ["kind", "uri", "v"]) || typeof value.uri !== "string" || value.uri.length > 4096) return null;
180
+ try {
181
+ const url = new URL(value.uri);
182
+ return url.href === value.uri && url.username === "" && url.password === ""
183
+ && !["data:", "file:", "javascript:"].includes(url.protocol)
184
+ ? { kind: "uri", uri: value.uri, v: 1 } : null;
185
+ } catch { return null; }
186
+ }
187
+ case "list":
188
+ case "set": {
189
+ if (!hasExactKeys(value, ["kind", "v", "values"]) || !Array.isArray(value.values)
190
+ || value.values.length > OH_KNOWLEDGE_LIMITS_V1.listValues) return null;
191
+ const values: KnowledgeValueV1[] = [];
192
+ for (const item of value.values) {
193
+ const parsed = parseKnowledgeValueInternal(item, depth + 1);
194
+ if (parsed === null) return null;
195
+ values.push(parsed);
196
+ }
197
+ if (value.kind === "set" && !orderedUnique(values, canonicalJson)) return null;
198
+ return { kind: value.kind, v: 1, values };
199
+ }
200
+ case "extension": {
201
+ if (!hasExactKeys(value, ["canonicalizerSha256", "canonicalValue", "kind", "mediaType", "schema", "v", "valueSha256"])) return null;
202
+ const canonicalizerSha256 = parseSha256Hex(value.canonicalizerSha256);
203
+ const canonicalValue = boundedText(value.canonicalValue, 64 * 1024);
204
+ const mediaType = typeof value.mediaType === "string" && /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/u.test(value.mediaType)
205
+ ? value.mediaType : null;
206
+ const schema = parseKnowledgeSchemaRefV1(value.schema);
207
+ const valueSha256 = parseSha256Hex(value.valueSha256);
208
+ return canonicalizerSha256 !== null && canonicalValue !== null && mediaType !== null && schema.ok && valueSha256 !== null
209
+ ? { canonicalizerSha256, canonicalValue, kind: "extension", mediaType, schema: schema.value, v: 1, valueSha256 }
210
+ : null;
211
+ }
212
+ default: return null;
213
+ }
214
+ }
215
+
216
+ export function parseKnowledgeValueV1(value: unknown): KnowledgeOntologyResult<KnowledgeValueV1> {
217
+ const parsed = parseKnowledgeValueInternal(value, 0);
218
+ return parsed === null ? failure("value") : success(parsed);
219
+ }
220
+
221
+ export function verifyKnowledgeValueV1(value: KnowledgeValueV1): KnowledgeOntologyResult<KnowledgeValueV1> {
222
+ const parsed = parseKnowledgeValueV1(value);
223
+ if (!parsed.ok) return parsed;
224
+ if (parsed.value.kind === "extension" && sha256Hex(parsed.value.canonicalValue) !== parsed.value.valueSha256) {
225
+ return failure("valueSha256", "digest-mismatch");
226
+ }
227
+ if (parsed.value.kind === "list" || parsed.value.kind === "set") {
228
+ for (const child of parsed.value.values) {
229
+ const verified = verifyKnowledgeValueV1(child);
230
+ if (!verified.ok) return verified;
231
+ }
232
+ }
233
+ return success(parsed.value);
234
+ }
235
+
236
+ export type KnowledgeDimensionV1 = Readonly<{ predicate: KnowledgeSchemaRefV1; v: 1; value: KnowledgeValueV1 }>;
237
+
238
+ function parseDimension(value: unknown): KnowledgeDimensionV1 | null {
239
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["predicate", "v", "value"]) || value.v !== 1) return null;
240
+ const predicate = parseKnowledgeSchemaRefV1(value.predicate);
241
+ const parsedValue = parseKnowledgeValueV1(value.value);
242
+ return predicate.ok && parsedValue.ok ? { predicate: predicate.value, v: 1, value: parsedValue.value } : null;
243
+ }
244
+
245
+ export type KnowledgeContextV1 = Readonly<{
246
+ contextSha256: Sha256Hex;
247
+ dimensions: readonly KnowledgeDimensionV1[];
248
+ scenario: "actual" | "counterfactual" | "hypothetical" | "planned";
249
+ v: 1;
250
+ }>;
251
+ export type KnowledgeContextInputV1 = Omit<KnowledgeContextV1, "contextSha256">;
252
+
253
+ export function createKnowledgeContextV1(input: KnowledgeContextInputV1): KnowledgeOntologyResult<KnowledgeContextV1> {
254
+ if (!isPlainRecord(input) || input.v !== 1 || !Array.isArray(input.dimensions)
255
+ || input.dimensions.length > OH_KNOWLEDGE_LIMITS_V1.dimensions
256
+ || !["actual", "counterfactual", "hypothetical", "planned"].includes(input.scenario)) return failure("context");
257
+ const dimensions: KnowledgeDimensionV1[] = [];
258
+ for (const item of input.dimensions) {
259
+ const parsed = parseDimension(item);
260
+ if (parsed === null) return failure("dimensions");
261
+ const verified = verifyKnowledgeValueV1(parsed.value);
262
+ if (!verified.ok) return verified;
263
+ dimensions.push(parsed);
264
+ }
265
+ let canonicalDimensions: readonly KnowledgeDimensionV1[];
266
+ try { canonicalDimensions = sortUnique(dimensions, canonicalJson); } catch { return failure("dimensions", "noncanonical-input"); }
267
+ const payload = { dimensions: canonicalDimensions, scenario: input.scenario, v: 1 as const };
268
+ return success({ ...payload, contextSha256: canonicalSha256(payload) });
269
+ }
270
+
271
+ export function parseKnowledgeContextV1(value: unknown): KnowledgeOntologyResult<KnowledgeContextV1> {
272
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["contextSha256", "dimensions", "scenario", "v"])) return failure("context");
273
+ const digest = parseSha256Hex(value.contextSha256);
274
+ if (digest === null) return failure("contextSha256");
275
+ const created = createKnowledgeContextV1({ dimensions: value.dimensions as KnowledgeDimensionV1[], scenario: value.scenario as KnowledgeContextInputV1["scenario"], v: value.v as 1 });
276
+ return created.ok && created.value.contextSha256 === digest && canonicalJson(created.value.dimensions) === canonicalJson(value.dimensions)
277
+ ? success({ ...created.value, contextSha256: digest }) : failure("contextSha256", "digest-mismatch");
278
+ }
279
+
280
+ export type KnowledgeStatementV1 = Readonly<{
281
+ object: KnowledgeValueV1;
282
+ predicate: KnowledgeSchemaRefV1;
283
+ qualifiers: readonly KnowledgeDimensionV1[];
284
+ statementSha256: Sha256Hex;
285
+ subject: KnowledgeEntityId;
286
+ v: 1;
287
+ }>;
288
+ export type KnowledgeStatementInputV1 = Omit<KnowledgeStatementV1, "statementSha256">;
289
+
290
+ export function createKnowledgeStatementV1(input: KnowledgeStatementInputV1): KnowledgeOntologyResult<KnowledgeStatementV1> {
291
+ const object = parseKnowledgeValueV1(input.object);
292
+ const predicate = parseKnowledgeSchemaRefV1(input.predicate);
293
+ const subject = parseKnowledgeEntityId(input.subject);
294
+ if (input.v !== 1 || !object.ok || !predicate.ok || subject === null || !Array.isArray(input.qualifiers)
295
+ || input.qualifiers.length > OH_KNOWLEDGE_LIMITS_V1.qualifiers) return failure("statement");
296
+ const verifiedObject = verifyKnowledgeValueV1(object.value);
297
+ if (!verifiedObject.ok) return verifiedObject;
298
+ const qualifiers: KnowledgeDimensionV1[] = [];
299
+ for (const item of input.qualifiers) {
300
+ const parsed = parseDimension(item);
301
+ if (parsed === null) return failure("qualifiers");
302
+ const verified = verifyKnowledgeValueV1(parsed.value);
303
+ if (!verified.ok) return verified;
304
+ qualifiers.push(parsed);
305
+ }
306
+ let canonicalQualifiers: readonly KnowledgeDimensionV1[];
307
+ try { canonicalQualifiers = sortUnique(qualifiers, canonicalJson); } catch { return failure("qualifiers", "noncanonical-input"); }
308
+ const payload = { object: object.value, predicate: predicate.value, qualifiers: canonicalQualifiers, subject, v: 1 as const };
309
+ if (Buffer.byteLength(canonicalJson(payload), "utf8") > OH_KNOWLEDGE_LIMITS_V1.statementBytes) return failure("statement", "limit-exceeded");
310
+ return success({ ...payload, statementSha256: canonicalSha256(payload) });
311
+ }
312
+
313
+ export function parseKnowledgeStatementV1(value: unknown): KnowledgeOntologyResult<KnowledgeStatementV1> {
314
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["object", "predicate", "qualifiers", "statementSha256", "subject", "v"])) return failure("statement");
315
+ const digest = parseSha256Hex(value.statementSha256);
316
+ const created = createKnowledgeStatementV1(value as unknown as KnowledgeStatementInputV1);
317
+ return digest !== null && created.ok && created.value.statementSha256 === digest
318
+ && canonicalJson(created.value.qualifiers) === canonicalJson(value.qualifiers)
319
+ ? success({ ...created.value, statementSha256: digest }) : failure("statementSha256", "digest-mismatch");
320
+ }
321
+
322
+ export type KnowledgeAgentRefV1 =
323
+ | Readonly<{ entityId: KnowledgeEntityId; kind: "entity"; v: 1 }>
324
+ | Readonly<{ kind: "model"; model: KnowledgeSchemaRefV1; receiptSha256: Sha256Hex; v: 1 }>
325
+ | Readonly<{ authority: KnowledgeSchemaRefV1; kind: "system"; receiptSha256: Sha256Hex; v: 1 }>;
326
+
327
+ function parseKnowledgeAgentRefV1(value: unknown): KnowledgeAgentRefV1 | null {
328
+ if (!isPlainRecord(value) || value.v !== 1) return null;
329
+ if (value.kind === "entity" && hasExactKeys(value, ["entityId", "kind", "v"])) {
330
+ const entityId = parseKnowledgeEntityId(value.entityId);
331
+ return entityId === null ? null : { entityId, kind: "entity", v: 1 };
332
+ }
333
+ if (value.kind === "model" && hasExactKeys(value, ["kind", "model", "receiptSha256", "v"])) {
334
+ const model = parseKnowledgeSchemaRefV1(value.model);
335
+ const receiptSha256 = parseSha256Hex(value.receiptSha256);
336
+ return model.ok && receiptSha256 !== null ? { kind: "model", model: model.value, receiptSha256, v: 1 } : null;
337
+ }
338
+ if (value.kind === "system" && hasExactKeys(value, ["authority", "kind", "receiptSha256", "v"])) {
339
+ const authority = parseKnowledgeSchemaRefV1(value.authority);
340
+ const receiptSha256 = parseSha256Hex(value.receiptSha256);
341
+ return authority.ok && receiptSha256 !== null
342
+ ? { authority: authority.value, kind: "system", receiptSha256, v: 1 } : null;
343
+ }
344
+ return null;
345
+ }
346
+
347
+ function parseDigestArray(value: unknown, maximum = 2048): readonly Sha256Hex[] | null {
348
+ if (!Array.isArray(value) || value.length > maximum) return null;
349
+ const digests = value.map(parseSha256Hex);
350
+ return digests.every((digest) => digest !== null) && orderedUnique(digests as Sha256Hex[], String)
351
+ ? digests as Sha256Hex[] : null;
352
+ }
353
+
354
+ export const OH_KNOWLEDGE_ACTIVITY_KINDS_V1 = [
355
+ "extraction", "human-entry", "human-review", "import", "model-proposal",
356
+ "normalization", "publication", "resolution", "transformation",
357
+ ] as const;
358
+ export type KnowledgeActivityKindV1 = (typeof OH_KNOWLEDGE_ACTIVITY_KINDS_V1)[number];
359
+ export type KnowledgeActivityV1 = Readonly<{
360
+ activitySha256: Sha256Hex;
361
+ actor: KnowledgeAgentRefV1;
362
+ inputSha256s: readonly Sha256Hex[];
363
+ kind: KnowledgeActivityKindV1;
364
+ occurredAt: string;
365
+ outputSha256s: readonly Sha256Hex[];
366
+ policySha256: Sha256Hex;
367
+ tool: KnowledgeSchemaRefV1 | null;
368
+ v: 1;
369
+ }>;
370
+ export type KnowledgeActivityInputV1 = Omit<KnowledgeActivityV1, "activitySha256">;
371
+
372
+ function parseActivityInput(value: unknown): KnowledgeActivityInputV1 | null {
373
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["actor", "inputSha256s", "kind", "occurredAt",
374
+ "outputSha256s", "policySha256", "tool", "v"]) || value.v !== 1) return null;
375
+ const actor = parseKnowledgeAgentRefV1(value.actor);
376
+ const inputSha256s = parseDigestArray(value.inputSha256s);
377
+ const kind = OH_KNOWLEDGE_ACTIVITY_KINDS_V1.find((candidate) => candidate === value.kind);
378
+ const occurredAt = parseCanonicalInstantV1(value.occurredAt);
379
+ const outputSha256s = parseDigestArray(value.outputSha256s);
380
+ const policySha256 = parseSha256Hex(value.policySha256);
381
+ const tool = value.tool === null ? null : parseKnowledgeSchemaRefV1(value.tool);
382
+ const parsedTool = tool === null ? null : tool.ok ? tool.value : null;
383
+ return actor !== null && inputSha256s !== null && kind !== undefined && occurredAt !== null
384
+ && outputSha256s !== null && policySha256 !== null && (value.tool === null || parsedTool !== null)
385
+ ? { actor, inputSha256s, kind, occurredAt, outputSha256s, policySha256, tool: parsedTool, v: 1 }
386
+ : null;
387
+ }
388
+
389
+ export function createKnowledgeActivityV1(input: KnowledgeActivityInputV1): KnowledgeOntologyResult<KnowledgeActivityV1> {
390
+ const parsed = parseActivityInput(input);
391
+ return parsed === null ? failure("activity") : success({ ...parsed, activitySha256: canonicalSha256(parsed) });
392
+ }
393
+
394
+ export function parseKnowledgeActivityV1(value: unknown): KnowledgeOntologyResult<KnowledgeActivityV1> {
395
+ if (!isPlainRecord(value) || !Object.hasOwn(value, "activitySha256")) return failure("activity");
396
+ const activitySha256 = parseSha256Hex(value.activitySha256);
397
+ const { activitySha256: _digest, ...input } = value;
398
+ const parsed = parseActivityInput(input);
399
+ return activitySha256 !== null && parsed !== null && canonicalSha256(parsed) === activitySha256
400
+ ? success({ ...parsed, activitySha256 }) : failure("activitySha256", "digest-mismatch");
401
+ }
402
+
403
+ export const OH_KNOWLEDGE_ASSERTION_STANCES_V1 = ["questions", "refutes", "reports", "supports", "undetermined"] as const;
404
+ export type KnowledgeAssertionStanceV1 = (typeof OH_KNOWLEDGE_ASSERTION_STANCES_V1)[number];
405
+ export const OH_KNOWLEDGE_ASSERTION_STATES_V1 = [
406
+ "accepted-for-purpose", "disputed", "proposed", "reviewed", "superseded", "withdrawn",
407
+ ] as const;
408
+ export type KnowledgeAssertionStateV1 = (typeof OH_KNOWLEDGE_ASSERTION_STATES_V1)[number];
409
+ export type KnowledgeAssertionV1 = Readonly<{
410
+ acceptedPurposes: readonly string[];
411
+ assertionId: KnowledgeAssertionId;
412
+ assertionSha256: Sha256Hex;
413
+ assertor: KnowledgeAgentRefV1;
414
+ confidence: KnowledgeSchemaRefV1 | null;
415
+ contextSha256: Sha256Hex | null;
416
+ provenanceActivitySha256: Sha256Hex;
417
+ reviewActivitySha256: Sha256Hex | null;
418
+ stance: KnowledgeAssertionStanceV1;
419
+ state: KnowledgeAssertionStateV1;
420
+ statementSha256: Sha256Hex;
421
+ v: 1;
422
+ }>;
423
+ export type KnowledgeAssertionInputV1 = Omit<KnowledgeAssertionV1, "assertionSha256">;
424
+
425
+ function parseStringCodes(value: unknown, maximum: number): readonly string[] | null {
426
+ if (!Array.isArray(value) || value.length > maximum) return null;
427
+ const codes = value.map((item) => safeCode(item));
428
+ return codes.every((code) => code !== null) && orderedUnique(codes as string[], String)
429
+ ? codes as string[] : null;
430
+ }
431
+
432
+ function parseAssertionInput(value: unknown): KnowledgeAssertionInputV1 | null {
433
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["acceptedPurposes", "assertionId", "assertor",
434
+ "confidence", "contextSha256", "provenanceActivitySha256", "reviewActivitySha256", "stance",
435
+ "state", "statementSha256", "v"]) || value.v !== 1) return null;
436
+ const acceptedPurposes = parseStringCodes(value.acceptedPurposes, 32);
437
+ const assertionId = parseKnowledgeAssertionId(value.assertionId);
438
+ const assertor = parseKnowledgeAgentRefV1(value.assertor);
439
+ const confidence = value.confidence === null ? null : parseKnowledgeSchemaRefV1(value.confidence);
440
+ const parsedConfidence = confidence === null ? null : confidence.ok ? confidence.value : null;
441
+ const contextSha256 = value.contextSha256 === null ? null : parseSha256Hex(value.contextSha256);
442
+ const provenanceActivitySha256 = parseSha256Hex(value.provenanceActivitySha256);
443
+ const reviewActivitySha256 = value.reviewActivitySha256 === null ? null : parseSha256Hex(value.reviewActivitySha256);
444
+ const stance = OH_KNOWLEDGE_ASSERTION_STANCES_V1.find((candidate) => candidate === value.stance);
445
+ const state = OH_KNOWLEDGE_ASSERTION_STATES_V1.find((candidate) => candidate === value.state);
446
+ const statementSha256 = parseSha256Hex(value.statementSha256);
447
+ if (acceptedPurposes === null || assertionId === null || assertor === null
448
+ || (value.confidence !== null && parsedConfidence === null)
449
+ || (value.contextSha256 !== null && contextSha256 === null)
450
+ || provenanceActivitySha256 === null
451
+ || (value.reviewActivitySha256 !== null && reviewActivitySha256 === null)
452
+ || stance === undefined || state === undefined || statementSha256 === null) return null;
453
+ if (assertor.kind === "model" && (state !== "proposed" || acceptedPurposes.length !== 0 || reviewActivitySha256 !== null)) return null;
454
+ if ((state === "accepted-for-purpose") !== (acceptedPurposes.length > 0)
455
+ || (state !== "proposed" && reviewActivitySha256 === null)) return null;
456
+ return { acceptedPurposes, assertionId, assertor, confidence: parsedConfidence, contextSha256,
457
+ provenanceActivitySha256, reviewActivitySha256, stance, state, statementSha256, v: 1 };
458
+ }
459
+
460
+ export function createKnowledgeAssertionV1(input: KnowledgeAssertionInputV1): KnowledgeOntologyResult<KnowledgeAssertionV1> {
461
+ const parsed = parseAssertionInput(input);
462
+ return parsed === null ? failure("assertion") : success({ ...parsed, assertionSha256: canonicalSha256(parsed) });
463
+ }
464
+
465
+ export function parseKnowledgeAssertionV1(value: unknown): KnowledgeOntologyResult<KnowledgeAssertionV1> {
466
+ if (!isPlainRecord(value) || !Object.hasOwn(value, "assertionSha256")) return failure("assertion");
467
+ const assertionSha256 = parseSha256Hex(value.assertionSha256);
468
+ const { assertionSha256: _digest, ...input } = value;
469
+ const parsed = parseAssertionInput(input);
470
+ return assertionSha256 !== null && parsed !== null && canonicalSha256(parsed) === assertionSha256
471
+ ? success({ ...parsed, assertionSha256 }) : failure("assertionSha256", "digest-mismatch");
472
+ }
473
+
474
+ export const OH_KNOWLEDGE_EVIDENCE_BEARINGS_V1 = [
475
+ "background", "contradicts", "corroborates", "direct-observation", "method",
476
+ "quotation", "registry-record", "supports",
477
+ ] as const;
478
+ export type KnowledgeEvidenceBearingV1 = (typeof OH_KNOWLEDGE_EVIDENCE_BEARINGS_V1)[number];
479
+ export type KnowledgeEvidenceLinkV1 = Readonly<{
480
+ assertionSha256: Sha256Hex;
481
+ bearing: KnowledgeEvidenceBearingV1;
482
+ disclosure: "private" | "public" | "shared";
483
+ evidenceId: KnowledgeEvidenceId;
484
+ evidenceSha256: Sha256Hex;
485
+ observationSha256: Sha256Hex | null;
486
+ provenanceActivitySha256: Sha256Hex;
487
+ selector: string | null;
488
+ sourceEntityId: KnowledgeEntityId | null;
489
+ v: 1;
490
+ }>;
491
+ export type KnowledgeEvidenceLinkInputV1 = Omit<KnowledgeEvidenceLinkV1, "evidenceSha256">;
492
+
493
+ function parseEvidenceInput(value: unknown): KnowledgeEvidenceLinkInputV1 | null {
494
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["assertionSha256", "bearing", "disclosure",
495
+ "evidenceId", "observationSha256", "provenanceActivitySha256", "selector", "sourceEntityId", "v"])
496
+ || value.v !== 1) return null;
497
+ const assertionSha256 = parseSha256Hex(value.assertionSha256);
498
+ const bearing = OH_KNOWLEDGE_EVIDENCE_BEARINGS_V1.find((candidate) => candidate === value.bearing);
499
+ const evidenceId = parseKnowledgeEvidenceId(value.evidenceId);
500
+ const observationSha256 = value.observationSha256 === null ? null : parseSha256Hex(value.observationSha256);
501
+ const provenanceActivitySha256 = parseSha256Hex(value.provenanceActivitySha256);
502
+ const selector = value.selector === null ? null : boundedText(value.selector, 8192);
503
+ const sourceEntityId = value.sourceEntityId === null ? null : parseKnowledgeEntityId(value.sourceEntityId);
504
+ return assertionSha256 !== null && bearing !== undefined
505
+ && (value.disclosure === "private" || value.disclosure === "public" || value.disclosure === "shared")
506
+ && evidenceId !== null && (value.observationSha256 === null || observationSha256 !== null)
507
+ && provenanceActivitySha256 !== null && (value.selector === null || selector !== null)
508
+ && (value.sourceEntityId === null || sourceEntityId !== null)
509
+ && (observationSha256 !== null || sourceEntityId !== null)
510
+ ? { assertionSha256, bearing, disclosure: value.disclosure, evidenceId, observationSha256,
511
+ provenanceActivitySha256, selector, sourceEntityId, v: 1 }
512
+ : null;
513
+ }
514
+
515
+ export function createKnowledgeEvidenceLinkV1(input: KnowledgeEvidenceLinkInputV1): KnowledgeOntologyResult<KnowledgeEvidenceLinkV1> {
516
+ const parsed = parseEvidenceInput(input);
517
+ return parsed === null ? failure("evidence") : success({ ...parsed, evidenceSha256: canonicalSha256(parsed) });
518
+ }
519
+
520
+ export function parseKnowledgeEvidenceLinkV1(value: unknown): KnowledgeOntologyResult<KnowledgeEvidenceLinkV1> {
521
+ if (!isPlainRecord(value) || !Object.hasOwn(value, "evidenceSha256")) return failure("evidence");
522
+ const evidenceSha256 = parseSha256Hex(value.evidenceSha256);
523
+ const { evidenceSha256: _digest, ...input } = value;
524
+ const parsed = parseEvidenceInput(input);
525
+ return evidenceSha256 !== null && parsed !== null && canonicalSha256(parsed) === evidenceSha256
526
+ ? success({ ...parsed, evidenceSha256 }) : failure("evidenceSha256", "digest-mismatch");
527
+ }
528
+
529
+ export type KnowledgeInquiryV1 = Readonly<{
530
+ answerForm: string;
531
+ authorEntityId: KnowledgeEntityId;
532
+ contextSha256: Sha256Hex | null;
533
+ createdAt: string;
534
+ inquiryId: KnowledgeInquiryId;
535
+ inquirySha256: Sha256Hex;
536
+ language: string;
537
+ parentInquiryIds: readonly KnowledgeInquiryId[];
538
+ privacy: "private" | "public" | "shared";
539
+ question: string;
540
+ status: "abandoned" | "open" | "paused" | "resolved";
541
+ v: 1;
542
+ }>;
543
+ export type KnowledgeInquiryInputV1 = Omit<KnowledgeInquiryV1, "inquirySha256">;
544
+
545
+ export function createKnowledgeInquiryV1(input: KnowledgeInquiryInputV1): KnowledgeOntologyResult<KnowledgeInquiryV1> {
546
+ const answerForm = safeCode(input.answerForm);
547
+ const authorEntityId = parseKnowledgeEntityId(input.authorEntityId);
548
+ const contextSha256 = input.contextSha256 === null ? null : parseSha256Hex(input.contextSha256);
549
+ const createdAt = parseCanonicalInstantV1(input.createdAt);
550
+ const inquiryId = parseKnowledgeInquiryId(input.inquiryId);
551
+ const language = typeof input.language === "string" && /^(?:und|[a-z]{2,3}(?:-[a-z0-9]{2,8})*)$/u.test(input.language)
552
+ ? input.language : null;
553
+ const parents = Array.isArray(input.parentInquiryIds) ? input.parentInquiryIds.map(parseKnowledgeInquiryId) : null;
554
+ const question = boundedText(input.question, 16_384);
555
+ if (input.v !== 1 || answerForm === null || authorEntityId === null
556
+ || (input.contextSha256 !== null && contextSha256 === null) || createdAt === null || inquiryId === null
557
+ || language === null || parents === null || parents.some((item) => item === null)
558
+ || !orderedUnique(parents as KnowledgeInquiryId[], String)
559
+ || !["private", "public", "shared"].includes(input.privacy) || question === null
560
+ || !["abandoned", "open", "paused", "resolved"].includes(input.status)) return failure("inquiry");
561
+ const payload = { answerForm, authorEntityId, contextSha256, createdAt, inquiryId, language,
562
+ parentInquiryIds: parents as KnowledgeInquiryId[], privacy: input.privacy, question, status: input.status, v: 1 as const };
563
+ return success({ ...payload, inquirySha256: canonicalSha256(payload) });
564
+ }
565
+
566
+ export function parseKnowledgeInquiryV1(value: unknown): KnowledgeOntologyResult<KnowledgeInquiryV1> {
567
+ if (!isPlainRecord(value) || !Object.hasOwn(value, "inquirySha256")) return failure("inquiry");
568
+ const digest = parseSha256Hex(value.inquirySha256);
569
+ const { inquirySha256: _digest, ...input } = value;
570
+ const created = createKnowledgeInquiryV1(input as unknown as KnowledgeInquiryInputV1);
571
+ return digest !== null && created.ok && created.value.inquirySha256 === digest
572
+ ? success({ ...created.value, inquirySha256: digest }) : failure("inquirySha256", "digest-mismatch");
573
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ canonicalJson,
3
+ canonicalSha256,
4
+ hasExactKeys,
5
+ isPlainRecord,
6
+ parseCanonicalInstantV1,
7
+ parseSha256Hex,
8
+ safeCode,
9
+ type Sha256Hex,
10
+ } from "./canonical";
11
+ import { OH_CONTRACT_ID_V1 } from "./ontology";
12
+ import { canonicalKnowledgeGraphChangesV1, OH_GRAPH_LIMITS_V1, type KnowledgeGraphChangeV1 } from "./graph";
13
+
14
+ export { graphRevisionSha256V1 } from "./graph";
15
+
16
+ export const OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
17
+
18
+ export type OhOperationPayloadV1 = Readonly<{
19
+ actorId: string;
20
+ changes: readonly KnowledgeGraphChangeV1[];
21
+ contractId: typeof OH_CONTRACT_ID_V1;
22
+ graphRevisionSha256: Sha256Hex;
23
+ instant: string;
24
+ operationId: string;
25
+ parentOperationSha256: Sha256Hex | null;
26
+ recordsSha256: Sha256Hex;
27
+ sequence: number;
28
+ spaceId: string;
29
+ v: 1;
30
+ }>;
31
+
32
+ export type OhOperationV1 = OhOperationPayloadV1 & Readonly<{ operationSha256: Sha256Hex }>;
33
+
34
+ function parsePayload(value: unknown): OhOperationPayloadV1 | null {
35
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["actorId", "changes", "contractId",
36
+ "graphRevisionSha256", "instant", "operationId", "parentOperationSha256", "recordsSha256",
37
+ "sequence", "spaceId", "v"]) || value.v !== 1 || value.contractId !== OH_CONTRACT_ID_V1
38
+ || !Array.isArray(value.changes)) return null;
39
+ const actorId = safeCode(value.actorId);
40
+ const operationId = safeCode(value.operationId);
41
+ const spaceId = safeCode(value.spaceId);
42
+ const graphRevisionSha256 = parseSha256Hex(value.graphRevisionSha256);
43
+ const parentOperationSha256 = value.parentOperationSha256 === null
44
+ ? null : parseSha256Hex(value.parentOperationSha256);
45
+ const recordsSha256 = parseSha256Hex(value.recordsSha256);
46
+ const instant = parseCanonicalInstantV1(value.instant);
47
+ const sequence = Number.isSafeInteger(value.sequence) && (value.sequence as number) > 0
48
+ ? value.sequence as number : null;
49
+ let changes: readonly KnowledgeGraphChangeV1[];
50
+ try { changes = canonicalKnowledgeGraphChangesV1(value.changes as KnowledgeGraphChangeV1[]); } catch { return null; }
51
+ if (changes.length === 0 || changes.length > OH_GRAPH_LIMITS_V1.changesPerOperation) return null;
52
+ return actorId !== null && operationId !== null && spaceId !== null
53
+ && graphRevisionSha256 !== null && recordsSha256 !== null && instant !== null && sequence !== null
54
+ && (value.parentOperationSha256 === null || parentOperationSha256 !== null)
55
+ && ((sequence === 1) === (parentOperationSha256 === null))
56
+ ? { actorId, changes, contractId: OH_CONTRACT_ID_V1, graphRevisionSha256, instant,
57
+ operationId, parentOperationSha256, recordsSha256, sequence, spaceId, v: 1 }
58
+ : null;
59
+ }
60
+
61
+ export function createOhOperationV1(input: OhOperationPayloadV1): OhOperationV1 {
62
+ const payload = parsePayload(input);
63
+ if (payload === null) throw new TypeError("Invalid Oh operation payload.");
64
+ const operation = { ...payload, operationSha256: canonicalSha256(payload) };
65
+ if (Buffer.byteLength(canonicalJson(operation), "utf8") > OH_OPERATION_MAX_BYTES_V1) {
66
+ throw new RangeError("Oh operation exceeds its canonical byte limit.");
67
+ }
68
+ return operation;
69
+ }
70
+
71
+ export function parseOhOperationV1(value: unknown): OhOperationV1 | null {
72
+ if (!isPlainRecord(value) || !Object.hasOwn(value, "operationSha256")) return null;
73
+ const operationSha256 = parseSha256Hex(value.operationSha256);
74
+ const { operationSha256: _digest, ...input } = value;
75
+ const payload = parsePayload(input);
76
+ return operationSha256 !== null && payload !== null
77
+ && Buffer.byteLength(canonicalJson({ ...payload, operationSha256 }), "utf8") <= OH_OPERATION_MAX_BYTES_V1
78
+ && canonicalSha256(payload) === operationSha256
79
+ ? { ...payload, operationSha256 } : null;
80
+ }