@hraness/oh 0.3.2 → 0.4.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 (57) hide show
  1. package/README.md +164 -114
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +811 -97
  5. package/dist/errors.d.ts +39 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/graph.d.ts.map +1 -1
  8. package/dist/index.js +676 -61
  9. package/dist/libsql.d.ts.map +1 -1
  10. package/dist/libsql.js +162 -35
  11. package/dist/memory-page.js +2 -2
  12. package/dist/memory.d.ts +87 -6
  13. package/dist/memory.d.ts.map +1 -1
  14. package/dist/memory.js +1106 -148
  15. package/dist/operation.d.ts +3 -1
  16. package/dist/operation.d.ts.map +1 -1
  17. package/dist/projection-public.js +2 -2
  18. package/dist/projection-suss.js +2 -2
  19. package/dist/sdk.js +780 -88
  20. package/dist/semantic-cloud.js +2 -2
  21. package/dist/semantic.js +2 -2
  22. package/dist/sqlite/index.js +1251 -306
  23. package/dist/sqlite/port.d.ts +31 -3
  24. package/dist/sqlite/port.d.ts.map +1 -1
  25. package/dist/sqlite/store.d.ts +18 -2
  26. package/dist/sqlite/store.d.ts.map +1 -1
  27. package/dist/store.d.ts +3 -12
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/store.js +154 -32
  30. package/dist/sync.d.ts +7 -1
  31. package/dist/sync.d.ts.map +1 -1
  32. package/dist/sync.js +668 -35
  33. package/package.json +5 -1
  34. package/skills/oh/SKILL.md +42 -16
  35. package/spec/README.md +2 -2
  36. package/spec/v1/memory.md +134 -16
  37. package/spec/v1/storage.md +8 -5
  38. package/spec/v1/store.md +20 -0
  39. package/spec/v1/sync.md +77 -8
  40. package/src/cli.test.ts +53 -1
  41. package/src/cli.ts +34 -8
  42. package/src/errors.test.ts +87 -0
  43. package/src/errors.ts +185 -0
  44. package/src/graph.ts +2 -2
  45. package/src/libsql.test.ts +36 -0
  46. package/src/libsql.ts +26 -5
  47. package/src/memory.test.ts +1488 -18
  48. package/src/memory.ts +1199 -122
  49. package/src/operation.ts +13 -3
  50. package/src/sqlite/port.test.ts +209 -0
  51. package/src/sqlite/port.ts +118 -4
  52. package/src/sqlite/store.test.ts +106 -1
  53. package/src/sqlite/store.ts +168 -30
  54. package/src/store.test.ts +12 -0
  55. package/src/store.ts +30 -20
  56. package/src/sync.test.ts +570 -2
  57. package/src/sync.ts +586 -36
package/src/memory.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { isProxy } from "node:util/types";
2
3
 
3
4
  export * from "./memory-pages";
4
5
 
@@ -7,6 +8,7 @@ import {
7
8
  canonicalSha256,
8
9
  hasExactKeys,
9
10
  isPlainRecord,
11
+ orderedUnique,
10
12
  parseCanonicalInstantV1,
11
13
  parseSha256Hex,
12
14
  safeCode,
@@ -16,10 +18,13 @@ import {
16
18
  } from "./canonical";
17
19
  import { type OhRecordCodecRegistry } from "./contract";
18
20
  import {
21
+ canonicalKnowledgeGraphChangesV1,
19
22
  createKnowledgeGraphRecordV1,
20
23
  knowledgeGraphRecordRefV1,
24
+ type KnowledgeGraphChangeV1,
21
25
  type KnowledgeGraphRecordV1,
22
26
  } from "./graph";
27
+ import { OH_OPERATION_MAX_BYTES_V1, parseOhOperationV1 } from "./operation";
23
28
  import {
24
29
  OH_PROJECTION_SEMANTICS_V1,
25
30
  OH_PROJECTION_LIMITS_V1,
@@ -44,15 +49,19 @@ import {
44
49
  type OhProjectionSnapshotV1,
45
50
  } from "./projection";
46
51
  import {
52
+ OhConflictError,
47
53
  OhIntegrityError,
48
54
  OH_DEPENDENCY_CLOSURE_LIMITS_V1,
49
55
  OhProfileError,
50
56
  OhSemanticBundleIngressV1,
57
+ parseOhDependencyClosureV1,
51
58
  parseOhHeadV1,
59
+ parseOhHeadRefV1,
52
60
  parseOhStoreBindingV1,
53
61
  verifyOhDependencyClosureAgainstV1,
54
62
  type OhDependencyClosureV1,
55
63
  type OhHeadV1,
64
+ type OhHeadRefV1,
56
65
  type OhSnapshotV1,
57
66
  type OhStoreBindingV1,
58
67
  type OhStoreV1,
@@ -60,6 +69,9 @@ import {
60
69
  export const OH_MEMORY_FORMAT_VERSION_V1 = 1 as const;
61
70
  export const OH_MEMORY_CONFLICT_POLICY_V1 = "visible-conflicts.v1" as const;
62
71
  export const OH_MEMORY_LIMITS_V1 = Object.freeze({
72
+ detachedCanonicalBreadth: 65_536,
73
+ detachedCanonicalDepth: 128,
74
+ detachedCanonicalNodes: 1_048_576,
63
75
  explainCapabilityEntryBytes: 32 * 1024 * 1024,
64
76
  explainCapabilities: 256,
65
77
  explainCapabilityLifetimeMs: 15 * 60 * 1_000,
@@ -301,7 +313,7 @@ export interface OhMemoryAgentV1 {
301
313
  remember(value: unknown): Promise<OhMemoryRememberReceiptV1>;
302
314
  }
303
315
 
304
- /** Additive experimental query/pagination limits; V1 contracts are unchanged. */
316
+ /** Additive query and pagination limits; V1 contracts are unchanged. */
305
317
  export const OH_MEMORY_QUERY_LIMITS_V2 = Object.freeze({
306
318
  bindingBytes: 64 * 1024,
307
319
  bindings: 32,
@@ -410,6 +422,23 @@ export type OhMemoryQueryResultV2 = Readonly<{
410
422
  v: 2;
411
423
  }>;
412
424
 
425
+ export type OhMemoryContinuationErrorReasonV2 = "authentication" | "encoding" | "identity";
426
+
427
+ /** A caller-supplied V2 continuation cannot be decoded, authenticated, or rebound exactly. */
428
+ export class OhMemoryContinuationError extends OhIntegrityError {
429
+ declare readonly code: "memory-continuation";
430
+ declare readonly reason: OhMemoryContinuationErrorReasonV2;
431
+
432
+ constructor(reason: OhMemoryContinuationErrorReasonV2, message: string) {
433
+ super(message);
434
+ this.name = "OhMemoryContinuationError";
435
+ Object.defineProperties(this, {
436
+ code: { configurable: false, enumerable: true, value: "memory-continuation", writable: false },
437
+ reason: { configurable: false, enumerable: true, value: reason, writable: false },
438
+ });
439
+ }
440
+ }
441
+
413
442
  export type OhMemoryExplanationV2 = Readonly<{
414
443
  authority: "derived";
415
444
  explanationSha256: Sha256Hex;
@@ -434,6 +463,91 @@ export interface OhMemoryAgentV2 {
434
463
  remember(value: unknown): Promise<OhMemoryRememberReceiptV1>;
435
464
  }
436
465
 
466
+ export const OH_MEMORY_AUTHORITY_LIMITS_V1 = Object.freeze({
467
+ adoptionReplacements: 128,
468
+ adoptionRequestBytes: OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes + 192 * 1024,
469
+ canonicalAdvanceOperations: 16_384,
470
+ canonicalAdvancePages: 64,
471
+ canonicalChangeFeedPage: 1_000,
472
+ canonicalChangeFeedPageBytes: 64 * 1024 * 1024,
473
+ retainedExplanationRoutes: OH_MEMORY_LIMITS_V1.explainCapabilities,
474
+ reportedAdoptionConflicts: 128,
475
+ });
476
+
477
+ export type OhMemoryCanonicalAdvanceReceiptV1 = Readonly<{
478
+ authorityId: string;
479
+ bindingSha256: Sha256Hex;
480
+ head: OhHeadV1;
481
+ priorHead: OhHeadV1;
482
+ receiptSha256: Sha256Hex;
483
+ status: "advanced" | "unchanged";
484
+ v: 1;
485
+ }>;
486
+
487
+ export type OhMemoryAdoptionConflictEntryV1 = Readonly<{
488
+ canonicalRecordSha256: Sha256Hex | null;
489
+ key: string;
490
+ nominatedRecordSha256: Sha256Hex;
491
+ v: 1;
492
+ }>;
493
+
494
+ /** Host-only compare-and-swap evidence for one intentional canonical replacement. */
495
+ export type OhMemoryAdoptionReplacementV1 = Readonly<{
496
+ expectedPriorRecordSha256: Sha256Hex;
497
+ key: string;
498
+ v: 1;
499
+ }>;
500
+
501
+ export type OhMemoryAdoptionConflictV1 = Readonly<{
502
+ actualHead: OhHeadV1;
503
+ conflicts: readonly OhMemoryAdoptionConflictEntryV1[];
504
+ conflictsSha256: Sha256Hex;
505
+ expectedHead: OhHeadV1;
506
+ reportedConflicts: number;
507
+ totalConflicts: number;
508
+ truncated: boolean;
509
+ v: 1;
510
+ }>;
511
+
512
+ export class OhMemoryAdoptionConflictError extends OhConflictError {
513
+ declare readonly conflict: OhMemoryAdoptionConflictV1;
514
+
515
+ constructor(conflict: OhMemoryAdoptionConflictV1) {
516
+ super("The nominated records conflict with the current canonical memory head.");
517
+ this.name = "OhMemoryAdoptionConflictError";
518
+ Object.defineProperty(this, "conflict", { configurable: false, enumerable: true,
519
+ value: immutableClone(conflict), writable: false });
520
+ }
521
+ }
522
+
523
+ export type OhMemoryAdoptionReceiptV1 = Readonly<{
524
+ actorId: string;
525
+ authorityId: string;
526
+ bindingSha256: Sha256Hex;
527
+ head: OhHeadV1;
528
+ nominationSha256: Sha256Hex;
529
+ operationSha256: Sha256Hex | null;
530
+ priorHead: OhHeadV1;
531
+ receiptSha256: Sha256Hex;
532
+ status: "adopted" | "already-present";
533
+ v: 1;
534
+ }>;
535
+
536
+ export interface OhMemoryHostControlV1 {
537
+ adoptNomination(value: unknown): Promise<OhMemoryAdoptionReceiptV1>;
538
+ advanceCanonical(value: unknown): Promise<OhMemoryCanonicalAdvanceReceiptV1>;
539
+ }
540
+
541
+ export type OhMemoryAuthorityV1 = Readonly<{
542
+ agent: OhMemoryAgentV2;
543
+ host: OhMemoryHostControlV1;
544
+ }>;
545
+
546
+ export type OhMemoryAuthorityOptionsV1 = OhMemoryFacadeOptionsV2 & Readonly<{
547
+ adoptionActorId: string;
548
+ maximumCanonicalOperationBytes?: number;
549
+ }>;
550
+
437
551
  type LaneSnapshot = Readonly<{
438
552
  authorityId: string;
439
553
  binding: OhStoreBindingV1;
@@ -480,6 +594,169 @@ function immutableClone<T>(value: T): T {
480
594
  return value;
481
595
  }
482
596
 
597
+ type DetachedCanonicalData = Readonly<{
598
+ canonical: string;
599
+ value: unknown;
600
+ }>;
601
+
602
+ function canonicalStringByteLength(
603
+ value: string,
604
+ label: string,
605
+ path: string,
606
+ maximumBytes: number,
607
+ ): number {
608
+ let bytes = 2;
609
+ for (let index = 0; index < value.length; index += 1) {
610
+ const code = value.charCodeAt(index);
611
+ if (code >= 0xd800 && code <= 0xdbff) {
612
+ const next = value.charCodeAt(index + 1);
613
+ if (!(next >= 0xdc00 && next <= 0xdfff)) {
614
+ throw new TypeError(`${label} contains invalid Unicode at ${path}.`);
615
+ }
616
+ bytes += 4;
617
+ index += 1;
618
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
619
+ throw new TypeError(`${label} contains invalid Unicode at ${path}.`);
620
+ } else if (code === 0x22 || code === 0x5c || code === 0x08 || code === 0x09
621
+ || code === 0x0a || code === 0x0c || code === 0x0d) {
622
+ bytes += 2;
623
+ } else if (code <= 0x1f) {
624
+ bytes += 6;
625
+ } else if (code <= 0x7f) {
626
+ bytes += 1;
627
+ } else if (code <= 0x7ff) {
628
+ bytes += 2;
629
+ } else {
630
+ bytes += 3;
631
+ }
632
+ if (bytes > maximumBytes) {
633
+ throw new RangeError(`${label} exceeds its canonical byte bound.`);
634
+ }
635
+ }
636
+ return bytes;
637
+ }
638
+
639
+ /**
640
+ * Takes one descriptor-based JSON snapshot of an external value. No accessor is
641
+ * invoked, proxies are refused at every depth, and callers validate and execute
642
+ * only the returned frozen graph and its matching canonical bytes.
643
+ */
644
+ function detachCanonicalData(
645
+ value: unknown,
646
+ label: string,
647
+ maximumBytes: number,
648
+ ): DetachedCanonicalData {
649
+ const ancestors = new Set<object>();
650
+ let bytes = 0;
651
+ let nodes = 0;
652
+ const spendBytes = (count: number): void => {
653
+ bytes += count;
654
+ if (bytes > maximumBytes) throw new RangeError(`${label} exceeds its canonical byte bound.`);
655
+ };
656
+ const detach = (candidate: unknown, path: string, depth: number): unknown => {
657
+ if (depth > OH_MEMORY_LIMITS_V1.detachedCanonicalDepth) {
658
+ throw new RangeError(`${label} exceeds its canonical nesting depth bound.`);
659
+ }
660
+ nodes += 1;
661
+ if (nodes > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes) {
662
+ throw new RangeError(`${label} exceeds its canonical node bound.`);
663
+ }
664
+ if (candidate === null) {
665
+ spendBytes(4);
666
+ return candidate;
667
+ }
668
+ if (typeof candidate === "boolean") {
669
+ spendBytes(candidate ? 4 : 5);
670
+ return candidate;
671
+ }
672
+ if (typeof candidate === "string") {
673
+ spendBytes(canonicalStringByteLength(candidate, label, path, maximumBytes - bytes));
674
+ return candidate;
675
+ }
676
+ if (typeof candidate === "number") {
677
+ if (!Number.isFinite(candidate) || Object.is(candidate, -0)) {
678
+ throw new TypeError(`${label} contains a noncanonical number at ${path}.`);
679
+ }
680
+ spendBytes(utf8ByteLength(canonicalJson(candidate)));
681
+ return candidate;
682
+ }
683
+ if (typeof candidate !== "object") {
684
+ throw new TypeError(`${label} contains a non-JSON value at ${path}.`);
685
+ }
686
+ if (isProxy(candidate)) throw new TypeError(`${label} contains a proxy at ${path}.`);
687
+ if (ancestors.has(candidate)) throw new TypeError(`${label} contains a cycle at ${path}.`);
688
+ ancestors.add(candidate);
689
+ try {
690
+ if (Array.isArray(candidate)) {
691
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(candidate, "length");
692
+ const length = lengthDescriptor?.value;
693
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
694
+ throw new TypeError(`${label} contains an invalid array at ${path}.`);
695
+ }
696
+ if (length > OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth) {
697
+ throw new RangeError(`${label} exceeds its canonical breadth bound.`);
698
+ }
699
+ if (length > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes - nodes) {
700
+ throw new RangeError(`${label} exceeds its canonical node bound.`);
701
+ }
702
+ spendBytes(2 + Math.max(0, length - 1));
703
+ const keys = Reflect.ownKeys(candidate);
704
+ if (keys.length !== length + 1 || !keys.includes("length")
705
+ || keys.some((key) => key !== "length" && (typeof key !== "string"
706
+ || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
707
+ throw new TypeError(`${label} contains a non-data array at ${path}.`);
708
+ }
709
+ const detached: unknown[] = [];
710
+ for (let index = 0; index < length; index += 1) {
711
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
712
+ if (descriptor === undefined || !descriptor.enumerable
713
+ || descriptor.get !== undefined || descriptor.set !== undefined) {
714
+ throw new TypeError(`${label} contains a non-data array entry at ${path}[${index}].`);
715
+ }
716
+ detached.push(detach(descriptor.value, `${path}[${index}]`, depth + 1));
717
+ }
718
+ return Object.freeze(detached);
719
+ }
720
+ const prototype = Object.getPrototypeOf(candidate);
721
+ if (prototype !== Object.prototype && prototype !== null) {
722
+ throw new TypeError(`${label} contains a non-plain object at ${path}.`);
723
+ }
724
+ const keys = Reflect.ownKeys(candidate);
725
+ if (keys.some((key) => typeof key !== "string")) {
726
+ throw new TypeError(`${label} contains a symbol property at ${path}.`);
727
+ }
728
+ if (keys.length > OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth) {
729
+ throw new RangeError(`${label} exceeds its canonical breadth bound.`);
730
+ }
731
+ if (keys.length > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes - nodes) {
732
+ throw new RangeError(`${label} exceeds its canonical node bound.`);
733
+ }
734
+ spendBytes(2 + Math.max(0, keys.length - 1));
735
+ const detached: Record<string, unknown> = {};
736
+ for (const key of keys as string[]) {
737
+ spendBytes(canonicalStringByteLength(key, label, `${path}.<key>`,
738
+ maximumBytes - bytes - 1) + 1);
739
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
740
+ if (descriptor === undefined || !descriptor.enumerable
741
+ || descriptor.get !== undefined || descriptor.set !== undefined) {
742
+ throw new TypeError(`${label} contains a non-data property at ${path}.${key}.`);
743
+ }
744
+ Object.defineProperty(detached, key, { configurable: false, enumerable: true,
745
+ value: detach(descriptor.value, `${path}.${key}`, depth + 1), writable: false });
746
+ }
747
+ return Object.freeze(detached);
748
+ } finally {
749
+ ancestors.delete(candidate);
750
+ }
751
+ };
752
+ const detached = detach(value, "$root", 0);
753
+ const canonical = canonicalJson(detached);
754
+ if (utf8ByteLength(canonical) !== bytes) {
755
+ throw new OhIntegrityError(`${label} canonical byte accounting did not reproduce its snapshot.`);
756
+ }
757
+ return Object.freeze({ canonical, value: detached });
758
+ }
759
+
483
760
  function compareText(left: string, right: string): number {
484
761
  return left < right ? -1 : left > right ? 1 : 0;
485
762
  }
@@ -517,15 +794,56 @@ function laneIdentity(value: LaneSnapshot): OhMemoryLaneIdentityV1 {
517
794
  });
518
795
  }
519
796
 
520
- function datasetForSnapshot(binding: OhStoreBindingV1, snapshot: OhSnapshotV1): Readonly<{
797
+ function parseDetachedStoreHead(value: unknown, label: string): OhHeadV1 {
798
+ const head = parseOhHeadV1(detachCanonicalData(value, `${label} head`, 4 * 1024).value);
799
+ if (head === null) throw new OhIntegrityError(`${label} returned an invalid head.`);
800
+ return immutableClone(head);
801
+ }
802
+
803
+ function parseDetachedStoreSnapshot(
804
+ value: unknown,
805
+ label: string,
806
+ expectedHead: OhHeadV1,
807
+ spaceId: string,
808
+ ): Readonly<{ projectionSnapshot: OhProjectionSnapshotV1; snapshot: OhSnapshotV1 }> {
809
+ const detachedSnapshot = detachCanonicalData(value, `${label} snapshot`,
810
+ OH_MEMORY_LIMITS_V1.snapshotBytesPerLane);
811
+ if (!isPlainRecord(detachedSnapshot.value)
812
+ || !hasExactKeys(detachedSnapshot.value, ["head", "records", "v"])
813
+ || detachedSnapshot.value.v !== 1 || !Array.isArray(detachedSnapshot.value.records)) {
814
+ throw new OhIntegrityError(`${label} returned an invalid snapshot envelope.`);
815
+ }
816
+ const detached = detachedSnapshot.value as Record<string, unknown>;
817
+ const detachedHead = parseOhHeadV1(detached.head);
818
+ if (detachedHead === null) throw new OhIntegrityError(`${label} returned an invalid snapshot head.`);
819
+ const snapshot: OhSnapshotV1 = immutableClone({ head: detachedHead,
820
+ records: detached.records as OhSnapshotV1["records"], v: 1 });
821
+ if (!exactHead(snapshot.head, expectedHead)) {
822
+ throw new OhIntegrityError(`${label} snapshot differs from its pinned head.`);
823
+ }
824
+ let projectionSnapshot: OhProjectionSnapshotV1;
825
+ try {
826
+ projectionSnapshot = createOhProjectionSnapshotV1({
827
+ head: snapshot.head,
828
+ records: snapshot.records,
829
+ spaceId,
830
+ });
831
+ } catch {
832
+ throw new OhIntegrityError(`${label} returned invalid snapshot records.`);
833
+ }
834
+ return Object.freeze({ projectionSnapshot, snapshot });
835
+ }
836
+
837
+ function datasetForSnapshot(
838
+ binding: OhStoreBindingV1,
839
+ snapshot: OhSnapshotV1,
840
+ validatedProjectionSnapshot?: OhProjectionSnapshotV1,
841
+ ): Readonly<{
521
842
  dataset: OhProjectionDatasetV1;
522
843
  projectionSnapshot: OhProjectionSnapshotV1;
523
844
  }> {
524
- const projectionSnapshot = createOhProjectionSnapshotV1({
525
- head: snapshot.head,
526
- records: snapshot.records,
527
- spaceId: binding.spaceId,
528
- });
845
+ const projectionSnapshot = validatedProjectionSnapshot ?? createOhProjectionSnapshotV1({
846
+ head: snapshot.head, records: snapshot.records, spaceId: binding.spaceId });
529
847
  const dataset = createOhProjectionDatasetV1({
530
848
  extractorSha256: canonicalSha256({ extractor: "oh.memory.lane-structural", v: 1 }),
531
849
  factPackId: "oh.memory.lane-structural",
@@ -541,32 +859,21 @@ async function readLane(
541
859
  lane: OhMemoryLaneV1,
542
860
  expectedHead?: OhHeadV1,
543
861
  ): Promise<LaneSnapshot> {
544
- const returnedHead = expectedHead ?? await authority.store.head();
545
- const head = parseOhHeadV1(immutableClone(returnedHead));
546
- if (head === null) throw new OhIntegrityError(`The ${lane} store returned an invalid head.`);
862
+ const label = `The ${lane} store`;
863
+ const head = expectedHead === undefined
864
+ ? parseDetachedStoreHead(await authority.store.head(), label)
865
+ : immutableClone(expectedHead);
547
866
  const returnedSnapshot = await authority.store.snapshot({
548
867
  head: { operationSha256: head.operationSha256, sequence: head.sequence },
549
868
  maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
550
869
  });
551
- if (!isPlainRecord(returnedSnapshot)
552
- || !hasExactKeys(returnedSnapshot, ["head", "records", "v"])
553
- || returnedSnapshot.v !== 1 || !Array.isArray(returnedSnapshot.records)) {
554
- throw new OhIntegrityError(`The ${lane} store returned an invalid snapshot envelope.`);
555
- }
556
- const detached = immutableClone(returnedSnapshot);
557
- const detachedHead = parseOhHeadV1(detached.head);
558
- if (detachedHead === null) throw new OhIntegrityError(`The ${lane} store returned an invalid snapshot head.`);
559
- const snapshot: OhSnapshotV1 = immutableClone({ head: detachedHead,
560
- records: detached.records, v: 1 });
561
- if (!exactHead(snapshot.head, head)) {
562
- throw new OhIntegrityError(`The ${lane} snapshot differs from its pinned head.`);
563
- }
564
- if (utf8ByteLength(canonicalJson(snapshot)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
565
- throw new RangeError(`The ${lane} memory snapshot exceeds its canonical byte bound.`);
566
- }
567
- const projected = datasetForSnapshot(authority.binding, snapshot);
870
+ const parsed = parseDetachedStoreSnapshot(returnedSnapshot, label, head,
871
+ authority.binding.spaceId);
872
+ const projected = datasetForSnapshot(authority.binding, parsed.snapshot,
873
+ parsed.projectionSnapshot);
568
874
  return Object.freeze({ authorityId: authority.authorityId, binding: authority.binding,
569
- dataset: projected.dataset, lane, projectionSnapshot: projected.projectionSnapshot, snapshot });
875
+ dataset: projected.dataset, lane, projectionSnapshot: projected.projectionSnapshot,
876
+ snapshot: parsed.snapshot });
570
877
  }
571
878
 
572
879
  function syntheticKey(lane: OhMemoryLaneV1, recordSha256: Sha256Hex): string {
@@ -845,9 +1152,11 @@ ReadonlyMap<string, OhMemoryNominationRouteV1> {
845
1152
  }
846
1153
 
847
1154
  function parseQueryRequest(value: unknown): Readonly<{ programId: string }> {
848
- if (!isPlainRecord(value) || !hasExactKeys(value, ["programId", "v"])
849
- || value.v !== 1) throw new TypeError("Invalid named memory query.");
850
- const programId = safeCode(value.programId, 128);
1155
+ const detached = detachCanonicalData(value, "The named memory query",
1156
+ OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
1157
+ if (!isPlainRecord(detached) || !hasExactKeys(detached, ["programId", "v"])
1158
+ || detached.v !== 1) throw new TypeError("Invalid named memory query.");
1159
+ const programId = safeCode(detached.programId, 128);
851
1160
  if (programId === null) throw new TypeError("Invalid named memory query identity.");
852
1161
  return { programId };
853
1162
  }
@@ -857,32 +1166,81 @@ function parseExplainRequest(value: unknown): Readonly<{
857
1166
  resultSha256: Sha256Hex;
858
1167
  token: string;
859
1168
  }> {
860
- if (!isPlainRecord(value) || !hasExactKeys(value, ["resultSha256", "row", "token", "v"])
861
- || value.v !== 1 || typeof value.token !== "string" || value.token.length !== 43
862
- || !Number.isSafeInteger(value.row) || (value.row as number) < 0) {
1169
+ const detached = detachCanonicalData(value, "The memory explanation request",
1170
+ OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
1171
+ if (!isPlainRecord(detached)
1172
+ || !hasExactKeys(detached, ["resultSha256", "row", "token", "v"])
1173
+ || detached.v !== 1 || typeof detached.token !== "string" || detached.token.length !== 43
1174
+ || !Number.isSafeInteger(detached.row) || (detached.row as number) < 0) {
863
1175
  throw new TypeError("Invalid memory explanation request.");
864
1176
  }
865
- const resultSha256 = parseSha256Hex(value.resultSha256);
1177
+ const resultSha256 = parseSha256Hex(detached.resultSha256);
866
1178
  if (resultSha256 === null) throw new TypeError("Invalid memory explanation result identity.");
867
- return { resultSha256, row: value.row as number, token: value.token };
1179
+ return { resultSha256, row: detached.row as number, token: detached.token };
868
1180
  }
869
1181
 
870
1182
  function parseNominationRequest(value: unknown): Readonly<{
871
1183
  nominationId: string;
872
1184
  roots: readonly string[];
873
1185
  }> {
874
- if (!isPlainRecord(value) || !hasExactKeys(value, ["nominationId", "roots", "v"])
875
- || value.v !== 1 || !Array.isArray(value.roots) || value.roots.length < 1
876
- || value.roots.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
1186
+ const detached = detachCanonicalData(value, "The memory nomination request",
1187
+ OH_MEMORY_QUERY_LIMITS_V2.requestBytes * 8).value;
1188
+ if (!isPlainRecord(detached) || !hasExactKeys(detached, ["nominationId", "roots", "v"])
1189
+ || detached.v !== 1 || !Array.isArray(detached.roots) || detached.roots.length < 1
1190
+ || detached.roots.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
877
1191
  throw new TypeError("Invalid memory nomination request.");
878
1192
  }
879
- const nominationId = safeCode(value.nominationId, 128);
880
- const roots = value.roots.map((root) => safeCode(root, 512)).sort();
1193
+ const nominationId = safeCode(detached.nominationId, 128);
1194
+ const roots = detached.roots.map((root) => safeCode(root, 512)).sort();
881
1195
  if (nominationId === null || roots.some((root) => root === null)
882
1196
  || new Set(roots).size !== roots.length) throw new TypeError("Invalid memory nomination identity.");
883
1197
  return { nominationId, roots: roots as readonly string[] };
884
1198
  }
885
1199
 
1200
+ function parseDetachedMemoryNominationV1(value: unknown): OhMemoryNominationV1 | null {
1201
+ try {
1202
+ const keys = ownDataKeysV2(value, 7, "The memory nomination");
1203
+ if (keys.length !== 7 || !["closure", "destinationPurpose", "nominationId",
1204
+ "nominationSha256", "source", "status", "v"].every((key) => keys.includes(key))) return null;
1205
+ const record = value as Record<string, unknown>;
1206
+ if (record.status !== "prepared" || record.v !== 1) return null;
1207
+ const closure = parseOhDependencyClosureV1(record.closure);
1208
+ const destinationPurpose = safeCode(record.destinationPurpose, 256);
1209
+ const nominationId = safeCode(record.nominationId, 128);
1210
+ const nominationSha256 = parseSha256Hex(record.nominationSha256);
1211
+ if (closure === null || destinationPurpose === null || nominationId === null
1212
+ || nominationSha256 === null || closure.binding.profile.profileKind !== "working"
1213
+ || [...ownDataKeysV2(record.source, 5, "The memory nomination source")].sort().join("\0")
1214
+ !== ["authorityId", "bindingSha256", "head", "lane", "v"].sort().join("\0")) return null;
1215
+ const sourceRecord = record.source as Record<string, unknown>;
1216
+ if (sourceRecord.lane !== "working" || sourceRecord.v !== 1) return null;
1217
+ const authorityIdValue = safeCode(sourceRecord.authorityId, 128);
1218
+ const bindingSha256 = parseSha256Hex(sourceRecord.bindingSha256);
1219
+ const head = parseOhHeadV1(sourceRecord.head);
1220
+ if (authorityIdValue === null || bindingSha256 === null || head === null
1221
+ || bindingSha256 !== closure.binding.bindingSha256
1222
+ || !exactHead(head, closure.head)) return null;
1223
+ const source = { authorityId: authorityIdValue, bindingSha256, head,
1224
+ lane: "working" as const, v: 1 as const };
1225
+ const payload = { closure, destinationPurpose, nominationId, source,
1226
+ status: "prepared" as const, v: 1 as const };
1227
+ return canonicalSha256(payload) === nominationSha256
1228
+ ? immutableClone({ ...payload, nominationSha256 }) : null;
1229
+ } catch {
1230
+ return null;
1231
+ }
1232
+ }
1233
+
1234
+ export function parseOhMemoryNominationV1(value: unknown): OhMemoryNominationV1 | null {
1235
+ try {
1236
+ const detached = detachCanonicalData(value, "The memory nomination",
1237
+ OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionRequestBytes);
1238
+ return parseDetachedMemoryNominationV1(detached.value);
1239
+ } catch {
1240
+ return null;
1241
+ }
1242
+ }
1243
+
886
1244
  function isoInstant(date: Date): string {
887
1245
  const value = date.toISOString();
888
1246
  if (parseCanonicalInstantV1(value) === null) throw new TypeError("The memory clock returned an invalid instant.");
@@ -923,12 +1281,15 @@ export async function createOhMemoryAgentV1(options: OhMemoryFacadeOptionsV1): P
923
1281
  options.canonical.expectedBindingSha256, "canonical");
924
1282
  const workingBinding = bindingFor(workingStore,
925
1283
  options.working.expectedBindingSha256, "working");
926
- const expectedCanonicalHead = parseOhHeadV1(options.canonical.expectedHead);
927
- if (expectedCanonicalHead === null) throw new TypeError("Invalid pinned canonical memory head.");
1284
+ const expectedCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead,
1285
+ "pinned canonical");
928
1286
  const programs = resolvePrograms(options.programs);
929
1287
  const extractors = resolveExtractors(options.extractors ?? []);
930
1288
  const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
931
- const ingress = new OhSemanticBundleIngressV1(workingStore, workingCodecs);
1289
+ const ingress = new OhSemanticBundleIngressV1(
1290
+ capacityGuardedWorkingStore(workingStore, workingBinding),
1291
+ workingCodecs,
1292
+ );
932
1293
  const now = options.now ?? (() => new Date());
933
1294
  const monotonicNow = options.monotonicNow ?? (() => performance.now());
934
1295
  const capabilityLifetime = options.explainCapabilityLifetimeMs
@@ -961,20 +1322,25 @@ export async function createOhMemoryAgentV1(options: OhMemoryFacadeOptionsV1): P
961
1322
  };
962
1323
 
963
1324
  const remember = async (value: unknown): Promise<OhMemoryRememberReceiptV1> => {
964
- if (utf8ByteLength(canonicalJson(value)) > OH_MEMORY_LIMITS_V1.rememberBytes) {
965
- throw new RangeError("The memory semantic bundle exceeds its canonical byte bound.");
966
- }
967
- if (!isPlainRecord(value) || !hasExactKeys(value,
968
- ["expectedHead", "puts", "requestId", "tombstones", "v"]) || value.v !== 1) {
1325
+ const detached = detachCanonicalData(value, "The memory semantic bundle",
1326
+ OH_MEMORY_LIMITS_V1.rememberBytes).value;
1327
+ if (!isPlainRecord(detached) || !hasExactKeys(detached,
1328
+ ["expectedHead", "puts", "requestId", "tombstones", "v"]) || detached.v !== 1) {
969
1329
  throw new TypeError("Invalid memory remember request.");
970
1330
  }
971
- const requestId = safeCode(value.requestId, 128);
1331
+ const requestId = safeCode(detached.requestId, 128);
972
1332
  if (requestId === null) throw new TypeError("Invalid memory remember request identity.");
973
1333
  const operationId = `memory_${canonicalSha256({ actorId: memoryActorId,
974
1334
  bindingSha256: workingBinding.bindingSha256, requestId, v: 1 }).slice(0, 48)}`;
975
- const operation = await ingress.commit({ actorId: memoryActorId,
976
- expectedHead: value.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
977
- puts: value.puts, tombstones: value.tombstones, v: 1 });
1335
+ const returnedOperation = await ingress.commit({ actorId: memoryActorId,
1336
+ expectedHead: detached.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
1337
+ puts: detached.puts, tombstones: detached.tombstones, v: 1 });
1338
+ const operation = parseOhOperationV1(detachCanonicalData(returnedOperation,
1339
+ "The returned working memory operation", OH_MEMORY_LIMITS_V1.rememberBytes * 2).value);
1340
+ if (operation === null || operation.actorId !== memoryActorId
1341
+ || operation.operationId !== operationId || operation.spaceId !== workingBinding.spaceId) {
1342
+ throw new OhIntegrityError("The working authority returned a different memory operation.");
1343
+ }
978
1344
  const head: OhHeadV1 = { generation: operation.sequence,
979
1345
  graphRevisionSha256: operation.graphRevisionSha256,
980
1346
  operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
@@ -1075,10 +1441,13 @@ export async function createOhMemoryAgentV1(options: OhMemoryFacadeOptionsV1): P
1075
1441
  const request = parseNominationRequest(value);
1076
1442
  const route = nominationRoutes.get(request.nominationId);
1077
1443
  if (route === undefined) throw new TypeError("Unknown named memory nomination route.");
1078
- const head = parseOhHeadV1(immutableClone(await workingStore.head()));
1444
+ const head = parseOhHeadV1(detachCanonicalData(await workingStore.head(),
1445
+ "The working nomination store head", 4 * 1024).value);
1079
1446
  if (head === null) throw new OhIntegrityError("The working nomination store returned an invalid head.");
1080
- const closure = await workingStore.exportDependencyClosure({ head: {
1447
+ const returnedClosure = await workingStore.exportDependencyClosure({ head: {
1081
1448
  operationSha256: head.operationSha256, sequence: head.sequence }, roots: request.roots });
1449
+ const closure = detachCanonicalData(returnedClosure, "The working nomination closure",
1450
+ OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes).value;
1082
1451
  const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
1083
1452
  if (!verified.ok) throw new OhIntegrityError("The working nomination closure failed exact verification.");
1084
1453
  if (canonicalJson(verified.closure.roots) !== canonicalJson(request.roots)) {
@@ -1108,6 +1477,34 @@ type StoredExplanationV2 = Readonly<{
1108
1477
  rows: readonly OhMemoryResultRowV2[];
1109
1478
  }>;
1110
1479
 
1480
+ type OhMemoryRuntimeV2 = {
1481
+ readonly capabilityLifetime: number;
1482
+ explanationBytes: number;
1483
+ readonly explanations: Map<string, StoredExplanationV2>;
1484
+ lastMonotonicMs: number;
1485
+ lastWallClockMs: number;
1486
+ readonly monotonicNow: () => number;
1487
+ readonly now: () => Date;
1488
+ };
1489
+
1490
+ function createOhMemoryRuntimeV2(options: OhMemoryFacadeOptionsV2): OhMemoryRuntimeV2 {
1491
+ const capabilityLifetime = options.explainCapabilityLifetimeMs
1492
+ ?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
1493
+ if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1_000
1494
+ || capabilityLifetime > 60 * 60 * 1_000) {
1495
+ throw new RangeError("Invalid memory explanation capability lifetime.");
1496
+ }
1497
+ return {
1498
+ capabilityLifetime,
1499
+ explanationBytes: 0,
1500
+ explanations: new Map<string, StoredExplanationV2>(),
1501
+ lastMonotonicMs: -1,
1502
+ lastWallClockMs: Number.NEGATIVE_INFINITY,
1503
+ monotonicNow: options.monotonicNow ?? (() => performance.now()),
1504
+ now: options.now ?? (() => new Date()),
1505
+ };
1506
+ }
1507
+
1111
1508
  type OhMemoryContinuationIdentityV2 = Readonly<{
1112
1509
  bindingsSha256: Sha256Hex;
1113
1510
  memorySha256: Sha256Hex;
@@ -1268,28 +1665,51 @@ function parseQueryRequestV2(value: unknown): Readonly<{
1268
1665
  continuation: string | null;
1269
1666
  programId: string;
1270
1667
  }> {
1668
+ let detached: unknown;
1669
+ let detachedCanonical = "";
1271
1670
  let keys: readonly string[];
1272
1671
  try {
1273
- keys = ownDataKeysV2(value, 4, "The parameterized memory query");
1274
- } catch {
1672
+ const detachedData = detachCanonicalData(value, "The parameterized memory query",
1673
+ OH_MEMORY_QUERY_LIMITS_V2.requestBytes);
1674
+ detached = detachedData.value;
1675
+ detachedCanonical = detachedData.canonical;
1676
+ } catch (error) {
1677
+ if (error instanceof RangeError) throw error;
1678
+ if (error instanceof Error && error.message.includes("$root.bindings")) {
1679
+ throw new TypeError("Memory bindings must be JSON primitives.");
1680
+ }
1681
+ throw new TypeError("Invalid parameterized memory query.");
1682
+ }
1683
+ try {
1684
+ keys = ownDataKeysV2(detached, 4, "The parameterized memory query");
1685
+ } catch (error) {
1686
+ if (error instanceof Error && error.message.includes("$root.bindings")) {
1687
+ throw new TypeError("Memory bindings must be JSON primitives.");
1688
+ }
1275
1689
  throw new TypeError("Invalid parameterized memory query.");
1276
1690
  }
1277
1691
  if (keys.length !== 4 || !["bindings", "continuation", "programId", "v"]
1278
1692
  .every((key) => keys.includes(key))) {
1279
1693
  throw new TypeError("Invalid parameterized memory query.");
1280
1694
  }
1281
- const record = value as Record<string, unknown>;
1282
- if (record.v !== 2 || (record.continuation !== null
1283
- && typeof record.continuation !== "string")) {
1695
+ if (utf8ByteLength(detachedCanonical) > OH_MEMORY_QUERY_LIMITS_V2.requestBytes) {
1696
+ throw new RangeError("The parameterized memory query exceeds its canonical byte bound.");
1697
+ }
1698
+ const record = detached as Record<string, unknown>;
1699
+ if (record.v !== 2) {
1284
1700
  throw new TypeError("Invalid parameterized memory query.");
1285
1701
  }
1702
+ if (record.continuation !== null && typeof record.continuation !== "string") {
1703
+ throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
1704
+ }
1286
1705
  const programId = safeCode(record.programId, 128);
1287
1706
  if (programId === null) throw new TypeError("Invalid parameterized memory query identity.");
1288
1707
  const continuation = record.continuation as string | null;
1289
1708
  if (typeof continuation === "string" && (continuation.length < 1
1290
1709
  || continuation.length > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes
1291
1710
  || utf8ByteLength(continuation) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes)) {
1292
- throw new RangeError("The memory continuation exceeds its byte bound.");
1711
+ throw new OhMemoryContinuationError("encoding",
1712
+ "The memory continuation exceeds its byte bound.");
1293
1713
  }
1294
1714
  const bindingKeys = ownDataKeysV2(record.bindings, OH_MEMORY_QUERY_LIMITS_V2.bindings,
1295
1715
  "The parameterized memory query bindings");
@@ -1369,20 +1789,26 @@ function encodeContinuationV2(value: OhMemoryContinuationIdentityV2,
1369
1789
 
1370
1790
  function parseContinuationV2(value: string, key: Uint8Array): OhMemoryContinuationV2 {
1371
1791
  if (value.length < 1 || utf8ByteLength(value) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes
1372
- || !/^[A-Za-z0-9_-]+$/u.test(value)) throw new TypeError("Invalid memory continuation encoding.");
1792
+ || !/^[A-Za-z0-9_-]+$/u.test(value)) {
1793
+ throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
1794
+ }
1373
1795
  const bytes = Buffer.from(value, "base64url");
1374
1796
  if (bytes.toString("base64url") !== value
1375
1797
  || bytes.byteLength > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes) {
1376
- throw new TypeError("Invalid memory continuation encoding.");
1798
+ throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
1377
1799
  }
1378
1800
  const text = bytes.toString("utf8");
1379
1801
  let decoded: unknown;
1380
- try { decoded = JSON.parse(text); } catch { throw new TypeError("Invalid memory continuation JSON."); }
1802
+ try { decoded = JSON.parse(text); } catch {
1803
+ throw new OhMemoryContinuationError("encoding", "Invalid memory continuation JSON.");
1804
+ }
1381
1805
  if (!isPlainRecord(decoded)
1382
1806
  || !hasExactKeys(decoded, ["bindingsSha256", "continuationHmacSha256", "continuationSha256",
1383
1807
  "memorySha256", "nextOffset", "pageSize", "programSha256", "projectionResultSha256",
1384
1808
  "totalRows", "v"])
1385
- || decoded.v !== 2) throw new TypeError("Invalid memory continuation payload.");
1809
+ || decoded.v !== 2) {
1810
+ throw new OhMemoryContinuationError("encoding", "Invalid memory continuation payload.");
1811
+ }
1386
1812
  const bindingsSha256 = parseSha256Hex(decoded.bindingsSha256);
1387
1813
  const continuationHmacSha256 = parseSha256Hex(decoded.continuationHmacSha256);
1388
1814
  const continuationSha256 = parseSha256Hex(decoded.continuationSha256);
@@ -1400,7 +1826,7 @@ function parseContinuationV2(value: string, key: Uint8Array): OhMemoryContinuati
1400
1826
  || (decoded.totalRows as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows
1401
1827
  || (decoded.nextOffset as number) >= (decoded.totalRows as number)
1402
1828
  || (decoded.nextOffset as number) % (decoded.pageSize as number) !== 0) {
1403
- throw new TypeError("Invalid memory continuation identity.");
1829
+ throw new OhMemoryContinuationError("identity", "Invalid memory continuation identity.");
1404
1830
  }
1405
1831
  const identity: OhMemoryContinuationIdentityV2 = { bindingsSha256, memorySha256,
1406
1832
  nextOffset: decoded.nextOffset as number,
@@ -1408,14 +1834,17 @@ function parseContinuationV2(value: string, key: Uint8Array): OhMemoryContinuati
1408
1834
  totalRows: decoded.totalRows as number, v: 2 as const };
1409
1835
  const signed: OhMemoryContinuationV2 = { ...identity, continuationSha256 };
1410
1836
  const envelope: OhMemoryContinuationEnvelopeV2 = { ...signed, continuationHmacSha256 };
1411
- if (canonicalJson(envelope) !== text) throw new TypeError("Invalid memory continuation payload.");
1837
+ if (canonicalJson(envelope) !== text) {
1838
+ throw new OhMemoryContinuationError("encoding", "Invalid memory continuation payload.");
1839
+ }
1412
1840
  const expectedHmac = continuationHmacV2(key, signed);
1413
1841
  const receivedHmac = Buffer.from(continuationHmacSha256, "hex");
1414
1842
  if (!timingSafeEqual(expectedHmac, receivedHmac)) {
1415
- throw new OhIntegrityError("The memory continuation is not an issued capability.");
1843
+ throw new OhMemoryContinuationError("authentication",
1844
+ "The memory continuation is not an issued capability.");
1416
1845
  }
1417
1846
  if (canonicalSha256(identity) !== continuationSha256) {
1418
- throw new OhIntegrityError("The memory continuation digest is invalid.");
1847
+ throw new OhMemoryContinuationError("identity", "The memory continuation digest is invalid.");
1419
1848
  }
1420
1849
  return Object.freeze(signed);
1421
1850
  }
@@ -1425,14 +1854,17 @@ function parseExplainRequestV2(value: unknown): Readonly<{
1425
1854
  resultSha256: Sha256Hex;
1426
1855
  token: string;
1427
1856
  }> {
1428
- if (!isPlainRecord(value) || !hasExactKeys(value, ["pageRow", "resultSha256", "token", "v"])
1429
- || value.v !== 2 || typeof value.token !== "string" || value.token.length !== 43
1430
- || !Number.isSafeInteger(value.pageRow) || (value.pageRow as number) < 0) {
1857
+ const detached = detachCanonicalData(value, "The V2 memory explanation request",
1858
+ OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
1859
+ if (!isPlainRecord(detached)
1860
+ || !hasExactKeys(detached, ["pageRow", "resultSha256", "token", "v"])
1861
+ || detached.v !== 2 || typeof detached.token !== "string" || detached.token.length !== 43
1862
+ || !Number.isSafeInteger(detached.pageRow) || (detached.pageRow as number) < 0) {
1431
1863
  throw new TypeError("Invalid V2 memory explanation request.");
1432
1864
  }
1433
- const resultSha256 = parseSha256Hex(value.resultSha256);
1865
+ const resultSha256 = parseSha256Hex(detached.resultSha256);
1434
1866
  if (resultSha256 === null) throw new TypeError("Invalid V2 memory explanation result identity.");
1435
- return { pageRow: value.pageRow as number, resultSha256, token: value.token };
1867
+ return { pageRow: detached.pageRow as number, resultSha256, token: detached.token };
1436
1868
  }
1437
1869
 
1438
1870
  /**
@@ -1440,7 +1872,10 @@ function parseExplainRequestV2(value: unknown): Readonly<{
1440
1872
  * bindings and fail-closed stable pagination; V1 request and digest contracts
1441
1873
  * remain untouched.
1442
1874
  */
1443
- export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): Promise<OhMemoryAgentV2> {
1875
+ async function createOhMemoryAgentV2WithRuntime(
1876
+ options: OhMemoryFacadeOptionsV2,
1877
+ sharedRuntime?: OhMemoryRuntimeV2,
1878
+ ): Promise<OhMemoryAgentV2> {
1444
1879
  const memoryActorId = safeCode(options.actorId, 128);
1445
1880
  if (memoryActorId === null) throw new TypeError("Invalid host-bound memory actor ID.");
1446
1881
  const continuationKey = continuationKeyV2(options.continuationKey);
@@ -1456,58 +1891,61 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1456
1891
  options.canonical.expectedBindingSha256, "canonical");
1457
1892
  const workingBinding = bindingFor(workingStore,
1458
1893
  options.working.expectedBindingSha256, "working");
1459
- const expectedCanonicalHead = parseOhHeadV1(options.canonical.expectedHead);
1460
- if (expectedCanonicalHead === null) throw new TypeError("Invalid pinned canonical memory head.");
1894
+ const expectedCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead,
1895
+ "pinned canonical");
1461
1896
  const programs = resolveProgramsV2(options.programs);
1462
1897
  const extractors = resolveExtractors(options.extractors ?? []);
1463
1898
  const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
1464
- const ingress = new OhSemanticBundleIngressV1(workingStore, workingCodecs);
1465
- const now = options.now ?? (() => new Date());
1466
- const monotonicNow = options.monotonicNow ?? (() => performance.now());
1467
- const capabilityLifetime = options.explainCapabilityLifetimeMs
1468
- ?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
1469
- if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1_000
1470
- || capabilityLifetime > 60 * 60 * 1_000) {
1471
- throw new RangeError("Invalid memory explanation capability lifetime.");
1472
- }
1899
+ const ingress = new OhSemanticBundleIngressV1(
1900
+ capacityGuardedWorkingStore(workingStore, workingBinding),
1901
+ workingCodecs,
1902
+ );
1903
+ const runtime = sharedRuntime ?? createOhMemoryRuntimeV2(options);
1473
1904
  const canonical = await readLane({ authorityId: canonicalAuthorityId,
1474
1905
  binding: canonicalBinding, store: canonicalStore }, "canonical", expectedCanonicalHead);
1475
- const explanations = new Map<string, StoredExplanationV2>();
1476
- let explanationBytes = 0;
1477
- let lastMonotonicMs = -1;
1478
- let lastWallClockMs = Number.NEGATIVE_INFINITY;
1479
1906
  const wallClock = () => {
1480
- const milliseconds = clockMilliseconds(now);
1481
- if (milliseconds < lastWallClockMs) throw new OhProfileError("The memory wall clock regressed.");
1482
- lastWallClockMs = milliseconds;
1907
+ const milliseconds = clockMilliseconds(runtime.now);
1908
+ if (milliseconds < runtime.lastWallClockMs) {
1909
+ throw new OhProfileError("The memory wall clock regressed.");
1910
+ }
1911
+ runtime.lastWallClockMs = milliseconds;
1483
1912
  return milliseconds;
1484
1913
  };
1485
1914
  const monotonicClock = () => {
1486
- const milliseconds = monotonicMilliseconds(monotonicNow);
1487
- if (milliseconds < lastMonotonicMs) throw new OhProfileError("The memory monotonic clock regressed.");
1488
- lastMonotonicMs = milliseconds;
1915
+ const milliseconds = monotonicMilliseconds(runtime.monotonicNow);
1916
+ if (milliseconds < runtime.lastMonotonicMs) {
1917
+ throw new OhProfileError("The memory monotonic clock regressed.");
1918
+ }
1919
+ runtime.lastMonotonicMs = milliseconds;
1489
1920
  return milliseconds;
1490
1921
  };
1491
1922
  const deleteExplanation = (token: string) => {
1492
- const stored = explanations.get(token);
1493
- if (stored !== undefined && explanations.delete(token)) explanationBytes -= stored.bytes;
1923
+ const stored = runtime.explanations.get(token);
1924
+ if (stored !== undefined && runtime.explanations.delete(token)) {
1925
+ runtime.explanationBytes -= stored.bytes;
1926
+ }
1494
1927
  };
1495
1928
 
1496
1929
  const remember = async (value: unknown): Promise<OhMemoryRememberReceiptV1> => {
1497
- if (utf8ByteLength(canonicalJson(value)) > OH_MEMORY_LIMITS_V1.rememberBytes) {
1498
- throw new RangeError("The memory semantic bundle exceeds its canonical byte bound.");
1499
- }
1500
- if (!isPlainRecord(value) || !hasExactKeys(value,
1501
- ["expectedHead", "puts", "requestId", "tombstones", "v"]) || value.v !== 1) {
1930
+ const detached = detachCanonicalData(value, "The memory semantic bundle",
1931
+ OH_MEMORY_LIMITS_V1.rememberBytes).value;
1932
+ if (!isPlainRecord(detached) || !hasExactKeys(detached,
1933
+ ["expectedHead", "puts", "requestId", "tombstones", "v"]) || detached.v !== 1) {
1502
1934
  throw new TypeError("Invalid memory remember request.");
1503
1935
  }
1504
- const requestId = safeCode(value.requestId, 128);
1936
+ const requestId = safeCode(detached.requestId, 128);
1505
1937
  if (requestId === null) throw new TypeError("Invalid memory remember request identity.");
1506
1938
  const operationId = `memory_${canonicalSha256({ actorId: memoryActorId,
1507
1939
  bindingSha256: workingBinding.bindingSha256, requestId, v: 1 }).slice(0, 48)}`;
1508
- const operation = await ingress.commit({ actorId: memoryActorId,
1509
- expectedHead: value.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
1510
- puts: value.puts, tombstones: value.tombstones, v: 1 });
1940
+ const returnedOperation = await ingress.commit({ actorId: memoryActorId,
1941
+ expectedHead: detached.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
1942
+ puts: detached.puts, tombstones: detached.tombstones, v: 1 });
1943
+ const operation = parseOhOperationV1(detachCanonicalData(returnedOperation,
1944
+ "The returned working memory operation", OH_MEMORY_LIMITS_V1.rememberBytes * 2).value);
1945
+ if (operation === null || operation.actorId !== memoryActorId
1946
+ || operation.operationId !== operationId || operation.spaceId !== workingBinding.spaceId) {
1947
+ throw new OhIntegrityError("The working authority returned a different memory operation.");
1948
+ }
1511
1949
  const head: OhHeadV1 = { generation: operation.sequence,
1512
1950
  graphRevisionSha256: operation.graphRevisionSha256,
1513
1951
  operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
@@ -1533,7 +1971,8 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1533
1971
  || requestedContinuation.totalRows > program.maximumRows
1534
1972
  || requestedContinuation.nextOffset >= requestedContinuation.totalRows
1535
1973
  || requestedContinuation.nextOffset % program.pageSize !== 0)) {
1536
- throw new OhIntegrityError("The memory continuation does not match this exact program, binding, and page identity.");
1974
+ throw new OhMemoryContinuationError("identity",
1975
+ "The memory continuation does not match this exact program, binding, and page identity.");
1537
1976
  }
1538
1977
  const boundQuery = bindQueryV2(program.query, bound.bindings);
1539
1978
  const working = await readLane({ authorityId: workingAuthorityId,
@@ -1571,13 +2010,15 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1571
2010
  if (requestedContinuation !== null
1572
2011
  && (requestedContinuation.memorySha256 !== identity.memorySha256
1573
2012
  || requestedContinuation.projectionResultSha256 !== projection.resultSha256)) {
1574
- throw new OhIntegrityError("The memory continuation does not match this exact source and projection identity.");
2013
+ throw new OhMemoryContinuationError("identity",
2014
+ "The memory continuation does not match this exact source and projection identity.");
1575
2015
  }
1576
2016
  if (requestedContinuation !== null
1577
2017
  && (requestedContinuation.totalRows !== projection.rows.length
1578
2018
  || requestedContinuation.nextOffset >= projection.rows.length
1579
2019
  || requestedContinuation.nextOffset % program.pageSize !== 0)) {
1580
- throw new OhIntegrityError("The memory continuation does not match this exact row identity.");
2020
+ throw new OhMemoryContinuationError("identity",
2021
+ "The memory continuation does not match this exact row identity.");
1581
2022
  }
1582
2023
  const start = requestedContinuation?.nextOffset ?? 0;
1583
2024
  const endExclusive = Math.min(start + program.pageSize, projection.rows.length);
@@ -1605,15 +2046,15 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1605
2046
  const resultPayload = immutableClone({ ...resultIdentityPayload, continuation });
1606
2047
  const issuedAt = wallClock();
1607
2048
  const issuedAtMonotonic = monotonicClock();
1608
- const expiresAtMs = issuedAt + capabilityLifetime;
1609
- const expiresAtMonotonicMs = issuedAtMonotonic + capabilityLifetime;
2049
+ const expiresAtMs = issuedAt + runtime.capabilityLifetime;
2050
+ const expiresAtMonotonicMs = issuedAtMonotonic + runtime.capabilityLifetime;
1610
2051
  const expiresAt = isoInstant(new Date(expiresAtMs));
1611
2052
  const pageBytePreflight = { ...resultPayload,
1612
2053
  explainCapability: { expiresAt, token: "A".repeat(43), v: 2 as const }, resultSha256 };
1613
2054
  if (utf8ByteLength(canonicalJson(pageBytePreflight)) > program.maximumPageBytes) {
1614
2055
  throw new RangeError("The V2 memory page exceeds its host-declared canonical byte bound.");
1615
2056
  }
1616
- for (const [existingToken, stored] of explanations) {
2057
+ for (const [existingToken, stored] of runtime.explanations) {
1617
2058
  if (issuedAtMonotonic >= stored.expiresAtMonotonicMs) deleteExplanation(existingToken);
1618
2059
  }
1619
2060
  const storedPayload = immutableClone({ expiresAtMonotonicMs, identity, page, proofs,
@@ -1623,16 +2064,16 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1623
2064
  || storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
1624
2065
  throw new RangeError("The V2 memory explanation exceeds its retained capability bound.");
1625
2066
  }
1626
- while (explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities
1627
- || explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
1628
- const oldest = explanations.keys().next().value as string | undefined;
2067
+ while (runtime.explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities
2068
+ || runtime.explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
2069
+ const oldest = runtime.explanations.keys().next().value as string | undefined;
1629
2070
  if (oldest === undefined) break;
1630
2071
  deleteExplanation(oldest);
1631
2072
  }
1632
2073
  let token = randomBytes(32).toString("base64url");
1633
- while (explanations.has(token)) token = randomBytes(32).toString("base64url");
1634
- explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
1635
- explanationBytes += storedBytes;
2074
+ while (runtime.explanations.has(token)) token = randomBytes(32).toString("base64url");
2075
+ runtime.explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
2076
+ runtime.explanationBytes += storedBytes;
1636
2077
  const result = immutableClone({ ...resultPayload,
1637
2078
  explainCapability: { expiresAt, token, v: 2 as const }, resultSha256 });
1638
2079
  if (utf8ByteLength(canonicalJson(result)) > program.maximumPageBytes) {
@@ -1644,7 +2085,7 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1644
2085
 
1645
2086
  const explain = async (value: unknown): Promise<OhMemoryExplanationV2> => {
1646
2087
  const request = parseExplainRequestV2(value);
1647
- const stored = explanations.get(request.token);
2088
+ const stored = runtime.explanations.get(request.token);
1648
2089
  const currentTime = monotonicClock();
1649
2090
  if (stored === undefined || stored.resultSha256 !== request.resultSha256
1650
2091
  || currentTime >= stored.expiresAtMonotonicMs) {
@@ -1666,10 +2107,13 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1666
2107
  const request = parseNominationRequest(value);
1667
2108
  const route = nominationRoutes.get(request.nominationId);
1668
2109
  if (route === undefined) throw new TypeError("Unknown named memory nomination route.");
1669
- const head = parseOhHeadV1(immutableClone(await workingStore.head()));
2110
+ const head = parseOhHeadV1(detachCanonicalData(await workingStore.head(),
2111
+ "The working nomination store head", 4 * 1024).value);
1670
2112
  if (head === null) throw new OhIntegrityError("The working nomination store returned an invalid head.");
1671
- const closure = await workingStore.exportDependencyClosure({ head: {
2113
+ const returnedClosure = await workingStore.exportDependencyClosure({ head: {
1672
2114
  operationSha256: head.operationSha256, sequence: head.sequence }, roots: request.roots });
2115
+ const closure = detachCanonicalData(returnedClosure, "The working nomination closure",
2116
+ OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes).value;
1673
2117
  const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
1674
2118
  if (!verified.ok) throw new OhIntegrityError("The working nomination closure failed exact verification.");
1675
2119
  if (canonicalJson(verified.closure.roots) !== canonicalJson(request.roots)) {
@@ -1684,3 +2128,636 @@ export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): P
1684
2128
 
1685
2129
  return Object.freeze({ explain, nominate, query, remember });
1686
2130
  }
2131
+
2132
+ export async function createOhMemoryAgentV2(
2133
+ options: OhMemoryFacadeOptionsV2,
2134
+ ): Promise<OhMemoryAgentV2> {
2135
+ return await createOhMemoryAgentV2WithRuntime(options);
2136
+ }
2137
+
2138
+ function parseDetachedMemoryAuthorityHead(value: unknown, label: string): OhHeadV1 {
2139
+ const head = parseOhHeadV1(value);
2140
+ if (head === null) throw new TypeError(`Invalid ${label} memory head.`);
2141
+ return immutableClone(head);
2142
+ }
2143
+
2144
+ function parseMemoryAuthorityHead(value: unknown, label: string): OhHeadV1 {
2145
+ return parseDetachedMemoryAuthorityHead(detachCanonicalData(value,
2146
+ `The ${label} memory head`, 4 * 1024).value, label);
2147
+ }
2148
+
2149
+ function parseCanonicalAdvanceRequest(value: unknown): Readonly<{
2150
+ expectedHead: OhHeadV1;
2151
+ nextHead: OhHeadV1;
2152
+ }> {
2153
+ const detached = detachCanonicalData(value, "The canonical memory advance request",
2154
+ OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
2155
+ if (!isPlainRecord(detached) || !hasExactKeys(detached, ["expectedHead", "nextHead", "v"])
2156
+ || detached.v !== 1) throw new TypeError("Invalid canonical memory advance request.");
2157
+ return Object.freeze({
2158
+ expectedHead: parseDetachedMemoryAuthorityHead(detached.expectedHead, "expected canonical"),
2159
+ nextHead: parseDetachedMemoryAuthorityHead(detached.nextHead, "next canonical"),
2160
+ });
2161
+ }
2162
+
2163
+ function parseAdoptionRequest(value: unknown): Readonly<{
2164
+ expectedCanonicalHead: OhHeadV1;
2165
+ nomination: OhMemoryNominationV1;
2166
+ replacements: readonly OhMemoryAdoptionReplacementV1[];
2167
+ }> {
2168
+ const detached = detachCanonicalData(value, "The memory adoption request",
2169
+ OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionRequestBytes).value;
2170
+ if (!isPlainRecord(detached)
2171
+ || (!hasExactKeys(detached, ["expectedCanonicalHead", "nomination", "v"])
2172
+ && !hasExactKeys(detached, ["expectedCanonicalHead", "nomination", "replacements", "v"]))
2173
+ || detached.v !== 1) throw new TypeError("Invalid memory adoption request.");
2174
+ const nomination = parseDetachedMemoryNominationV1(detached.nomination);
2175
+ if (nomination === null) throw new TypeError("Invalid memory adoption nomination.");
2176
+ const replacementValues = "replacements" in detached ? detached.replacements : [];
2177
+ if (!Array.isArray(replacementValues)
2178
+ || replacementValues.length > OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionReplacements) {
2179
+ throw new TypeError("Invalid memory adoption replacements.");
2180
+ }
2181
+ const nominatedByKey = new Map(nomination.closure.records.map((record) => [record.key, record]));
2182
+ const replacements = replacementValues.map((replacement) => {
2183
+ if (!isPlainRecord(replacement)
2184
+ || !hasExactKeys(replacement, ["expectedPriorRecordSha256", "key", "v"])
2185
+ || replacement.v !== 1) throw new TypeError("Invalid memory adoption replacement.");
2186
+ const key = safeCode(replacement.key, 512);
2187
+ const expectedPriorRecordSha256 = parseSha256Hex(replacement.expectedPriorRecordSha256);
2188
+ const nominated = key === null ? undefined : nominatedByKey.get(key);
2189
+ if (key === null || expectedPriorRecordSha256 === null || nominated === undefined) {
2190
+ throw new TypeError("Invalid memory adoption replacement.");
2191
+ }
2192
+ return { expectedPriorRecordSha256, key, v: 1 as const };
2193
+ }).sort((left, right) => compareText(left.key, right.key));
2194
+ if (!orderedUnique(replacements, (replacement) => replacement.key)) {
2195
+ throw new TypeError("Memory adoption replacement keys must be unique.");
2196
+ }
2197
+ return Object.freeze({
2198
+ expectedCanonicalHead: parseDetachedMemoryAuthorityHead(detached.expectedCanonicalHead,
2199
+ "expected canonical adoption"),
2200
+ nomination,
2201
+ replacements: immutableClone(replacements),
2202
+ });
2203
+ }
2204
+
2205
+ function headRef(head: OhHeadV1): OhHeadRefV1 {
2206
+ return Object.freeze({ operationSha256: head.operationSha256, sequence: head.sequence });
2207
+ }
2208
+
2209
+ async function proveCanonicalDescendant(
2210
+ authority: Readonly<{ authorityId: string; binding: OhStoreBindingV1; store: OhStoreV1 }>,
2211
+ priorHead: OhHeadV1,
2212
+ nextHead: OhHeadV1,
2213
+ requiredFirstHead?: OhHeadV1,
2214
+ ): Promise<LaneSnapshot> {
2215
+ if (nextHead.sequence <= priorHead.sequence) {
2216
+ throw new OhConflictError("The next canonical memory head is not a descendant of the current pin.");
2217
+ }
2218
+ const distance = nextHead.sequence - priorHead.sequence;
2219
+ if (distance > OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvanceOperations
2220
+ || Math.ceil(distance / OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPage)
2221
+ > OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvancePages) {
2222
+ throw new RangeError("The canonical memory advance exceeds its total proof bound; advance in host-reviewed chunks.");
2223
+ }
2224
+ const through = headRef(nextHead);
2225
+ let cursor = headRef(priorHead);
2226
+ let pageCount = 0;
2227
+ let reachedHead: OhHeadV1 | null = null;
2228
+ let firstHead: OhHeadV1 | null = null;
2229
+ while (cursor.sequence < through.sequence) {
2230
+ if (pageCount >= OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvancePages) {
2231
+ throw new RangeError("The canonical memory advance exceeded its page proof bound; advance in host-reviewed chunks.");
2232
+ }
2233
+ pageCount += 1;
2234
+ const remaining = through.sequence - cursor.sequence;
2235
+ const limit = Math.min(remaining, OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPage);
2236
+ const returnedData = detachCanonicalData(
2237
+ await authority.store.changesSince(cursor, { limit, through }),
2238
+ "The canonical change-feed page",
2239
+ OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPageBytes,
2240
+ ).value;
2241
+ const returned = returnedData as Record<string, unknown>;
2242
+ if (!isPlainRecord(returned) || !hasExactKeys(returned,
2243
+ ["from", "hasMore", "operations", "through", "to", "v"])
2244
+ || returned.v !== 1 || typeof returned.hasMore !== "boolean"
2245
+ || !Array.isArray(returned.operations) || returned.operations.length > limit) {
2246
+ throw new OhIntegrityError("The canonical change feed returned an invalid page envelope.");
2247
+ }
2248
+ const from = parseOhHeadRefV1(returned.from);
2249
+ const returnedThrough = parseOhHeadV1(returned.through);
2250
+ const returnedTo = parseOhHeadRefV1(returned.to);
2251
+ if (from === null || returnedThrough === null || returnedTo === null
2252
+ || canonicalJson(from) !== canonicalJson(cursor)
2253
+ || !exactHead(returnedThrough, nextHead)) {
2254
+ throw new OhIntegrityError("The canonical change feed changed its pinned bounds.");
2255
+ }
2256
+ let reached = cursor;
2257
+ for (const value of returned.operations) {
2258
+ const operation = parseOhOperationV1(value);
2259
+ if (operation === null || operation.spaceId !== authority.binding.spaceId
2260
+ || operation.sequence !== reached.sequence + 1
2261
+ || operation.parentOperationSha256 !== reached.operationSha256) {
2262
+ throw new OhIntegrityError("The canonical change feed contains a gap or different authority.");
2263
+ }
2264
+ reached = Object.freeze({ operationSha256: operation.operationSha256,
2265
+ sequence: operation.sequence });
2266
+ reachedHead = immutableClone({ generation: operation.sequence,
2267
+ graphRevisionSha256: operation.graphRevisionSha256,
2268
+ operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
2269
+ sequence: operation.sequence, v: 1 });
2270
+ firstHead ??= reachedHead;
2271
+ }
2272
+ if (canonicalJson(reached) !== canonicalJson(returnedTo)
2273
+ || (returned.hasMore && returned.operations.length === 0)
2274
+ || (returned.hasMore && reached.sequence >= through.sequence)
2275
+ || reached.sequence > through.sequence
2276
+ || (!returned.hasMore && canonicalJson(reached) !== canonicalJson(through))) {
2277
+ throw new OhIntegrityError("The canonical change feed did not prove the requested descendant.");
2278
+ }
2279
+ cursor = reached;
2280
+ }
2281
+ if (reachedHead === null || !exactHead(reachedHead, nextHead)) {
2282
+ throw new OhIntegrityError("The canonical change feed did not prove the requested full head.");
2283
+ }
2284
+ if (requiredFirstHead !== undefined
2285
+ && (firstHead === null || !exactHead(firstHead, requiredFirstHead))) {
2286
+ throw new OhIntegrityError("The returned adoption operation is not on the current canonical path.");
2287
+ }
2288
+ return await readLane(authority, "canonical", nextHead);
2289
+ }
2290
+
2291
+ function canonicalAdvanceReceipt(
2292
+ authorityIdValue: string,
2293
+ bindingSha256: Sha256Hex,
2294
+ priorHead: OhHeadV1,
2295
+ head: OhHeadV1,
2296
+ status: OhMemoryCanonicalAdvanceReceiptV1["status"],
2297
+ ): OhMemoryCanonicalAdvanceReceiptV1 {
2298
+ const payload = { authorityId: authorityIdValue, bindingSha256, head, priorHead, status,
2299
+ v: 1 as const };
2300
+ return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
2301
+ }
2302
+
2303
+ function adoptionReceipt(
2304
+ actorId: string,
2305
+ authorityIdValue: string,
2306
+ bindingSha256: Sha256Hex,
2307
+ nominationSha256: Sha256Hex,
2308
+ operationSha256: Sha256Hex | null,
2309
+ priorHead: OhHeadV1,
2310
+ head: OhHeadV1,
2311
+ status: OhMemoryAdoptionReceiptV1["status"],
2312
+ ): OhMemoryAdoptionReceiptV1 {
2313
+ const payload = { actorId, authorityId: authorityIdValue, bindingSha256, head,
2314
+ nominationSha256, operationSha256, priorHead, status, v: 1 as const };
2315
+ return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
2316
+ }
2317
+
2318
+ function adoptionDifferences(
2319
+ snapshot: OhSnapshotV1,
2320
+ records: readonly KnowledgeGraphRecordV1[],
2321
+ ): readonly OhMemoryAdoptionConflictEntryV1[] {
2322
+ const canonicalByKey = new Map(snapshot.records.map((record) => [record.key, record]));
2323
+ return immutableClone(records.flatMap((record) => {
2324
+ const canonicalRecord = canonicalByKey.get(record.key);
2325
+ return canonicalRecord?.recordSha256 === record.recordSha256 ? [] : [{
2326
+ canonicalRecordSha256: canonicalRecord?.recordSha256 ?? null,
2327
+ key: record.key,
2328
+ nominatedRecordSha256: record.recordSha256,
2329
+ v: 1 as const,
2330
+ }];
2331
+ }).sort((left, right) => compareText(left.key, right.key)));
2332
+ }
2333
+
2334
+ function unauthorizedAdoptionDifferences(
2335
+ reviewedSnapshot: OhSnapshotV1,
2336
+ currentSnapshot: OhSnapshotV1,
2337
+ records: readonly KnowledgeGraphRecordV1[],
2338
+ replacements: readonly OhMemoryAdoptionReplacementV1[],
2339
+ ): readonly OhMemoryAdoptionConflictEntryV1[] {
2340
+ const reviewedByKey = new Map(reviewedSnapshot.records.map((record) => [record.key, record]));
2341
+ const currentByKey = new Map(currentSnapshot.records.map((record) => [record.key, record]));
2342
+ const replacementByKey = new Map(replacements.map((replacement) => [replacement.key,
2343
+ replacement.expectedPriorRecordSha256]));
2344
+ const conflicts = records.flatMap((nominated) => {
2345
+ const reviewed = reviewedByKey.get(nominated.key);
2346
+ const expectedPriorRecordSha256 = replacementByKey.get(nominated.key);
2347
+ const alreadyEqual = reviewed?.recordSha256 === nominated.recordSha256;
2348
+ const authorized = reviewed === undefined
2349
+ ? expectedPriorRecordSha256 === undefined
2350
+ : alreadyEqual
2351
+ ? expectedPriorRecordSha256 === undefined
2352
+ || expectedPriorRecordSha256 === reviewed.recordSha256
2353
+ : expectedPriorRecordSha256 === reviewed.recordSha256;
2354
+ if (authorized) return [];
2355
+ return [{
2356
+ canonicalRecordSha256: currentByKey.get(nominated.key)?.recordSha256 ?? null,
2357
+ key: nominated.key,
2358
+ nominatedRecordSha256: nominated.recordSha256,
2359
+ v: 1 as const,
2360
+ }];
2361
+ });
2362
+ return immutableClone(conflicts
2363
+ .sort((left, right) => compareText(left.key, right.key)));
2364
+ }
2365
+
2366
+ async function assertWorkingCommitCapacity(
2367
+ store: OhStoreV1,
2368
+ binding: OhStoreBindingV1,
2369
+ changes: readonly KnowledgeGraphChangeV1[],
2370
+ head: OhHeadV1,
2371
+ ): Promise<void> {
2372
+ const returnedSnapshot = await store.snapshot({
2373
+ head: { operationSha256: head.operationSha256, sequence: head.sequence },
2374
+ maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
2375
+ });
2376
+ const { snapshot } = parseDetachedStoreSnapshot(returnedSnapshot,
2377
+ "The working capacity store", head, binding.spaceId);
2378
+ const recordsByKey = new Map(snapshot.records.map((record) => [record.key, record]));
2379
+ for (const change of canonicalKnowledgeGraphChangesV1(changes)) {
2380
+ if (change.kind === "put") recordsByKey.set(change.record.key, change.record);
2381
+ else recordsByKey.delete(change.key);
2382
+ }
2383
+ if (recordsByKey.size > OH_MEMORY_LIMITS_V1.maximumRecordsPerLane) {
2384
+ throw new RangeError("The remembered working memory would exceed its record snapshot bound.");
2385
+ }
2386
+ const nextSequence = snapshot.head.sequence + 1;
2387
+ if (!Number.isSafeInteger(nextSequence)) {
2388
+ throw new RangeError("The remembered working memory would exceed its head sequence bound.");
2389
+ }
2390
+ const records = [...recordsByKey.values()]
2391
+ .sort((left, right) => compareText(left.key, right.key));
2392
+ const placeholderDigest = canonicalSha256({ kind: "oh.memory.remember-capacity", v: 1 });
2393
+ const prospective: OhSnapshotV1 = {
2394
+ head: {
2395
+ generation: nextSequence,
2396
+ graphRevisionSha256: placeholderDigest,
2397
+ operationSha256: placeholderDigest,
2398
+ recordsSha256: canonicalSha256(records.map(knowledgeGraphRecordRefV1)),
2399
+ sequence: nextSequence,
2400
+ v: 1,
2401
+ },
2402
+ records,
2403
+ v: 1,
2404
+ };
2405
+ if (utf8ByteLength(canonicalJson(prospective)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
2406
+ throw new RangeError("The remembered working memory would exceed its snapshot byte bound.");
2407
+ }
2408
+ }
2409
+
2410
+ function capacityGuardedWorkingStore(store: OhStoreV1, binding: OhStoreBindingV1): OhStoreV1 {
2411
+ const guarded: OhStoreV1 = {
2412
+ binding,
2413
+ changesSince: async (from, options) => await store.changesSince(from, options),
2414
+ close: async () => await store.close(),
2415
+ commit: async (input) => {
2416
+ const current = parseDetachedStoreHead(await store.head(),
2417
+ "The working capacity store");
2418
+ // The store owns exact operation-id replay. A stale expected head may
2419
+ // therefore be either a harmless replay or a conflict. Delegate it
2420
+ // unchanged so a hypothetical reapplication cannot reject a replay.
2421
+ if (
2422
+ current.generation === input.expectedHead.generation
2423
+ && current.operationSha256 === input.expectedHead.operationSha256
2424
+ ) await assertWorkingCommitCapacity(store, binding, input.changes, current);
2425
+ return await store.commit(input);
2426
+ },
2427
+ exportDependencyClosure: async (input) => await store.exportDependencyClosure(input),
2428
+ head: async () => await store.head(),
2429
+ snapshot: async (options) => await store.snapshot(options),
2430
+ verify: async () => await store.verify(),
2431
+ };
2432
+ return Object.freeze(guarded);
2433
+ }
2434
+
2435
+ function assertAdoptionSnapshotCapacity(
2436
+ snapshot: OhSnapshotV1,
2437
+ changedRecords: readonly KnowledgeGraphRecordV1[],
2438
+ ): void {
2439
+ const recordsByKey = new Map(snapshot.records.map((record) => [record.key, record]));
2440
+ for (const record of changedRecords) recordsByKey.set(record.key, record);
2441
+ if (recordsByKey.size > OH_MEMORY_LIMITS_V1.maximumRecordsPerLane) {
2442
+ throw new RangeError("The adopted canonical memory would exceed its record snapshot bound.");
2443
+ }
2444
+ const nextSequence = snapshot.head.sequence + 1;
2445
+ if (!Number.isSafeInteger(nextSequence)) {
2446
+ throw new RangeError("The adopted canonical memory would exceed its head sequence bound.");
2447
+ }
2448
+ const records = [...recordsByKey.values()]
2449
+ .sort((left, right) => compareText(left.key, right.key));
2450
+ const placeholderDigest = canonicalSha256({ kind: "oh.memory.adoption-capacity", v: 1 });
2451
+ const prospective: OhSnapshotV1 = {
2452
+ head: {
2453
+ generation: nextSequence,
2454
+ graphRevisionSha256: placeholderDigest,
2455
+ operationSha256: placeholderDigest,
2456
+ recordsSha256: canonicalSha256(records.map(knowledgeGraphRecordRefV1)),
2457
+ sequence: nextSequence,
2458
+ v: 1,
2459
+ },
2460
+ records,
2461
+ v: 1,
2462
+ };
2463
+ if (utf8ByteLength(canonicalJson(prospective)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
2464
+ throw new RangeError("The adopted canonical memory would exceed its canonical snapshot byte bound.");
2465
+ }
2466
+ }
2467
+
2468
+ function adoptionConflict(
2469
+ expectedHead: OhHeadV1,
2470
+ actualHead: OhHeadV1,
2471
+ completeConflicts: readonly OhMemoryAdoptionConflictEntryV1[],
2472
+ ): OhMemoryAdoptionConflictError {
2473
+ const sorted = immutableClone([...completeConflicts]
2474
+ .sort((left, right) => compareText(left.key, right.key)));
2475
+ const conflicts = immutableClone(sorted.slice(0,
2476
+ OH_MEMORY_AUTHORITY_LIMITS_V1.reportedAdoptionConflicts));
2477
+ const conflict = immutableClone({ actualHead, conflicts,
2478
+ conflictsSha256: canonicalSha256({ conflicts: sorted, v: 1 }), expectedHead,
2479
+ reportedConflicts: conflicts.length, totalConflicts: sorted.length,
2480
+ truncated: conflicts.length !== sorted.length, v: 1 as const });
2481
+ return new OhMemoryAdoptionConflictError(conflict);
2482
+ }
2483
+
2484
+ /**
2485
+ * Creates the stable two-lane memory boundary. The agent object carries no
2486
+ * canonical mutation handle; trusted host code retains serialized rollover and
2487
+ * reviewed adoption controls separately.
2488
+ */
2489
+ export async function createOhMemoryAuthorityV1(
2490
+ options: OhMemoryAuthorityOptionsV1,
2491
+ ): Promise<OhMemoryAuthorityV1> {
2492
+ const maximumCanonicalOperationBytes = options.maximumCanonicalOperationBytes
2493
+ ?? OH_OPERATION_MAX_BYTES_V1;
2494
+ if (!Number.isSafeInteger(maximumCanonicalOperationBytes)
2495
+ || maximumCanonicalOperationBytes < 1
2496
+ || maximumCanonicalOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
2497
+ throw new TypeError("Invalid canonical memory operation byte bound.");
2498
+ }
2499
+ const memoryActorId = safeCode(options.actorId, 128);
2500
+ const adoptionActorId = safeCode(options.adoptionActorId, 128);
2501
+ if (memoryActorId === null || adoptionActorId === null) {
2502
+ throw new TypeError("Invalid host-bound memory authority actor ID.");
2503
+ }
2504
+ const canonicalStore = options.canonical.store;
2505
+ const workingStore = options.working.store;
2506
+ const canonicalAuthorityId = authorityId(options.canonical.authorityId);
2507
+ const workingAuthorityId = authorityId(options.working.authorityId);
2508
+ if (canonicalAuthorityId === workingAuthorityId) {
2509
+ throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
2510
+ }
2511
+ const canonicalBinding = bindingFor(canonicalStore,
2512
+ options.canonical.expectedBindingSha256, "canonical");
2513
+ const workingBinding = bindingFor(workingStore,
2514
+ options.working.expectedBindingSha256, "working");
2515
+ const initialCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead,
2516
+ "initial canonical");
2517
+ const workingCodecs = options.working.codecs;
2518
+ const explainCapabilityLifetimeMs = options.explainCapabilityLifetimeMs;
2519
+ const monotonicNow = options.monotonicNow;
2520
+ const now = options.now;
2521
+ const continuationKey = continuationKeyV2(options.continuationKey);
2522
+ const programs = Object.freeze([...resolveProgramsV2(options.programs).values()].map((program) =>
2523
+ immutableClone({ evaluation: program.evaluation, maximumPageBytes: program.maximumPageBytes,
2524
+ maximumRows: program.maximumRows, pageSize: program.pageSize, parameters: program.parameters,
2525
+ programId: program.programId, purpose: program.purpose, query: program.query,
2526
+ rulePack: program.rulePack, v: 2 as const })));
2527
+ const extractors = resolveExtractors(options.extractors ?? []);
2528
+ const nominationRoutes = Object.freeze([...resolveNominationRoutes(options.nominationRoutes ?? []).values()]);
2529
+ const routesById = new Map(nominationRoutes.map((route) => [route.nominationId, route]));
2530
+ const runtime = createOhMemoryRuntimeV2(options);
2531
+
2532
+ const createAgentAt = async (expectedHead: OhHeadV1) => await createOhMemoryAgentV2WithRuntime({
2533
+ actorId: memoryActorId,
2534
+ canonical: { authorityId: canonicalAuthorityId,
2535
+ expectedBindingSha256: canonicalBinding.bindingSha256, expectedHead, store: canonicalStore },
2536
+ continuationKey,
2537
+ ...(explainCapabilityLifetimeMs === undefined ? {} : {
2538
+ explainCapabilityLifetimeMs,
2539
+ }),
2540
+ extractors,
2541
+ ...(monotonicNow === undefined ? {} : { monotonicNow }),
2542
+ nominationRoutes,
2543
+ ...(now === undefined ? {} : { now }),
2544
+ programs,
2545
+ working: { authorityId: workingAuthorityId, codecs: workingCodecs,
2546
+ expectedBindingSha256: workingBinding.bindingSha256, store: workingStore },
2547
+ }, runtime);
2548
+
2549
+ let activeCanonicalHead = initialCanonicalHead;
2550
+ let activeAgent = await createAgentAt(activeCanonicalHead);
2551
+ const agent: OhMemoryAgentV2 = Object.freeze({
2552
+ async explain(value: unknown) {
2553
+ const selected = activeAgent;
2554
+ return await selected.explain(value);
2555
+ },
2556
+ nominate(value: unknown) {
2557
+ const selected = activeAgent;
2558
+ return selected.nominate(value);
2559
+ },
2560
+ async query(value: unknown) {
2561
+ const selected = activeAgent;
2562
+ return await selected.query(value);
2563
+ },
2564
+ remember(value: unknown) {
2565
+ const selected = activeAgent;
2566
+ return selected.remember(value);
2567
+ },
2568
+ });
2569
+
2570
+ let hostTail: Promise<void> = Promise.resolve();
2571
+ const serialized = <T>(operation: () => Promise<T>): Promise<T> => {
2572
+ const result = hostTail.then(operation);
2573
+ hostTail = result.then(() => undefined, () => undefined);
2574
+ return result;
2575
+ };
2576
+
2577
+ const installCanonicalHead = async (head: OhHeadV1): Promise<void> => {
2578
+ const nextAgent = await createAgentAt(head);
2579
+ activeAgent = nextAgent;
2580
+ activeCanonicalHead = immutableClone(head);
2581
+ };
2582
+
2583
+ const canonicalAuthority = Object.freeze({ authorityId: canonicalAuthorityId,
2584
+ binding: canonicalBinding, store: canonicalStore });
2585
+ const readPhysicalCanonicalHead = async (): Promise<OhHeadV1> =>
2586
+ parseMemoryAuthorityHead(await canonicalStore.head(), "physical canonical");
2587
+
2588
+ const advanceCanonical = (value: unknown): Promise<OhMemoryCanonicalAdvanceReceiptV1> => {
2589
+ let request: ReturnType<typeof parseCanonicalAdvanceRequest>;
2590
+ try { request = parseCanonicalAdvanceRequest(value); } catch (error) { return Promise.reject(error); }
2591
+ return serialized(async () => {
2592
+ const priorHead = activeCanonicalHead;
2593
+ if (!exactHead(request.expectedHead, priorHead)) {
2594
+ throw new OhConflictError("The expected canonical memory head does not match the current pin.");
2595
+ }
2596
+ if (exactHead(request.nextHead, priorHead)) {
2597
+ return canonicalAdvanceReceipt(canonicalAuthorityId, canonicalBinding.bindingSha256,
2598
+ priorHead, priorHead, "unchanged");
2599
+ }
2600
+ await proveCanonicalDescendant(canonicalAuthority, priorHead, request.nextHead);
2601
+ await installCanonicalHead(request.nextHead);
2602
+ return canonicalAdvanceReceipt(canonicalAuthorityId, canonicalBinding.bindingSha256,
2603
+ priorHead, request.nextHead, "advanced");
2604
+ });
2605
+ };
2606
+
2607
+ const adoptNomination = (value: unknown): Promise<OhMemoryAdoptionReceiptV1> => {
2608
+ let request: ReturnType<typeof parseAdoptionRequest>;
2609
+ try { request = parseAdoptionRequest(value); } catch (error) { return Promise.reject(error); }
2610
+ return serialized(async () => {
2611
+ const route = routesById.get(request.nomination.nominationId);
2612
+ if (route === undefined || route.destinationPurpose !== request.nomination.destinationPurpose) {
2613
+ throw new OhProfileError("The memory nomination is not bound to this adoption route.");
2614
+ }
2615
+ if (request.nomination.source.authorityId !== workingAuthorityId
2616
+ || request.nomination.source.bindingSha256 !== workingBinding.bindingSha256
2617
+ || request.nomination.closure.binding.bindingSha256 !== workingBinding.bindingSha256) {
2618
+ throw new OhProfileError("The memory nomination is not from the bound working authority.");
2619
+ }
2620
+ const returnedReexport = await workingStore.exportDependencyClosure({
2621
+ head: headRef(request.nomination.source.head),
2622
+ maximumRecords: OH_DEPENDENCY_CLOSURE_LIMITS_V1.records,
2623
+ roots: request.nomination.closure.roots,
2624
+ });
2625
+ const reexported = detachCanonicalData(returnedReexport, "The working re-exported nomination",
2626
+ OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes);
2627
+ if (reexported.canonical !== canonicalJson(request.nomination.closure)) {
2628
+ throw new OhIntegrityError("The working authority did not re-export the nominated closure exactly.");
2629
+ }
2630
+
2631
+ const priorHead = activeCanonicalHead;
2632
+ const physicalHead = await readPhysicalCanonicalHead();
2633
+ const replacementConflictsAt = async (currentLane: LaneSnapshot) => {
2634
+ if (request.replacements.length === 0) return [];
2635
+ const reviewedLane = exactHead(request.expectedCanonicalHead, currentLane.snapshot.head)
2636
+ ? currentLane
2637
+ : await readLane(canonicalAuthority, "canonical", request.expectedCanonicalHead);
2638
+ return unauthorizedAdoptionDifferences(reviewedLane.snapshot, currentLane.snapshot,
2639
+ request.nomination.closure.records, request.replacements);
2640
+ };
2641
+ if (!exactHead(physicalHead, priorHead)) {
2642
+ const physicalLane = await proveCanonicalDescendant(canonicalAuthority, priorHead, physicalHead);
2643
+ const physicalDifferences = adoptionDifferences(physicalLane.snapshot,
2644
+ request.nomination.closure.records);
2645
+ if (physicalDifferences.length === 0) {
2646
+ const replacementConflicts = await replacementConflictsAt(physicalLane);
2647
+ if (replacementConflicts.length > 0) {
2648
+ throw adoptionConflict(request.expectedCanonicalHead, physicalHead, replacementConflicts);
2649
+ }
2650
+ await installCanonicalHead(physicalHead);
2651
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2652
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
2653
+ priorHead, physicalHead, "already-present");
2654
+ }
2655
+ throw adoptionConflict(request.expectedCanonicalHead, physicalHead, physicalDifferences);
2656
+ }
2657
+
2658
+ const lane = await readLane(canonicalAuthority, "canonical", priorHead);
2659
+ const differences = adoptionDifferences(lane.snapshot, request.nomination.closure.records);
2660
+ if (!exactHead(request.expectedCanonicalHead, priorHead)) {
2661
+ if (differences.length === 0) {
2662
+ const replacementConflicts = await replacementConflictsAt(lane);
2663
+ if (replacementConflicts.length > 0) {
2664
+ throw adoptionConflict(request.expectedCanonicalHead, priorHead, replacementConflicts);
2665
+ }
2666
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2667
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
2668
+ priorHead, priorHead, "already-present");
2669
+ }
2670
+ throw adoptionConflict(request.expectedCanonicalHead, priorHead, differences);
2671
+ }
2672
+ if (differences.length === 0) {
2673
+ const replacementConflicts = await replacementConflictsAt(lane);
2674
+ if (replacementConflicts.length > 0) {
2675
+ throw adoptionConflict(request.expectedCanonicalHead, priorHead, replacementConflicts);
2676
+ }
2677
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2678
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
2679
+ priorHead, priorHead, "already-present");
2680
+ }
2681
+ const unauthorized = unauthorizedAdoptionDifferences(lane.snapshot, lane.snapshot,
2682
+ request.nomination.closure.records, request.replacements);
2683
+ if (unauthorized.length > 0) {
2684
+ throw adoptionConflict(request.expectedCanonicalHead, priorHead, unauthorized);
2685
+ }
2686
+ const changedKeys = new Set(differences.map(({ key }) => key));
2687
+ const changedRecords = request.nomination.closure.records
2688
+ .filter(({ key }) => changedKeys.has(key));
2689
+ if (changedRecords.length === 0) {
2690
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2691
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
2692
+ priorHead, priorHead, "already-present");
2693
+ }
2694
+ assertAdoptionSnapshotCapacity(lane.snapshot, changedRecords);
2695
+ const changes = canonicalKnowledgeGraphChangesV1(changedRecords
2696
+ .map((record): KnowledgeGraphChangeV1 => ({ kind: "put", record, v: 1 })));
2697
+ const operationId = `memory_adopt_${canonicalSha256({ actorId: adoptionActorId,
2698
+ bindingSha256: canonicalBinding.bindingSha256,
2699
+ nominationSha256: request.nomination.nominationSha256,
2700
+ priorHead, v: 1 }).slice(0, 48)}`;
2701
+ let returnedOperation: unknown;
2702
+ try {
2703
+ returnedOperation = await canonicalStore.commit({ actorId: adoptionActorId, changes,
2704
+ expectedHead: { generation: priorHead.generation,
2705
+ operationSha256: priorHead.operationSha256 },
2706
+ maximumOperationBytes: maximumCanonicalOperationBytes,
2707
+ operationId });
2708
+ } catch (error) {
2709
+ if (!(error instanceof OhConflictError)) throw error;
2710
+ const actualHead = await readPhysicalCanonicalHead();
2711
+ const actualLane = exactHead(actualHead, priorHead)
2712
+ ? await readLane(canonicalAuthority, "canonical", actualHead)
2713
+ : await proveCanonicalDescendant(canonicalAuthority, priorHead, actualHead);
2714
+ const actualDifferences = adoptionDifferences(actualLane.snapshot,
2715
+ request.nomination.closure.records);
2716
+ if (actualDifferences.length === 0) {
2717
+ if (!exactHead(actualHead, priorHead)) {
2718
+ await installCanonicalHead(actualHead);
2719
+ }
2720
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2721
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
2722
+ priorHead, actualHead, "already-present");
2723
+ }
2724
+ throw adoptionConflict(request.expectedCanonicalHead, actualHead, actualDifferences);
2725
+ }
2726
+ const operation = parseOhOperationV1(detachCanonicalData(returnedOperation,
2727
+ "The returned canonical adoption operation",
2728
+ OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPageBytes).value);
2729
+ if (operation === null || operation.actorId !== adoptionActorId
2730
+ || operation.operationId !== operationId || operation.spaceId !== canonicalBinding.spaceId
2731
+ || operation.parentOperationSha256 !== priorHead.operationSha256
2732
+ || operation.sequence !== priorHead.sequence + 1
2733
+ || canonicalJson(operation.changes) !== canonicalJson(changes)) {
2734
+ throw new OhIntegrityError("The canonical authority returned a different adoption operation.");
2735
+ }
2736
+ const head: OhHeadV1 = immutableClone({ generation: operation.sequence,
2737
+ graphRevisionSha256: operation.graphRevisionSha256,
2738
+ operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
2739
+ sequence: operation.sequence, v: 1 });
2740
+ const actualHead = await readPhysicalCanonicalHead();
2741
+ if (!exactHead(actualHead, head)) {
2742
+ const actualLane = exactHead(actualHead, priorHead)
2743
+ ? await readLane(canonicalAuthority, "canonical", actualHead)
2744
+ : await proveCanonicalDescendant(canonicalAuthority, priorHead, actualHead, head);
2745
+ const actualDifferences = adoptionDifferences(actualLane.snapshot,
2746
+ request.nomination.closure.records);
2747
+ if (actualDifferences.length !== 0) {
2748
+ throw adoptionConflict(request.expectedCanonicalHead, actualHead, actualDifferences);
2749
+ }
2750
+ await installCanonicalHead(actualHead);
2751
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2752
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
2753
+ priorHead, actualHead, "already-present");
2754
+ }
2755
+ await installCanonicalHead(actualHead);
2756
+ return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
2757
+ canonicalBinding.bindingSha256, request.nomination.nominationSha256,
2758
+ operation.operationSha256, priorHead, actualHead, "adopted");
2759
+ });
2760
+ };
2761
+
2762
+ return Object.freeze({ agent, host: Object.freeze({ adoptNomination, advanceCanonical }) });
2763
+ }