@hraness/oh 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +119 -12
  2. package/dist/canonical.d.ts.map +1 -1
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.js +91 -19
  5. package/dist/cloudflare-embedding.d.ts +104 -0
  6. package/dist/cloudflare-embedding.d.ts.map +1 -0
  7. package/dist/graph.d.ts.map +1 -1
  8. package/dist/index.js +90 -18
  9. package/dist/libsql-semantic.d.ts +130 -0
  10. package/dist/libsql-semantic.d.ts.map +1 -0
  11. package/dist/libsql.js +105 -18
  12. package/dist/memory-page.d.ts +2 -0
  13. package/dist/memory-page.d.ts.map +1 -0
  14. package/dist/memory-page.js +725 -0
  15. package/dist/memory-pages.d.ts +76 -0
  16. package/dist/memory-pages.d.ts.map +1 -0
  17. package/dist/memory.d.ts +1 -0
  18. package/dist/memory.d.ts.map +1 -1
  19. package/dist/memory.js +478 -18
  20. package/dist/projection-public.js +105 -18
  21. package/dist/projection-suss.js +105 -18
  22. package/dist/sdk.js +90 -18
  23. package/dist/semantic-cloud.d.ts +3 -0
  24. package/dist/semantic-cloud.d.ts.map +1 -0
  25. package/dist/semantic-cloud.js +1870 -0
  26. package/dist/semantic.d.ts.map +1 -1
  27. package/dist/semantic.js +104 -21
  28. package/dist/sqlite/index.js +90 -18
  29. package/dist/store.js +105 -18
  30. package/dist/sync.js +90 -18
  31. package/package.json +10 -2
  32. package/skills/oh/SKILL.md +28 -2
  33. package/spec/README.md +11 -3
  34. package/spec/manifest.json +9 -1
  35. package/spec/v1/cloudflare-embedding-profile.json +13 -0
  36. package/spec/v1/cloudflare-embedding-renderer.json +8 -0
  37. package/spec/v1/memory-page.md +153 -0
  38. package/spec/v1/memory-page.schema.json +154 -0
  39. package/spec/v1/memory.md +18 -0
  40. package/spec/v1/migration.md +13 -0
  41. package/spec/v1/semantic-cloud.md +96 -0
  42. package/src/canonical.ts +28 -13
  43. package/src/cli.ts +1 -1
  44. package/src/cloudflare-embedding.test.ts +306 -0
  45. package/src/cloudflare-embedding.ts +385 -0
  46. package/src/contracts.test.ts +20 -0
  47. package/src/graph.ts +63 -6
  48. package/src/libsql-semantic.test.ts +585 -0
  49. package/src/libsql-semantic.ts +1168 -0
  50. package/src/memory-page.ts +1 -0
  51. package/src/memory-pages.test.ts +277 -0
  52. package/src/memory-pages.ts +440 -0
  53. package/src/memory.ts +2 -0
  54. package/src/semantic-cloud.ts +2 -0
  55. package/src/semantic.ts +14 -3
@@ -1 +1 @@
1
- {"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../src/semantic.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../src/semantic.ts"],"names":[],"mappings":"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"}
package/dist/semantic.js CHANGED
@@ -65,17 +65,27 @@ function encodeCanonical(value, path, ancestors) {
65
65
  ancestors.add(value);
66
66
  try {
67
67
  if (Array.isArray(value)) {
68
- const encoded = [];
69
- for (let index = 0;index < value.length; index += 1) {
70
- if (!Object.hasOwn(value, index)) {
71
- throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
72
- }
73
- encoded.push(encodeCanonical(value[index], `${path}[${index}]`, ancestors));
68
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
69
+ const length = lengthDescriptor?.value;
70
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
71
+ throw new OhValidationError("non-json-property", path, "array has an invalid length descriptor");
74
72
  }
75
- const extraKeys = Reflect.ownKeys(value).filter((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= value.length));
76
- if (extraKeys.length > 0) {
73
+ const ownKeys2 = Reflect.ownKeys(value);
74
+ if (!ownKeys2.includes("length") || ownKeys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
77
75
  throw new OhValidationError("non-json-property", path, "array has non-index properties");
78
76
  }
77
+ const elements = [];
78
+ for (let index = 0;index < length; index += 1) {
79
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
80
+ if (descriptor === undefined) {
81
+ throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
82
+ }
83
+ if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
84
+ throw new OhValidationError("non-json-property", `${path}[${index}]`, "must be an enumerable data property");
85
+ }
86
+ elements.push(descriptor.value);
87
+ }
88
+ const encoded = elements.map((element, index) => encodeCanonical(element, `${path}[${index}]`, ancestors));
79
89
  return `[${encoded.join(",")}]`;
80
90
  }
81
91
  if (!isPlainRecord(value)) {
@@ -85,19 +95,21 @@ function encodeCanonical(value, path, ancestors) {
85
95
  if (ownKeys.some((key) => typeof key !== "string")) {
86
96
  throw new OhValidationError("non-json-property", path, "object has a symbol property");
87
97
  }
98
+ const entries = [];
88
99
  const keys = ownKeys;
89
100
  for (const key of keys) {
90
101
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
91
102
  if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
92
103
  throw new OhValidationError("non-json-property", `${path}.${key}`, "must be an enumerable data property");
93
104
  }
105
+ entries.push([key, descriptor.value]);
94
106
  }
95
- keys.sort();
96
- const entries = keys.map((key) => {
107
+ entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
108
+ const encodedEntries = entries.map(([key, entryValue]) => {
97
109
  assertUnicodeScalarString(key, `${path}.<key>`);
98
- return `${JSON.stringify(key)}:${encodeCanonical(value[key], `${path}.${key}`, ancestors)}`;
110
+ return `${JSON.stringify(key)}:${encodeCanonical(entryValue, `${path}.${key}`, ancestors)}`;
99
111
  });
100
- return `{${entries.join(",")}}`;
112
+ return `{${encodedEntries.join(",")}}`;
101
113
  } finally {
102
114
  ancestors.delete(value);
103
115
  }
@@ -209,17 +221,70 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
209
221
  "view",
210
222
  "vocabulary"
211
223
  ];
224
+ var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
225
+ "dependencies",
226
+ "key",
227
+ "kind",
228
+ "recordSha256",
229
+ "v",
230
+ "value"
231
+ ];
232
+ function exactKnowledgeGraphRecordEnvelopeV1(value) {
233
+ try {
234
+ if (!isPlainRecord(value))
235
+ return null;
236
+ const ownKeys = Reflect.ownKeys(value);
237
+ if (ownKeys.length !== KNOWLEDGE_GRAPH_RECORD_KEYS_V1.length || ownKeys.some((key) => typeof key !== "string") || KNOWLEDGE_GRAPH_RECORD_KEYS_V1.some((key) => !ownKeys.includes(key)))
238
+ return null;
239
+ const detached = {};
240
+ for (const key of KNOWLEDGE_GRAPH_RECORD_KEYS_V1) {
241
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
242
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
243
+ return null;
244
+ detached[key] = descriptor.value;
245
+ }
246
+ return detached;
247
+ } catch {
248
+ return null;
249
+ }
250
+ }
251
+ function exactGraphDependenciesV1(value) {
252
+ try {
253
+ if (!Array.isArray(value))
254
+ return null;
255
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
256
+ const length = lengthDescriptor?.value;
257
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
258
+ return null;
259
+ const ownKeys = Reflect.ownKeys(value);
260
+ if (ownKeys.length !== length + 1 || !ownKeys.includes("length") || ownKeys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
261
+ return null;
262
+ const detached = [];
263
+ for (let index = 0;index < length; index += 1) {
264
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
265
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
266
+ return null;
267
+ detached.push(descriptor.value);
268
+ }
269
+ return detached;
270
+ } catch {
271
+ return null;
272
+ }
273
+ }
212
274
  function recordKey(value) {
213
275
  return typeof value === "string" && value.length <= 512 && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
214
276
  }
215
277
  function createKnowledgeGraphRecordV1(input) {
216
- if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1 || !Array.isArray(input.dependencies))
278
+ if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1)
217
279
  throw new TypeError("Invalid graph record input.");
280
+ const dependencyInput = exactGraphDependenciesV1(input.dependencies);
281
+ if (dependencyInput === null)
282
+ throw new TypeError("Invalid graph record dependencies.");
218
283
  const key = recordKey(input.key);
219
284
  const kind = OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.find((candidate) => candidate === input.kind);
220
- if (key === null || kind === undefined || input.dependencies.length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
285
+ if (key === null || kind === undefined)
221
286
  throw new TypeError("Invalid graph record identity.");
222
- const dependencies = input.dependencies.map(recordKey);
287
+ const dependencies = dependencyInput.map(recordKey);
223
288
  if (dependencies.some((dependency) => dependency === null) || !orderedUnique(dependencies, String) || dependencies.includes(key)) {
224
289
  throw new TypeError("Graph dependencies must be ordered, unique, and non-reflexive.");
225
290
  }
@@ -231,10 +296,17 @@ function createKnowledgeGraphRecordV1(input) {
231
296
  return { ...payload, recordSha256: canonicalSha256(payload) };
232
297
  }
233
298
  function parseKnowledgeGraphRecordV1(value) {
234
- if (!isPlainRecord(value) || !Object.hasOwn(value, "recordSha256"))
299
+ const envelope = exactKnowledgeGraphRecordEnvelopeV1(value);
300
+ if (envelope === null)
235
301
  return null;
236
- const recordSha256 = parseSha256Hex(value.recordSha256);
237
- const { recordSha256: _digest, ...input } = value;
302
+ const recordSha256 = parseSha256Hex(envelope.recordSha256);
303
+ const input = {
304
+ dependencies: envelope.dependencies,
305
+ key: envelope.key,
306
+ kind: envelope.kind,
307
+ v: envelope.v,
308
+ value: envelope.value
309
+ };
238
310
  try {
239
311
  const created = createKnowledgeGraphRecordV1(input);
240
312
  return recordSha256 !== null && created.recordSha256 === recordSha256 ? { ...created, recordSha256 } : null;
@@ -431,10 +503,21 @@ function normalizeOhEmbeddingV1(vector) {
431
503
  if (vector.length !== OH_EMBEDDING_PROFILE_V1.dimensions || vector.some((component) => !Number.isFinite(component))) {
432
504
  throw new TypeError(`Embedding vectors must contain ${OH_EMBEDDING_PROFILE_V1.dimensions} finite values.`);
433
505
  }
434
- const magnitude = Math.sqrt(vector.reduce((sum, component) => sum + component * component, 0));
435
- if (magnitude === 0)
506
+ const scale = vector.reduce((maximum, component) => Math.max(maximum, Math.abs(component)), 0);
507
+ if (scale === 0)
436
508
  throw new TypeError("Embedding vectors must have nonzero magnitude.");
437
- return vector.map((component) => component / magnitude);
509
+ const scaledMagnitude = Math.sqrt(vector.reduce((sum, component) => {
510
+ const scaled = component / scale;
511
+ return sum + scaled * scaled;
512
+ }, 0));
513
+ if (!Number.isFinite(scaledMagnitude) || scaledMagnitude === 0) {
514
+ throw new TypeError("Embedding vectors must have finite nonzero magnitude.");
515
+ }
516
+ const normalized = vector.map((component) => component / scale / scaledMagnitude);
517
+ if (normalized.some((component) => !Number.isFinite(component))) {
518
+ throw new TypeError("Embedding vectors must normalize to finite values.");
519
+ }
520
+ return normalized;
438
521
  }
439
522
  function cosineSimilarityV1(left, right) {
440
523
  const normalizedLeft = normalizeOhEmbeddingV1(left);
@@ -135,17 +135,27 @@ function encodeCanonical(value, path, ancestors) {
135
135
  ancestors.add(value);
136
136
  try {
137
137
  if (Array.isArray(value)) {
138
- const encoded = [];
139
- for (let index = 0;index < value.length; index += 1) {
140
- if (!Object.hasOwn(value, index)) {
141
- throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
142
- }
143
- encoded.push(encodeCanonical(value[index], `${path}[${index}]`, ancestors));
138
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
139
+ const length = lengthDescriptor?.value;
140
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
141
+ throw new OhValidationError("non-json-property", path, "array has an invalid length descriptor");
144
142
  }
145
- const extraKeys = Reflect.ownKeys(value).filter((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= value.length));
146
- if (extraKeys.length > 0) {
143
+ const ownKeys2 = Reflect.ownKeys(value);
144
+ if (!ownKeys2.includes("length") || ownKeys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
147
145
  throw new OhValidationError("non-json-property", path, "array has non-index properties");
148
146
  }
147
+ const elements = [];
148
+ for (let index = 0;index < length; index += 1) {
149
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
150
+ if (descriptor === undefined) {
151
+ throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
152
+ }
153
+ if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
154
+ throw new OhValidationError("non-json-property", `${path}[${index}]`, "must be an enumerable data property");
155
+ }
156
+ elements.push(descriptor.value);
157
+ }
158
+ const encoded = elements.map((element, index) => encodeCanonical(element, `${path}[${index}]`, ancestors));
149
159
  return `[${encoded.join(",")}]`;
150
160
  }
151
161
  if (!isPlainRecord(value)) {
@@ -155,19 +165,21 @@ function encodeCanonical(value, path, ancestors) {
155
165
  if (ownKeys.some((key) => typeof key !== "string")) {
156
166
  throw new OhValidationError("non-json-property", path, "object has a symbol property");
157
167
  }
168
+ const entries = [];
158
169
  const keys = ownKeys;
159
170
  for (const key of keys) {
160
171
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
161
172
  if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
162
173
  throw new OhValidationError("non-json-property", `${path}.${key}`, "must be an enumerable data property");
163
174
  }
175
+ entries.push([key, descriptor.value]);
164
176
  }
165
- keys.sort();
166
- const entries = keys.map((key) => {
177
+ entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
178
+ const encodedEntries = entries.map(([key, entryValue]) => {
167
179
  assertUnicodeScalarString(key, `${path}.<key>`);
168
- return `${JSON.stringify(key)}:${encodeCanonical(value[key], `${path}.${key}`, ancestors)}`;
180
+ return `${JSON.stringify(key)}:${encodeCanonical(entryValue, `${path}.${key}`, ancestors)}`;
169
181
  });
170
- return `{${entries.join(",")}}`;
182
+ return `{${encodedEntries.join(",")}}`;
171
183
  } finally {
172
184
  ancestors.delete(value);
173
185
  }
@@ -449,17 +461,70 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
449
461
  "view",
450
462
  "vocabulary"
451
463
  ];
464
+ var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
465
+ "dependencies",
466
+ "key",
467
+ "kind",
468
+ "recordSha256",
469
+ "v",
470
+ "value"
471
+ ];
472
+ function exactKnowledgeGraphRecordEnvelopeV1(value) {
473
+ try {
474
+ if (!isPlainRecord(value))
475
+ return null;
476
+ const ownKeys = Reflect.ownKeys(value);
477
+ if (ownKeys.length !== KNOWLEDGE_GRAPH_RECORD_KEYS_V1.length || ownKeys.some((key) => typeof key !== "string") || KNOWLEDGE_GRAPH_RECORD_KEYS_V1.some((key) => !ownKeys.includes(key)))
478
+ return null;
479
+ const detached = {};
480
+ for (const key of KNOWLEDGE_GRAPH_RECORD_KEYS_V1) {
481
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
482
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
483
+ return null;
484
+ detached[key] = descriptor.value;
485
+ }
486
+ return detached;
487
+ } catch {
488
+ return null;
489
+ }
490
+ }
491
+ function exactGraphDependenciesV1(value) {
492
+ try {
493
+ if (!Array.isArray(value))
494
+ return null;
495
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
496
+ const length = lengthDescriptor?.value;
497
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
498
+ return null;
499
+ const ownKeys = Reflect.ownKeys(value);
500
+ if (ownKeys.length !== length + 1 || !ownKeys.includes("length") || ownKeys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
501
+ return null;
502
+ const detached = [];
503
+ for (let index = 0;index < length; index += 1) {
504
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
505
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
506
+ return null;
507
+ detached.push(descriptor.value);
508
+ }
509
+ return detached;
510
+ } catch {
511
+ return null;
512
+ }
513
+ }
452
514
  function recordKey(value) {
453
515
  return typeof value === "string" && value.length <= 512 && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
454
516
  }
455
517
  function createKnowledgeGraphRecordV1(input) {
456
- if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1 || !Array.isArray(input.dependencies))
518
+ if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1)
457
519
  throw new TypeError("Invalid graph record input.");
520
+ const dependencyInput = exactGraphDependenciesV1(input.dependencies);
521
+ if (dependencyInput === null)
522
+ throw new TypeError("Invalid graph record dependencies.");
458
523
  const key = recordKey(input.key);
459
524
  const kind = OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.find((candidate) => candidate === input.kind);
460
- if (key === null || kind === undefined || input.dependencies.length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
525
+ if (key === null || kind === undefined)
461
526
  throw new TypeError("Invalid graph record identity.");
462
- const dependencies = input.dependencies.map(recordKey);
527
+ const dependencies = dependencyInput.map(recordKey);
463
528
  if (dependencies.some((dependency) => dependency === null) || !orderedUnique(dependencies, String) || dependencies.includes(key)) {
464
529
  throw new TypeError("Graph dependencies must be ordered, unique, and non-reflexive.");
465
530
  }
@@ -471,10 +536,17 @@ function createKnowledgeGraphRecordV1(input) {
471
536
  return { ...payload, recordSha256: canonicalSha256(payload) };
472
537
  }
473
538
  function parseKnowledgeGraphRecordV1(value) {
474
- if (!isPlainRecord(value) || !Object.hasOwn(value, "recordSha256"))
539
+ const envelope = exactKnowledgeGraphRecordEnvelopeV1(value);
540
+ if (envelope === null)
475
541
  return null;
476
- const recordSha256 = parseSha256Hex(value.recordSha256);
477
- const { recordSha256: _digest, ...input } = value;
542
+ const recordSha256 = parseSha256Hex(envelope.recordSha256);
543
+ const input = {
544
+ dependencies: envelope.dependencies,
545
+ key: envelope.key,
546
+ kind: envelope.kind,
547
+ v: envelope.v,
548
+ value: envelope.value
549
+ };
478
550
  try {
479
551
  const created = createKnowledgeGraphRecordV1(input);
480
552
  return recordSha256 !== null && created.recordSha256 === recordSha256 ? { ...created, recordSha256 } : null;
package/dist/store.js CHANGED
@@ -60,17 +60,27 @@ function encodeCanonical(value, path, ancestors) {
60
60
  ancestors.add(value);
61
61
  try {
62
62
  if (Array.isArray(value)) {
63
- const encoded = [];
64
- for (let index = 0;index < value.length; index += 1) {
65
- if (!Object.hasOwn(value, index)) {
66
- throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
67
- }
68
- encoded.push(encodeCanonical(value[index], `${path}[${index}]`, ancestors));
63
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
64
+ const length = lengthDescriptor?.value;
65
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
66
+ throw new OhValidationError("non-json-property", path, "array has an invalid length descriptor");
69
67
  }
70
- const extraKeys = Reflect.ownKeys(value).filter((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= value.length));
71
- if (extraKeys.length > 0) {
68
+ const ownKeys2 = Reflect.ownKeys(value);
69
+ if (!ownKeys2.includes("length") || ownKeys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
72
70
  throw new OhValidationError("non-json-property", path, "array has non-index properties");
73
71
  }
72
+ const elements = [];
73
+ for (let index = 0;index < length; index += 1) {
74
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
75
+ if (descriptor === undefined) {
76
+ throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
77
+ }
78
+ if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
79
+ throw new OhValidationError("non-json-property", `${path}[${index}]`, "must be an enumerable data property");
80
+ }
81
+ elements.push(descriptor.value);
82
+ }
83
+ const encoded = elements.map((element, index) => encodeCanonical(element, `${path}[${index}]`, ancestors));
74
84
  return `[${encoded.join(",")}]`;
75
85
  }
76
86
  if (!isPlainRecord(value)) {
@@ -80,19 +90,21 @@ function encodeCanonical(value, path, ancestors) {
80
90
  if (ownKeys.some((key) => typeof key !== "string")) {
81
91
  throw new OhValidationError("non-json-property", path, "object has a symbol property");
82
92
  }
93
+ const entries = [];
83
94
  const keys = ownKeys;
84
95
  for (const key of keys) {
85
96
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
86
97
  if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
87
98
  throw new OhValidationError("non-json-property", `${path}.${key}`, "must be an enumerable data property");
88
99
  }
100
+ entries.push([key, descriptor.value]);
89
101
  }
90
- keys.sort();
91
- const entries = keys.map((key) => {
102
+ entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
103
+ const encodedEntries = entries.map(([key, entryValue]) => {
92
104
  assertUnicodeScalarString(key, `${path}.<key>`);
93
- return `${JSON.stringify(key)}:${encodeCanonical(value[key], `${path}.${key}`, ancestors)}`;
105
+ return `${JSON.stringify(key)}:${encodeCanonical(entryValue, `${path}.${key}`, ancestors)}`;
94
106
  });
95
- return `{${entries.join(",")}}`;
107
+ return `{${encodedEntries.join(",")}}`;
96
108
  } finally {
97
109
  ancestors.delete(value);
98
110
  }
@@ -140,6 +152,21 @@ function canonicalNow() {
140
152
  function safeCode(value, maximumLength = 128) {
141
153
  return typeof value === "string" && value.length <= maximumLength && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
142
154
  }
155
+ function boundedText(value, maximumBytes = 64 * 1024) {
156
+ if (typeof value !== "string" || value.length === 0 || value.normalize("NFC") !== value || utf8ByteLength(value) > maximumBytes)
157
+ return null;
158
+ try {
159
+ assertUnicodeScalarString(value, "$text");
160
+ } catch {
161
+ return null;
162
+ }
163
+ for (const character of value) {
164
+ const code = character.codePointAt(0) ?? 0;
165
+ if (code <= 8 || code >= 11 && code <= 12 || code >= 14 && code <= 31 || code >= 127 && code <= 159)
166
+ return null;
167
+ }
168
+ return value;
169
+ }
143
170
  function orderedUnique(values, key) {
144
171
  return values.every((value, index) => index === 0 || key(values[index - 1]) < key(value));
145
172
  }
@@ -183,17 +210,70 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
183
210
  "view",
184
211
  "vocabulary"
185
212
  ];
213
+ var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
214
+ "dependencies",
215
+ "key",
216
+ "kind",
217
+ "recordSha256",
218
+ "v",
219
+ "value"
220
+ ];
221
+ function exactKnowledgeGraphRecordEnvelopeV1(value) {
222
+ try {
223
+ if (!isPlainRecord(value))
224
+ return null;
225
+ const ownKeys = Reflect.ownKeys(value);
226
+ if (ownKeys.length !== KNOWLEDGE_GRAPH_RECORD_KEYS_V1.length || ownKeys.some((key) => typeof key !== "string") || KNOWLEDGE_GRAPH_RECORD_KEYS_V1.some((key) => !ownKeys.includes(key)))
227
+ return null;
228
+ const detached = {};
229
+ for (const key of KNOWLEDGE_GRAPH_RECORD_KEYS_V1) {
230
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
231
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
232
+ return null;
233
+ detached[key] = descriptor.value;
234
+ }
235
+ return detached;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+ function exactGraphDependenciesV1(value) {
241
+ try {
242
+ if (!Array.isArray(value))
243
+ return null;
244
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
245
+ const length = lengthDescriptor?.value;
246
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
247
+ return null;
248
+ const ownKeys = Reflect.ownKeys(value);
249
+ if (ownKeys.length !== length + 1 || !ownKeys.includes("length") || ownKeys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
250
+ return null;
251
+ const detached = [];
252
+ for (let index = 0;index < length; index += 1) {
253
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
254
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
255
+ return null;
256
+ detached.push(descriptor.value);
257
+ }
258
+ return detached;
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
186
263
  function recordKey(value) {
187
264
  return typeof value === "string" && value.length <= 512 && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
188
265
  }
189
266
  function createKnowledgeGraphRecordV1(input) {
190
- if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1 || !Array.isArray(input.dependencies))
267
+ if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1)
191
268
  throw new TypeError("Invalid graph record input.");
269
+ const dependencyInput = exactGraphDependenciesV1(input.dependencies);
270
+ if (dependencyInput === null)
271
+ throw new TypeError("Invalid graph record dependencies.");
192
272
  const key = recordKey(input.key);
193
273
  const kind = OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.find((candidate) => candidate === input.kind);
194
- if (key === null || kind === undefined || input.dependencies.length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
274
+ if (key === null || kind === undefined)
195
275
  throw new TypeError("Invalid graph record identity.");
196
- const dependencies = input.dependencies.map(recordKey);
276
+ const dependencies = dependencyInput.map(recordKey);
197
277
  if (dependencies.some((dependency) => dependency === null) || !orderedUnique(dependencies, String) || dependencies.includes(key)) {
198
278
  throw new TypeError("Graph dependencies must be ordered, unique, and non-reflexive.");
199
279
  }
@@ -205,10 +285,17 @@ function createKnowledgeGraphRecordV1(input) {
205
285
  return { ...payload, recordSha256: canonicalSha256(payload) };
206
286
  }
207
287
  function parseKnowledgeGraphRecordV1(value) {
208
- if (!isPlainRecord(value) || !Object.hasOwn(value, "recordSha256"))
288
+ const envelope = exactKnowledgeGraphRecordEnvelopeV1(value);
289
+ if (envelope === null)
209
290
  return null;
210
- const recordSha256 = parseSha256Hex(value.recordSha256);
211
- const { recordSha256: _digest, ...input } = value;
291
+ const recordSha256 = parseSha256Hex(envelope.recordSha256);
292
+ const input = {
293
+ dependencies: envelope.dependencies,
294
+ key: envelope.key,
295
+ kind: envelope.kind,
296
+ v: envelope.v,
297
+ value: envelope.value
298
+ };
212
299
  try {
213
300
  const created = createKnowledgeGraphRecordV1(input);
214
301
  return recordSha256 !== null && created.recordSha256 === recordSha256 ? { ...created, recordSha256 } : null;