arborkit 1.0.0

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 (94) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +134 -0
  4. package/dist/addressing.d.ts +22 -0
  5. package/dist/addressing.js +56 -0
  6. package/dist/addressing.js.map +1 -0
  7. package/dist/ag-ui.d.ts +45 -0
  8. package/dist/ag-ui.js +50 -0
  9. package/dist/ag-ui.js.map +1 -0
  10. package/dist/arbor.d.ts +73 -0
  11. package/dist/arbor.js +1542 -0
  12. package/dist/arbor.js.map +1 -0
  13. package/dist/artifact-tree.d.ts +66 -0
  14. package/dist/artifact-tree.js +285 -0
  15. package/dist/artifact-tree.js.map +1 -0
  16. package/dist/clock.d.ts +15 -0
  17. package/dist/clock.js +23 -0
  18. package/dist/clock.js.map +1 -0
  19. package/dist/decompose.d.ts +18 -0
  20. package/dist/decompose.js +22 -0
  21. package/dist/decompose.js.map +1 -0
  22. package/dist/delta-storage.d.ts +55 -0
  23. package/dist/delta-storage.js +106 -0
  24. package/dist/delta-storage.js.map +1 -0
  25. package/dist/delta.d.ts +55 -0
  26. package/dist/delta.js +740 -0
  27. package/dist/delta.js.map +1 -0
  28. package/dist/embedding-port.d.ts +17 -0
  29. package/dist/embedding-port.js +21 -0
  30. package/dist/embedding-port.js.map +1 -0
  31. package/dist/embedding-text.d.ts +14 -0
  32. package/dist/embedding-text.js +21 -0
  33. package/dist/embedding-text.js.map +1 -0
  34. package/dist/errors.d.ts +37 -0
  35. package/dist/errors.js +59 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/event-log.d.ts +75 -0
  38. package/dist/event-log.js +82 -0
  39. package/dist/event-log.js.map +1 -0
  40. package/dist/file-storage.d.ts +22 -0
  41. package/dist/file-storage.js +42 -0
  42. package/dist/file-storage.js.map +1 -0
  43. package/dist/ids.d.ts +17 -0
  44. package/dist/ids.js +22 -0
  45. package/dist/ids.js.map +1 -0
  46. package/dist/index.d.ts +29 -0
  47. package/dist/index.js +1826 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/json-edit.d.ts +18 -0
  50. package/dist/json-edit.js +85 -0
  51. package/dist/json-edit.js.map +1 -0
  52. package/dist/jsonpointer.d.ts +14 -0
  53. package/dist/jsonpointer.js +33 -0
  54. package/dist/jsonpointer.js.map +1 -0
  55. package/dist/mutator.d.ts +59 -0
  56. package/dist/mutator.js +244 -0
  57. package/dist/mutator.js.map +1 -0
  58. package/dist/navigator.d.ts +85 -0
  59. package/dist/navigator.js +192 -0
  60. package/dist/navigator.js.map +1 -0
  61. package/dist/path-glob.d.ts +13 -0
  62. package/dist/path-glob.js +40 -0
  63. package/dist/path-glob.js.map +1 -0
  64. package/dist/registry-validator.d.ts +15 -0
  65. package/dist/registry-validator.js +11 -0
  66. package/dist/registry-validator.js.map +1 -0
  67. package/dist/replay.d.ts +38 -0
  68. package/dist/replay.js +183 -0
  69. package/dist/replay.js.map +1 -0
  70. package/dist/semantic-index.d.ts +88 -0
  71. package/dist/semantic-index.js +226 -0
  72. package/dist/semantic-index.js.map +1 -0
  73. package/dist/storage.d.ts +39 -0
  74. package/dist/storage.js +378 -0
  75. package/dist/storage.js.map +1 -0
  76. package/dist/toolset.d.ts +78 -0
  77. package/dist/toolset.js +306 -0
  78. package/dist/toolset.js.map +1 -0
  79. package/dist/type-aware-decision.d.ts +8 -0
  80. package/dist/type-aware-decision.js +17 -0
  81. package/dist/type-aware-decision.js.map +1 -0
  82. package/dist/type-registry.d.ts +20 -0
  83. package/dist/type-registry.js +17 -0
  84. package/dist/type-registry.js.map +1 -0
  85. package/dist/types.d.ts +28 -0
  86. package/dist/types.js +1 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/vector-index-port.d.ts +34 -0
  89. package/dist/vector-index-port.js +49 -0
  90. package/dist/vector-index-port.js.map +1 -0
  91. package/dist/zod-adapter.d.ts +13 -0
  92. package/dist/zod-adapter.js +34 -0
  93. package/dist/zod-adapter.js.map +1 -0
  94. package/package.json +47 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jsonpointer.ts","../src/addressing.ts","../src/errors.ts","../src/event-log.ts","../src/mutator.ts","../src/decompose.ts","../src/artifact-tree.ts","../src/storage.ts","../src/delta.ts"],"sourcesContent":["/** Escape a single reference token per RFC 6901: ~ -> ~0, / -> ~1. */\r\nexport function encodeSegment(s: string): string {\r\n return s.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\r\n}\r\n\r\n/** Unescape a single reference token per RFC 6901: ~1 -> /, then ~0 -> ~. */\r\nexport function decodeSegment(s: string): string {\r\n return s.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\r\n}\r\n\r\n/** Build a JSON Pointer string. Empty segment list => \"\" (root). */\r\nexport function buildPointer(segments: ReadonlyArray<string | number>): string {\r\n if (segments.length === 0) return \"\";\r\n return \"/\" + segments.map((s) => encodeSegment(String(s))).join(\"/\");\r\n}\r\n\r\n/** Append one child key to a parent pointer, escaping the key exactly as `buildPointer` does. */\r\nexport function appendPointer(parent: string, key: string | number): string {\r\n return parent + \"/\" + encodeSegment(String(key));\r\n}\r\n\r\n/** True when `path` is at or under `scope` (JSON Pointer prefix). Undefined scope = everywhere. */\r\nexport function isWithin(path: string, scope: string | undefined): boolean {\r\n return scope === undefined || path === scope || path.startsWith(scope + \"/\");\r\n}\r\n\r\n/** Parse a JSON Pointer into decoded segments. \"\" => [] (root). */\r\nexport function parsePointer(pointer: string): string[] {\r\n if (pointer === \"\") return [];\r\n if (!pointer.startsWith(\"/\")) {\r\n throw new Error(`Invalid JSON Pointer (must be \"\" or start with \"/\"): ${pointer}`);\r\n }\r\n return pointer.slice(1).split(\"/\").map(decodeSegment);\r\n}\r\n","import type { ArbNode, NodeId } from \"./types\";\r\nimport type { ArtifactTree } from \"./artifact-tree\";\r\nimport { buildPointer, parsePointer } from \"./jsonpointer\";\r\n\r\n/**\r\n * Resolves nodes by stable id and by JSON Pointer path.\r\n * `path` is DERIVED from the current structure (not stored), so it stays\r\n * consistent automatically — id is identity, path is current location.\r\n */\r\nexport class Addressing {\r\n constructor(private readonly tree: ArtifactTree) {}\r\n\r\n byId(id: NodeId): ArbNode | undefined {\r\n return this.tree.get(id);\r\n }\r\n\r\n /** Compute the JSON Pointer for a node by walking parent links to the root. */\r\n pathOf(id: NodeId): string {\r\n const cur0 = this.tree.get(id);\r\n if (!cur0) throw new Error(`Unknown node: ${id}`);\r\n const segments: (string | number)[] = [];\r\n let cur: ArbNode | undefined = cur0;\r\n while (cur && cur.parentId !== null) {\r\n segments.unshift(cur.key as string | number);\r\n cur = this.tree.get(cur.parentId);\r\n }\r\n return buildPointer(segments);\r\n }\r\n\r\n /** Resolve a JSON Pointer to a node, or undefined if any segment is missing. */\r\n byPath(pointer: string): ArbNode | undefined {\r\n const segments = parsePointer(pointer);\r\n let cur: ArbNode | undefined = this.tree.root();\r\n for (const seg of segments) {\r\n if (!cur) return undefined;\r\n // parsePointer yields UNescaped segments and node keys are raw strings —\r\n // they match childByKey's String(key) map keys directly.\r\n cur = this.tree.childByKey(cur.id, seg);\r\n if (!cur) return undefined;\r\n }\r\n return cur;\r\n }\r\n}\r\n","import type { NodeId } from \"./types\";\r\n\r\n/** A reference to a node: by stable id or by JSON Pointer path. */\r\nexport type Ref = { id: NodeId } | { path: string };\r\n\r\nexport class ArborError extends Error {\r\n constructor(\r\n public readonly code: string,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = new.target.name;\r\n }\r\n}\r\n\r\nexport class NodeNotFoundError extends ArborError {\r\n constructor(public readonly ref: Ref) {\r\n super(\"NODE_NOT_FOUND\", `Node not found: ${JSON.stringify(ref)}`);\r\n }\r\n}\r\n\r\nexport class ScopeViolationError extends ArborError {\r\n constructor(\r\n public readonly targetPath: string,\r\n public readonly scope: string,\r\n ) {\r\n super(\"SCOPE_VIOLATION\", `Access outside scope: ${targetPath} (scope: ${scope})`);\r\n }\r\n}\r\n\r\nexport class StaleVersionError extends ArborError {\r\n constructor(\r\n public readonly id: NodeId,\r\n public readonly expected: number,\r\n public readonly actual: number,\r\n ) {\r\n super(\"STALE_VERSION\", `Stale version for ${id}: expected ${expected}, actual ${actual}`);\r\n }\r\n}\r\n\r\nexport class InvalidOpError extends ArborError {\r\n constructor(message: string) {\r\n super(\"INVALID_OP\", message);\r\n }\r\n}\r\n\r\nexport class ValidationError extends ArborError {\r\n constructor(\r\n public readonly type: string | undefined,\r\n public readonly details: string,\r\n ) {\r\n super(\"VALIDATION_ERROR\", `Validation failed${type ? ` for type ${type}` : \"\"}: ${details}`);\r\n }\r\n}\r\n","import type { Json, NodeId } from \"./types\";\r\nimport { InvalidOpError } from \"./errors\";\r\n\r\nexport type OpKind = \"set\" | \"insert\" | \"remove\" | \"move\";\r\n\r\n/**\r\n * A recorded mutation. Carries enough to reverse it later (M7 replay):\r\n * - set: before = old subtree value, after = new value\r\n * - insert: after = inserted value (inverse is remove of targetId)\r\n * - remove: before = removed subtree value (inverse is insert at parentId/key)\r\n * - move: from/to capture old and new (parentId, key)\r\n */\r\nexport interface MutationEvent {\r\n seq: number;\r\n kind: OpKind;\r\n targetId: NodeId;\r\n parentId: NodeId | null;\r\n key: string | number | null;\r\n before?: Json;\r\n after?: Json;\r\n from?: { parentId: NodeId | null; key: string | number | null };\r\n to?: { parentId: NodeId | null; key: string | number | null };\r\n /** JSON Pointer of the affected node (set/insert: its path; remove: its pre-removal path). */\r\n path?: string;\r\n /** move: source path (before the move). */\r\n fromPath?: string;\r\n /** move: destination path (after the move). */\r\n toPath?: string;\r\n /** set/remove: the node's type BEFORE the op; insert/set: `nodeType` = type AFTER.\r\n * `null` = explicitly untyped; ABSENT = pre-M10 event (unknown — replay keeps the current type). */\r\n nodeTypeBefore?: string | null;\r\n nodeType?: string | null;\r\n /** set/remove: the node's tags BEFORE the op ([] = untagged).\r\n * ABSENT = pre-M14 event (unknown — revert leaves the current tags alone). */\r\n tagsBefore?: string[];\r\n /** set/insert: the node's tags AFTER the op ([] = untagged). ABSENT = pre-M14. */\r\n tags?: string[];\r\n actor?: string;\r\n ts: number;\r\n}\r\n\r\n/** Append-only log of mutations with monotonic, absolute seq. Supports compaction:\r\n * events before `baseSeq` are dropped, but retained events keep their absolute seq\r\n * and `length()` stays the absolute next-seq, so versions never shift. */\r\nexport class EventLog {\r\n private readonly events: MutationEvent[] = [];\r\n private baseSeq = 0; // count of compacted-away events; events[0].seq === baseSeq\r\n\r\n append(event: Omit<MutationEvent, \"seq\">): MutationEvent {\r\n const full: MutationEvent = { ...event, seq: this.baseSeq + this.events.length };\r\n this.events.push(full);\r\n return full;\r\n }\r\n\r\n entries(): readonly MutationEvent[] {\r\n return this.events;\r\n }\r\n\r\n /** Absolute seq of the oldest retained event (0 until compaction). Versions below\r\n * this have been compacted away and are no longer reconstructable. */\r\n baseSeqValue(): number {\r\n return this.baseSeq;\r\n }\r\n\r\n /** The event at absolute `seq`, or undefined if compacted away / past the end. */\r\n at(seq: number): MutationEvent | undefined {\r\n const i = seq - this.baseSeq;\r\n return i >= 0 && i < this.events.length ? this.events[i] : undefined;\r\n }\r\n\r\n since(seq: number): MutationEvent[] {\r\n return this.events.filter((e) => e.seq >= seq);\r\n }\r\n\r\n /** Absolute next-seq / current version (unchanged across compaction). */\r\n length(): number {\r\n return this.baseSeq + this.events.length;\r\n }\r\n\r\n /** Drop events past absolute `length` — used to roll back a failed transaction.\r\n * Throws below the compaction floor: that history is gone and the log cannot roll\r\n * back past it — `compactTo` must never run inside a transaction. */\r\n truncateTo(length: number): void {\r\n if (length < this.baseSeq) {\r\n throw new InvalidOpError(\r\n `cannot truncate to ${length}: events before ${this.baseSeq} were compacted away (compactTo must not run inside a transaction)`,\r\n );\r\n }\r\n this.events.length = length - this.baseSeq;\r\n }\r\n\r\n /** Compaction: drop every retained event with seq < `floorSeq` (history before it\r\n * becomes unreconstructable). `floorSeq` is clamped to [baseSeq, length()].\r\n * Returns the number of events dropped. Must NOT be called inside a Mutator\r\n * transaction (rollback would need the dropped events). */\r\n compactTo(floorSeq: number): number {\r\n const floor = Math.max(this.baseSeq, Math.min(floorSeq, this.length()));\r\n const drop = floor - this.baseSeq;\r\n if (drop > 0) {\r\n this.events.splice(0, drop);\r\n this.baseSeq = floor;\r\n }\r\n return drop;\r\n }\r\n\r\n /** Rebuild a log from previously serialized events, preserving their seq + the\r\n * compaction floor. */\r\n static fromStored(events: MutationEvent[], baseSeq = 0): EventLog {\r\n const log = new EventLog();\r\n log.baseSeq = baseSeq;\r\n for (const e of events) log.events.push({ ...e });\r\n return log;\r\n }\r\n}\r\n","import type { ArbNode, Json, NodeId } from \"./types\";\r\nimport type { Clock } from \"./clock\";\r\nimport type { ArtifactTree } from \"./artifact-tree\";\r\nimport type { Addressing } from \"./addressing\";\r\nimport type { EventLog, OpKind } from \"./event-log\";\r\nimport { type Ref, NodeNotFoundError, ScopeViolationError, StaleVersionError, InvalidOpError } from \"./errors\";\r\nimport { isWithin } from \"./jsonpointer\";\r\n\r\n/** Optional validation hook. Throws to reject a mutation. M3 plugs Zod in here. */\r\nexport type Validator = (input: { node: ArbNode | null; proposed: Json; type?: string; op: OpKind }) => void;\r\n\r\nexport interface MutatorDeps {\r\n clock: Clock;\r\n validate?: Validator;\r\n /** Called after a node's content changes (set/insert) — e.g. to mark a semantic index stale. */\r\n onChange?: (node: ArbNode) => void;\r\n /** Called after a node is removed — e.g. to drop it from a semantic index. */\r\n onRemove?: (nodeId: NodeId) => void;\r\n /** Called at transaction start; the returned snapshot is passed to `onTxRestore` on rollback. */\r\n onTxSnapshot?: () => unknown;\r\n /** Called on transaction rollback with the snapshot from `onTxSnapshot`. */\r\n onTxRestore?: (snapshot: unknown) => void;\r\n}\r\n\r\nexport interface MutateOpts {\r\n owner?: string;\r\n /** JSON Pointer prefix; the target must be at or under it. */\r\n writeScope?: string;\r\n /** Optimistic concurrency: reject unless the target's current version equals this. */\r\n ifVersion?: number;\r\n /** Register/override the node's type (drives validation and the decompose override).\r\n * `null` explicitly CLEARS the type (validation is skipped) — used by type-aware revert. */\r\n type?: string | null;\r\n /** Replace the node's tags (identity labels for exact `find` by tag). */\r\n tags?: string[];\r\n}\r\n\r\nexport class Mutator {\r\n constructor(\r\n private readonly tree: ArtifactTree,\r\n private readonly addressing: Addressing,\r\n private readonly log: EventLog,\r\n private readonly deps: MutatorDeps,\r\n ) {}\r\n\r\n private resolve(ref: Ref): ArbNode {\r\n const node = \"id\" in ref ? this.addressing.byId(ref.id) : this.addressing.byPath(ref.path);\r\n if (!node) throw new NodeNotFoundError(ref);\r\n return node;\r\n }\r\n\r\n private checkScope(node: ArbNode, writeScope?: string): void {\r\n if (writeScope === undefined) return;\r\n const path = this.addressing.pathOf(node.id);\r\n if (!isWithin(path, writeScope)) throw new ScopeViolationError(path, writeScope);\r\n }\r\n\r\n private checkVersion(node: ArbNode, ifVersion?: number): void {\r\n if (ifVersion !== undefined && node.meta.version !== ifVersion) {\r\n throw new StaleVersionError(node.id, ifVersion, node.meta.version);\r\n }\r\n }\r\n\r\n private bump(node: ArbNode, owner?: string): void {\r\n node.meta.version += 1;\r\n node.meta.updatedAt = this.deps.clock.now();\r\n if (owner !== undefined) node.meta.owner = owner;\r\n }\r\n\r\n set(ref: Ref, value: Json, opts: MutateOpts = {}): void {\r\n const node = this.resolve(ref);\r\n this.checkScope(node, opts.writeScope);\r\n this.checkVersion(node, opts.ifVersion);\r\n // Defensive copy: neither the tree nor the event log may alias the caller's\r\n // live object — post-call mutation would silently rewrite state AND history.\r\n const cloned = structuredClone(value);\r\n const clearType = opts.type === null;\r\n const type = clearType ? undefined : (opts.type ?? node.type);\r\n this.deps.validate?.({ node, proposed: cloned, type, op: \"set\" });\r\n const before = this.tree.toJson(node.id);\r\n const typeBefore = node.type;\r\n const tagsBefore = [...(node.tags ?? [])];\r\n const orphaned = this.tree.descendantIds(node.id);\r\n this.tree.replaceValue(node.id, cloned, type, clearType);\r\n if (opts.tags !== undefined) node.tags = [...opts.tags];\r\n this.bump(node, opts.owner);\r\n this.deps.onChange?.(node);\r\n if (this.deps.onChange) {\r\n // replaceValue rebuilt the subtree: every descendant is a NEW node and\r\n // must be announced too (text leaves are what the semantic index embeds).\r\n for (const id of this.tree.descendantIds(node.id)) {\r\n const child = this.tree.get(id);\r\n if (child) this.deps.onChange(child);\r\n }\r\n }\r\n if (this.deps.onRemove) {\r\n for (const id of orphaned) this.deps.onRemove(id);\r\n }\r\n this.log.append({\r\n kind: \"set\",\r\n targetId: node.id,\r\n parentId: node.parentId,\r\n key: node.key,\r\n path: this.addressing.pathOf(node.id),\r\n before,\r\n after: cloned,\r\n nodeTypeBefore: typeBefore ?? null,\r\n nodeType: type ?? null,\r\n tagsBefore,\r\n tags: [...(node.tags ?? [])],\r\n actor: opts.owner,\r\n ts: this.deps.clock.now(),\r\n });\r\n }\r\n\r\n insert(parentRef: Ref, keyOrIndex: string | number, value: Json, opts: MutateOpts = {}): NodeId {\r\n const parent = this.resolve(parentRef);\r\n this.checkScope(parent, opts.writeScope);\r\n this.checkVersion(parent, opts.ifVersion);\r\n // Defensive copy: neither the tree nor the event log may alias the caller's\r\n // live object — post-call mutation would silently rewrite state AND history.\r\n const cloned = structuredClone(value);\r\n const type = opts.type === null ? undefined : opts.type;\r\n this.deps.validate?.({ node: null, proposed: cloned, type, op: \"insert\" });\r\n const newId = this.tree.insertChild(parent.id, keyOrIndex, cloned, type);\r\n const child = this.tree.get(newId)!;\r\n if (opts.tags !== undefined) child.tags = [...opts.tags];\r\n this.bump(parent, opts.owner);\r\n this.deps.onChange?.(child);\r\n if (this.deps.onChange) {\r\n for (const id of this.tree.descendantIds(newId)) {\r\n const desc = this.tree.get(id);\r\n if (desc) this.deps.onChange(desc);\r\n }\r\n }\r\n this.log.append({\r\n kind: \"insert\",\r\n targetId: newId,\r\n parentId: parent.id,\r\n key: child.key,\r\n path: this.addressing.pathOf(newId),\r\n after: cloned,\r\n nodeType: child.type ?? null,\r\n tags: [...(child.tags ?? [])],\r\n actor: opts.owner,\r\n ts: this.deps.clock.now(),\r\n });\r\n return newId;\r\n }\r\n\r\n remove(ref: Ref, opts: MutateOpts = {}): void {\r\n const node = this.resolve(ref);\r\n if (node.parentId === null) throw new InvalidOpError(\"cannot remove the root\");\r\n this.checkScope(node, opts.writeScope);\r\n this.checkVersion(node, opts.ifVersion);\r\n const before = this.tree.toJson(node.id);\r\n const path = this.addressing.pathOf(node.id);\r\n const removedIds = [node.id, ...this.tree.descendantIds(node.id)];\r\n const parent = this.tree.get(node.parentId)!;\r\n const removedKey = node.key;\r\n this.tree.removeChild(node.parentId, node.id);\r\n this.bump(parent, opts.owner);\r\n if (this.deps.onRemove) {\r\n for (const id of removedIds) this.deps.onRemove(id);\r\n }\r\n // The parent's semantic unit (if any) lost content — re-hash it.\r\n this.deps.onChange?.(parent);\r\n this.log.append({\r\n kind: \"remove\",\r\n targetId: node.id,\r\n parentId: parent.id,\r\n key: removedKey,\r\n path,\r\n before,\r\n nodeTypeBefore: node.type ?? null,\r\n tagsBefore: [...(node.tags ?? [])],\r\n actor: opts.owner,\r\n ts: this.deps.clock.now(),\r\n });\r\n }\r\n\r\n move(ref: Ref, toParentRef: Ref, keyOrIndex: string | number, opts: MutateOpts = {}): void {\r\n const node = this.resolve(ref);\r\n if (node.parentId === null) throw new InvalidOpError(\"cannot move the root\");\r\n const toParent = this.resolve(toParentRef);\r\n this.checkScope(node, opts.writeScope);\r\n this.checkScope(toParent, opts.writeScope);\r\n this.checkVersion(node, opts.ifVersion);\r\n const oldParentId = node.parentId;\r\n const from = { parentId: node.parentId, key: node.key };\r\n const fromPath = this.addressing.pathOf(node.id);\r\n this.tree.moveNode(node.id, toParent.id, keyOrIndex);\r\n const toPath = this.addressing.pathOf(node.id);\r\n // bump moved node + both parents (dedupe if old === new parent)\r\n const bumped = new Set<NodeId>();\r\n for (const id of [node.id, oldParentId, toParent.id]) {\r\n if (id !== null && !bumped.has(id)) {\r\n const n = this.tree.get(id);\r\n if (n) this.bump(n, opts.owner);\r\n bumped.add(id);\r\n }\r\n }\r\n if (this.deps.onChange) {\r\n // The moved subtree's ancestry changed (suppression status may flip), and\r\n // both old and new locations' semantic units changed content.\r\n this.deps.onChange(node);\r\n for (const id of this.tree.descendantIds(node.id)) {\r\n const d = this.tree.get(id);\r\n if (d) this.deps.onChange(d);\r\n }\r\n const oldP = oldParentId !== null ? this.tree.get(oldParentId) : undefined;\r\n if (oldP) this.deps.onChange(oldP);\r\n const newP = this.tree.get(toParent.id);\r\n if (newP) this.deps.onChange(newP);\r\n }\r\n this.log.append({\r\n kind: \"move\",\r\n targetId: node.id,\r\n parentId: toParent.id,\r\n key: node.key,\r\n from,\r\n to: { parentId: toParent.id, key: node.key },\r\n fromPath,\r\n toPath,\r\n actor: opts.owner,\r\n ts: this.deps.clock.now(),\r\n });\r\n }\r\n\r\n /** Run `fn` atomically: if it throws, the tree, log, and any hooked index state are restored. */\r\n transaction(fn: () => void): void {\r\n const snap = this.tree.snapshot();\r\n const logLen = this.log.length();\r\n const hookSnap = this.deps.onTxSnapshot?.();\r\n try {\r\n fn();\r\n } catch (err) {\r\n this.tree.restore(snap);\r\n this.log.truncateTo(logLen);\r\n if (this.deps.onTxRestore) this.deps.onTxRestore(hookSnap);\r\n throw err;\r\n }\r\n }\r\n}\r\n","import type { Json, NodeKind } from \"./types\";\r\n\r\n/** Policy deciding whether a value is stored whole (opaque leaf) or split into child nodes. */\r\nexport interface DecomposeDecision {\r\n /** `type` is the optional registered node type (used by the by-type override in a later milestone). */\r\n isOpaque(value: Json, type?: string): boolean;\r\n}\r\n\r\n/** UTF-8 byte length of the JSON serialization of a value. */\r\nexport function byteSize(value: Json): number {\r\n return Buffer.byteLength(JSON.stringify(value), \"utf8\");\r\n}\r\n\r\n/** Structural kind of a value given whether it is being stored opaquely. */\r\nexport function kindOf(value: Json, opaque: boolean): NodeKind {\r\n if (opaque) return \"leaf\";\r\n return Array.isArray(value) ? \"array\" : \"object\";\r\n}\r\n\r\n/**\r\n * Default policy: scalars are always opaque leaves; containers stay opaque\r\n * while their serialized size is within `maxOpaqueBytes`, otherwise they split.\r\n */\r\nexport function sizeBasedDecision(maxOpaqueBytes: number): DecomposeDecision {\r\n return {\r\n isOpaque(value: Json): boolean {\r\n if (value === null || typeof value !== \"object\") return true;\r\n return byteSize(value) <= maxOpaqueBytes;\r\n },\r\n };\r\n}\r\n","import type { ArbNode, Json, NodeId } from \"./types\";\r\nimport type { IdGen } from \"./ids\";\r\nimport type { Clock } from \"./clock\";\r\nimport { type DecomposeDecision, kindOf } from \"./decompose\";\r\nimport { InvalidOpError } from \"./errors\";\r\n\r\nexport interface TreeDeps {\r\n idGen: IdGen;\r\n clock: Clock;\r\n decision: DecomposeDecision;\r\n}\r\n\r\nexport interface TreeSnapshot {\r\n nodes: Map<NodeId, ArbNode>;\r\n rootId: NodeId;\r\n}\r\n\r\nexport class ArtifactTree {\r\n private readonly nodes = new Map<NodeId, ArbNode>();\r\n private rootId!: NodeId;\r\n /** Lazily built per-parent key→childId maps for O(1) object-child lookup.\r\n * A cache OUTSIDE the nodes (ArbNode/StoredArtifact stay byte-identical);\r\n * invalidated on every child-set change, rebuilt on read. Arrays never\r\n * populate it (index lookup is already O(1)). */\r\n private readonly keyMaps = new Map<NodeId, Map<string, NodeId>>();\r\n\r\n private constructor(private readonly deps: TreeDeps) {}\r\n\r\n static fromJson(json: Json, deps: TreeDeps): ArtifactTree {\r\n const tree = new ArtifactTree(deps);\r\n tree.rootId = tree.build(json, null, null);\r\n return tree;\r\n }\r\n\r\n private build(value: Json, parentId: NodeId | null, key: string | number | null, type?: string): NodeId {\r\n const opaque = this.deps.decision.isOpaque(value, type);\r\n const kind = kindOf(value, opaque);\r\n const id = this.deps.idGen.next();\r\n const node: ArbNode = {\r\n id,\r\n parentId,\r\n key,\r\n kind,\r\n content: kind === \"leaf\" ? value : null,\r\n childIds: [],\r\n meta: { version: 0, updatedAt: this.deps.clock.now(), embedding: { state: \"none\" } },\r\n };\r\n if (type !== undefined) node.type = type;\r\n this.nodes.set(id, node);\r\n\r\n if (kind === \"object\") {\r\n for (const [k, v] of Object.entries(value as Record<string, Json>)) {\r\n node.childIds.push(this.build(v, id, k));\r\n }\r\n } else if (kind === \"array\") {\r\n (value as Json[]).forEach((v, i) => {\r\n node.childIds.push(this.build(v, id, i));\r\n });\r\n }\r\n return id;\r\n }\r\n\r\n get(id: NodeId): ArbNode | undefined {\r\n return this.nodes.get(id);\r\n }\r\n\r\n root(): ArbNode {\r\n return this.nodes.get(this.rootId)!;\r\n }\r\n\r\n rootIdValue(): NodeId {\r\n return this.rootId;\r\n }\r\n\r\n /** O(1) child lookup: arrays by index, objects via a lazily built key map. */\r\n childByKey(parentId: NodeId, key: string): ArbNode | undefined {\r\n const parent = this.nodes.get(parentId);\r\n if (!parent) return undefined;\r\n if (parent.kind === \"array\") {\r\n if (!/^(0|[1-9]\\d*)$/.test(key)) return undefined; // canonical RFC 6901 index: digits only, no leading zeros\r\n const i = Number(key);\r\n if (i >= parent.childIds.length) return undefined;\r\n return this.nodes.get(parent.childIds[i]);\r\n }\r\n let map = this.keyMaps.get(parentId);\r\n if (!map) {\r\n map = new Map();\r\n for (const cid of parent.childIds) map.set(String(this.nodes.get(cid)!.key), cid);\r\n this.keyMaps.set(parentId, map);\r\n }\r\n const cid = map.get(key);\r\n return cid === undefined ? undefined : this.nodes.get(cid);\r\n }\r\n\r\n children(id: NodeId): ArbNode[] {\r\n const n = this.nodes.get(id);\r\n if (!n) return [];\r\n return n.childIds.map((cid) => this.nodes.get(cid)!);\r\n }\r\n\r\n has(id: NodeId): boolean {\r\n return this.nodes.has(id);\r\n }\r\n\r\n size(): number {\r\n return this.nodes.size;\r\n }\r\n\r\n /** Reconstruct the JSON value rooted at `id` (defaults to the tree root). */\r\n toJson(id: NodeId = this.rootId): Json {\r\n const n = this.nodes.get(id);\r\n if (!n) throw new Error(`Unknown node: ${id}`);\r\n if (n.kind === \"leaf\") return n.content;\r\n if (n.kind === \"array\") return n.childIds.map((cid) => this.toJson(cid));\r\n const obj: Record<string, Json> = {};\r\n for (const cid of n.childIds) {\r\n const c = this.nodes.get(cid)!;\r\n obj[String(c.key)] = this.toJson(cid);\r\n }\r\n return obj;\r\n }\r\n\r\n /** Replace the subtree value at `id` in place, keeping the node's id/key/parentId.\r\n * `clearType` explicitly un-types the node (used by type-aware revert). */\r\n replaceValue(id: NodeId, value: Json, type?: string, clearType = false): void {\r\n const node = this.nodes.get(id);\r\n if (!node) throw new InvalidOpError(`Unknown node: ${id}`);\r\n this.deleteDescendants(id);\r\n this.keyMaps.delete(id);\r\n const opaque = this.deps.decision.isOpaque(value, type);\r\n const kind = kindOf(value, opaque);\r\n node.kind = kind;\r\n node.content = kind === \"leaf\" ? value : null;\r\n node.childIds = [];\r\n if (clearType) node.type = undefined;\r\n else if (type !== undefined) node.type = type;\r\n if (kind === \"object\") {\r\n for (const [k, v] of Object.entries(value as Record<string, Json>)) {\r\n node.childIds.push(this.build(v, id, k));\r\n }\r\n } else if (kind === \"array\") {\r\n (value as Json[]).forEach((v, i) => {\r\n node.childIds.push(this.build(v, id, i));\r\n });\r\n }\r\n }\r\n\r\n /** Recursively remove all descendants of `id` from the node map (keeps `id` itself). */\r\n private deleteDescendants(id: NodeId): void {\r\n const node = this.nodes.get(id);\r\n if (!node) return;\r\n for (const cid of node.childIds) {\r\n this.deleteDescendants(cid);\r\n this.nodes.delete(cid);\r\n this.keyMaps.delete(cid);\r\n }\r\n node.childIds = [];\r\n }\r\n\r\n /** Deep, independent copy of the tree state for transaction rollback.\r\n * `restore` consumes the snapshot; do not reuse it afterwards. */\r\n snapshot(): TreeSnapshot {\r\n const nodes = new Map<NodeId, ArbNode>();\r\n for (const [id, node] of this.nodes) {\r\n nodes.set(id, structuredClone(node));\r\n }\r\n return { nodes, rootId: this.rootId };\r\n }\r\n\r\n /** Replace the tree state with a previously taken snapshot. The snapshot's\r\n * nodes are adopted BY REFERENCE — restore consumes the snapshot; do not\r\n * reuse it afterwards. */\r\n restore(snap: TreeSnapshot): void {\r\n this.nodes.clear();\r\n this.keyMaps.clear();\r\n for (const [id, node] of snap.nodes) {\r\n this.nodes.set(id, node);\r\n }\r\n this.rootId = snap.rootId;\r\n }\r\n\r\n /** Insert a decomposed `value` as a child of `parentId`. For objects `keyOrIndex` is the string key; for arrays it is the insert index. Returns the new child's id. */\r\n insertChild(parentId: NodeId, keyOrIndex: string | number, value: Json, type?: string): NodeId {\r\n const parent = this.nodes.get(parentId);\r\n if (!parent) throw new InvalidOpError(`Unknown node: ${parentId}`);\r\n if (parent.kind === \"object\") {\r\n if (typeof keyOrIndex !== \"string\") {\r\n throw new InvalidOpError(\"object insert requires a string key\");\r\n }\r\n if (parent.childIds.some((cid) => this.nodes.get(cid)!.key === keyOrIndex)) {\r\n throw new InvalidOpError(`key already exists: ${keyOrIndex}`);\r\n }\r\n const cid = this.build(value, parentId, keyOrIndex, type);\r\n parent.childIds.push(cid);\r\n this.keyMaps.delete(parentId);\r\n return cid;\r\n }\r\n if (parent.kind === \"array\") {\r\n if (typeof keyOrIndex !== \"number\") {\r\n throw new InvalidOpError(\"array insert requires a numeric index\");\r\n }\r\n const at = Math.max(0, Math.min(keyOrIndex, parent.childIds.length));\r\n const cid = this.build(value, parentId, at, type);\r\n parent.childIds.splice(at, 0, cid);\r\n this.renumberArray(parentId);\r\n return cid;\r\n }\r\n throw new InvalidOpError(\"cannot insert into a leaf node\");\r\n }\r\n\r\n /** Remove `childId` (and its subtree) from `parentId`. Renumbers array siblings. */\r\n removeChild(parentId: NodeId, childId: NodeId): void {\r\n const parent = this.nodes.get(parentId);\r\n if (!parent) throw new InvalidOpError(`Unknown node: ${parentId}`);\r\n const idx = parent.childIds.indexOf(childId);\r\n if (idx < 0) throw new InvalidOpError(`${childId} is not a child of ${parentId}`);\r\n this.deleteDescendants(childId);\r\n this.nodes.delete(childId);\r\n this.keyMaps.delete(childId);\r\n parent.childIds.splice(idx, 1);\r\n this.keyMaps.delete(parentId);\r\n if (parent.kind === \"array\") this.renumberArray(parentId);\r\n }\r\n\r\n /** Move `id` under `newParentId` at `keyOrIndex`, preserving `id`. Renumbers affected arrays.\r\n * ALL validation happens before any mutation — a rejected move leaves the tree untouched. */\r\n moveNode(id: NodeId, newParentId: NodeId, keyOrIndex: string | number): void {\r\n const node = this.nodes.get(id);\r\n if (!node) throw new InvalidOpError(`Unknown node: ${id}`);\r\n if (node.parentId === null) throw new InvalidOpError(\"cannot move the root\");\r\n const newParent = this.nodes.get(newParentId);\r\n if (!newParent) throw new InvalidOpError(`Unknown node: ${newParentId}`);\r\n if (newParent.kind === \"leaf\") throw new InvalidOpError(\"cannot move into a leaf node\");\r\n // Moving into itself or its own subtree would create a parent-chain cycle:\r\n // toJson silently drops the subtree and pathOf never terminates.\r\n let anc: NodeId | null = newParentId;\r\n while (anc !== null) {\r\n if (anc === id) throw new InvalidOpError(\"cannot move a node into itself or its own subtree\");\r\n anc = this.nodes.get(anc)?.parentId ?? null;\r\n }\r\n if (newParent.kind === \"object\") {\r\n if (typeof keyOrIndex !== \"string\") throw new InvalidOpError(\"object move requires a string key\");\r\n // Mirrors insertChild: a duplicate key silently shadows the existing child.\r\n if (newParent.childIds.some((cid) => cid !== id && this.nodes.get(cid)!.key === keyOrIndex)) {\r\n throw new InvalidOpError(`key already exists: ${keyOrIndex}`);\r\n }\r\n }\r\n\r\n const oldParent = this.nodes.get(node.parentId)!;\r\n const oldIdx = oldParent.childIds.indexOf(id);\r\n oldParent.childIds.splice(oldIdx, 1);\r\n this.keyMaps.delete(oldParent.id);\r\n this.keyMaps.delete(newParentId);\r\n if (oldParent.kind === \"array\") this.renumberArray(oldParent.id);\r\n\r\n if (newParent.kind === \"object\") {\r\n node.parentId = newParentId;\r\n node.key = keyOrIndex;\r\n newParent.childIds.push(id);\r\n } else {\r\n const at = typeof keyOrIndex === \"number\" ? Math.max(0, Math.min(keyOrIndex, newParent.childIds.length)) : newParent.childIds.length;\r\n node.parentId = newParentId;\r\n newParent.childIds.splice(at, 0, id);\r\n this.renumberArray(newParentId);\r\n }\r\n }\r\n\r\n /** Set each array child's `key` to its current position. */\r\n private renumberArray(parentId: NodeId): void {\r\n const parent = this.nodes.get(parentId);\r\n if (!parent) return;\r\n parent.childIds.forEach((cid, i) => {\r\n this.nodes.get(cid)!.key = i;\r\n });\r\n }\r\n\r\n /** All nodes in the tree (for serialization). */\r\n allNodes(): ArbNode[] {\r\n return [...this.nodes.values()];\r\n }\r\n\r\n /** Rebuild a tree from previously serialized nodes, preserving their ids. */\r\n static fromStored(nodes: ArbNode[], rootId: NodeId, deps: TreeDeps): ArtifactTree {\r\n const tree = new ArtifactTree(deps);\r\n for (const node of nodes) tree.nodes.set(node.id, node);\r\n tree.rootId = rootId;\r\n return tree;\r\n }\r\n\r\n /** All transitive descendant ids of `id` (depth-first), excluding `id` itself. */\r\n descendantIds(id: NodeId): NodeId[] {\r\n const out: NodeId[] = [];\r\n const node = this.nodes.get(id);\r\n if (!node) return out;\r\n for (const cid of node.childIds) {\r\n out.push(cid);\r\n out.push(...this.descendantIds(cid));\r\n }\r\n return out;\r\n }\r\n}\r\n","import type { ArbNode, NodeId } from \"./types\";\r\nimport { ArtifactTree, type TreeDeps } from \"./artifact-tree\";\r\nimport { EventLog, type MutationEvent } from \"./event-log\";\r\nimport type { VectorIndexPort, VectorIndexEntry } from \"./vector-index-port\";\r\n\r\n/** A JSON-serializable snapshot of an entire artifact: tree + event-log + vectors.\r\n * v2 adds `baseSeq` (the event-log compaction floor); v1 files restore with floor 0. */\r\nexport interface StoredArtifact {\r\n version: 1 | 2;\r\n rootId: NodeId;\r\n nodes: ArbNode[];\r\n events: MutationEvent[];\r\n /** Absolute seq of the oldest retained event (compaction floor). Absent in v1 → 0. */\r\n baseSeq?: number;\r\n vectors: VectorIndexEntry[];\r\n}\r\n\r\n/** Persists/loads a StoredArtifact. Adapters: MemoryStorage, FileStorage (and DB-backed later). */\r\nexport interface StoragePort {\r\n save(artifact: StoredArtifact): Promise<void>;\r\n load(): Promise<StoredArtifact | null>;\r\n}\r\n\r\n/** Dump the live components into a StoredArtifact (v2). */\r\nexport async function serializeArtifact(\r\n tree: ArtifactTree,\r\n log: EventLog,\r\n vectors: VectorIndexPort,\r\n): Promise<StoredArtifact> {\r\n return {\r\n version: 2,\r\n rootId: tree.rootIdValue(),\r\n nodes: tree.allNodes(),\r\n events: [...log.entries()],\r\n baseSeq: log.baseSeqValue(),\r\n vectors: await vectors.entries(),\r\n };\r\n}\r\n\r\n/** Rebuild a fresh tree + log from a StoredArtifact, and upsert its vectors into `vectors`. */\r\nexport async function restoreArtifact(\r\n stored: StoredArtifact,\r\n deps: TreeDeps,\r\n vectors: VectorIndexPort,\r\n): Promise<{ tree: ArtifactTree; log: EventLog }> {\r\n const tree = ArtifactTree.fromStored(stored.nodes, stored.rootId, deps);\r\n const log = EventLog.fromStored(stored.events, stored.baseSeq ?? 0);\r\n await vectors.upsert(stored.vectors);\r\n return { tree, log };\r\n}\r\n\r\n/** In-memory StoragePort: holds a deep-cloned bundle. */\r\nexport class MemoryStorage implements StoragePort {\r\n private stored: StoredArtifact | null = null;\r\n\r\n async save(artifact: StoredArtifact): Promise<void> {\r\n this.stored = structuredClone(artifact);\r\n }\r\n\r\n async load(): Promise<StoredArtifact | null> {\r\n return this.stored ? structuredClone(this.stored) : null;\r\n }\r\n}\r\n","import type { Json, NodeId } from \"./types\";\r\nimport { ArtifactTree, type TreeDeps } from \"./artifact-tree\";\r\nimport { Addressing } from \"./addressing\";\r\nimport { EventLog, type MutationEvent } from \"./event-log\";\r\nimport { Mutator } from \"./mutator\";\r\nimport type { VectorIndexPort } from \"./vector-index-port\";\r\nimport { serializeArtifact, restoreArtifact } from \"./storage\";\r\nimport type { DeltaStoragePort } from \"./delta-storage\";\r\nimport { InvalidOpError } from \"./errors\";\r\n\r\n/** The JSON-Pointer of the parent container of `pointer` (\"\" = root). Pointer\r\n * separators are literal \"/\"; an in-key \"/\" is escaped \"~1\", so lastIndexOf is safe. */\r\nfunction parentPointer(pointer: string): string {\r\n const i = pointer.lastIndexOf(\"/\");\r\n return i <= 0 ? \"\" : pointer.slice(0, i);\r\n}\r\n\r\n/**\r\n * Re-apply ONE recorded event FORWARD onto a live tree via the `Mutator`, addressed by\r\n * the event's stable path(s). The inverse of replay's `reverseApplyValue`: set→`after`,\r\n * insert→insert `after`, remove→remove, move→to. Goes through the Mutator so\r\n * decomposition, typing (via `e.nodeType`), and the index hooks run exactly as in normal\r\n * operation. Node ids for touched subtrees are regenerated; replay/revert are path-addressed and\r\n * unaffected, but id-addressed views (e.g. toolset `history`, which filters by targetId)\r\n * may not see a regenerated node's pre-restore history. Malformed/pre-M7 events (missing\r\n * paths) are skipped.\r\n */\r\nexport function applyEventForward(mutator: Mutator, e: MutationEvent): void {\r\n switch (e.kind) {\r\n case \"set\": {\r\n if (e.path === undefined) return;\r\n const opts: { type?: string | null; tags?: string[] } = {};\r\n if (e.nodeType !== undefined) opts.type = e.nodeType;\r\n if (e.tags !== undefined) opts.tags = e.tags;\r\n mutator.set({ path: e.path }, e.after ?? null, opts);\r\n return;\r\n }\r\n case \"insert\": {\r\n if (e.path === undefined || e.key === null) return;\r\n const opts: { type?: string | null; tags?: string[] } = {};\r\n if (e.nodeType !== undefined) opts.type = e.nodeType;\r\n if (e.tags !== undefined) opts.tags = e.tags;\r\n mutator.insert({ path: parentPointer(e.path) }, e.key, e.after ?? null, opts);\r\n return;\r\n }\r\n case \"remove\":\r\n if (e.path === undefined) return;\r\n mutator.remove({ path: e.path });\r\n return;\r\n case \"move\":\r\n if (e.fromPath === undefined || e.toPath === undefined || !e.to || e.to.key === null) return;\r\n mutator.move({ path: e.fromPath }, { path: parentPointer(e.toPath) }, e.to.key);\r\n return;\r\n }\r\n}\r\n\r\n/** Forward-apply a sequence of events, in order. */\r\nexport function replayForward(mutator: Mutator, events: readonly MutationEvent[]): void {\r\n for (const e of events) applyEventForward(mutator, e);\r\n}\r\n\r\n/**\r\n * Append every event newer than `sinceSeq` to the journal (the cheap, common save).\r\n * Returns the new high-water seq to pass next time. No-op if nothing is new.\r\n * Throws if events in `[sinceSeq, baseSeq)` were compacted away before being journaled\r\n * (that history is unrecoverable) — compact only right before `persistCheckpoint`.\r\n */\r\nexport async function persistDelta(store: DeltaStoragePort, log: EventLog, sinceSeq: number): Promise<number> {\r\n if (sinceSeq < log.baseSeqValue()) {\r\n throw new InvalidOpError(\r\n `persistDelta: events [${sinceSeq}, ${log.baseSeqValue()}) were compacted before being journaled`,\r\n );\r\n }\r\n const fresh = log.since(sinceSeq);\r\n if (fresh.length > 0) await store.appendEvents(fresh);\r\n return log.length();\r\n}\r\n\r\n/**\r\n * Write a full checkpoint (replacing the prior one and clearing the journal) and return\r\n * its high-water seq. Pair with M12 `log.compactTo(...)` BEFORE calling to keep the\r\n * checkpoint's embedded event window small.\r\n */\r\nexport async function persistCheckpoint(\r\n store: DeltaStoragePort,\r\n tree: ArtifactTree,\r\n log: EventLog,\r\n vectors: VectorIndexPort,\r\n): Promise<number> {\r\n await store.writeCheckpoint(await serializeArtifact(tree, log, vectors));\r\n return log.length();\r\n}\r\n\r\n/**\r\n * Restore a tree + log from a checkpoint plus its journaled deltas. Returns null if no\r\n * checkpoint has been written yet. Forward-replays the journal through a `Mutator` so node\r\n * TYPES are preserved (via each event's `nodeType`) and UNCHANGED nodes keep their ids and\r\n * checkpoint vectors; touched nodes are re-decomposed and marked `embedding.state: \"stale\"`\r\n * for the consumer's `SemanticIndex` reindex, and removed/orphaned nodes' vectors are\r\n * dropped. `vectors` should be a fresh index (the checkpoint's vectors are upserted into it).\r\n * Restore must use the same `decompose` decision as the original run (journal-touched nodes\r\n * are re-decomposed); replay does not re-validate.\r\n */\r\nexport async function restoreFromDelta(\r\n store: DeltaStoragePort,\r\n deps: TreeDeps,\r\n vectors: VectorIndexPort,\r\n): Promise<{ tree: ArtifactTree; log: EventLog } | null> {\r\n const { checkpoint, journal } = await store.loadDelta();\r\n if (!checkpoint) return null;\r\n // The journal must continue exactly where the checkpoint ends — a gap means events\r\n // were lost (torn journal line, or compaction before journaling); replaying past a\r\n // gap would silently produce a wrong tree, so fail loudly instead.\r\n const checkpointVersion = (checkpoint.baseSeq ?? 0) + checkpoint.events.length;\r\n for (let i = 0; i < journal.length; i++) {\r\n if (journal[i].seq !== checkpointVersion + i) {\r\n throw new InvalidOpError(\r\n `restoreFromDelta: journal not contiguous with checkpoint (expected seq ${checkpointVersion + i}, got ${journal[i].seq})`,\r\n );\r\n }\r\n }\r\n // Guard the id generator: `fromStored` preserves the checkpoint's node ids, but\r\n // `deps.idGen` starts fresh, so ids minted while replaying the journal could collide\r\n // with restored ids (a collision silently overwrites a live node in the node map and\r\n // corrupts the parent chain into a cycle — `pathOf` then never terminates). Skip any\r\n // id already in use; deterministic generators (SeqIdGen) simply advance past them.\r\n const usedIds = new Set(checkpoint.nodes.map((n) => n.id));\r\n const guardedDeps: TreeDeps = {\r\n ...deps,\r\n idGen: {\r\n next: () => {\r\n let id = deps.idGen.next();\r\n while (usedIds.has(id)) id = deps.idGen.next();\r\n usedIds.add(id);\r\n return id;\r\n },\r\n },\r\n };\r\n const { tree } = await restoreArtifact(checkpoint, guardedDeps, vectors);\r\n const addressing = new Addressing(tree);\r\n const replayLog = new EventLog(); // throwaway — the faithful log is rebuilt below\r\n // Mutator hooks are synchronous but the vector port is async — queue removals\r\n // during replay and flush them (awaited) afterwards, so a DB-backed index never\r\n // gets a fire-and-forget delete.\r\n const removedIds: NodeId[] = [];\r\n const mutator = new Mutator(tree, addressing, replayLog, {\r\n clock: deps.clock,\r\n onChange: (node) => {\r\n node.meta.embedding = { state: \"stale\" };\r\n },\r\n onRemove: (id) => {\r\n removedIds.push(id);\r\n },\r\n });\r\n replayForward(mutator, journal);\r\n for (const id of removedIds) await vectors.remove(id);\r\n const log = EventLog.fromStored([...checkpoint.events, ...journal], checkpoint.baseSeq ?? 0);\r\n return { tree, log };\r\n}\r\n"],"mappings":"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","names":["cid"]}
@@ -0,0 +1,17 @@
1
+ /** Turns text into vectors. Swappable; production adapters (OpenAI, local, etc.) implement this. */
2
+ interface EmbeddingPort {
3
+ readonly dims: number;
4
+ embed(texts: string[]): Promise<number[][]>;
5
+ }
6
+ /**
7
+ * Deterministic test/dev embedding: sums char codes into fixed-dimension buckets.
8
+ * Same text → same vector (so an exact-text query ranks its node first); no network.
9
+ */
10
+ declare class MockEmbeddingPort implements EmbeddingPort {
11
+ readonly dims: number;
12
+ constructor(dims?: number);
13
+ embed(texts: string[]): Promise<number[][]>;
14
+ private vectorize;
15
+ }
16
+
17
+ export { type EmbeddingPort, MockEmbeddingPort };
@@ -0,0 +1,21 @@
1
+ // src/embedding-port.ts
2
+ var MockEmbeddingPort = class {
3
+ constructor(dims = 32) {
4
+ this.dims = dims;
5
+ }
6
+ dims;
7
+ async embed(texts) {
8
+ return texts.map((t) => this.vectorize(t));
9
+ }
10
+ vectorize(text) {
11
+ const v = new Array(this.dims).fill(0);
12
+ for (let i = 0; i < text.length; i++) {
13
+ v[i % this.dims] += text.charCodeAt(i);
14
+ }
15
+ return v;
16
+ }
17
+ };
18
+ export {
19
+ MockEmbeddingPort
20
+ };
21
+ //# sourceMappingURL=embedding-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedding-port.ts"],"sourcesContent":["/** Turns text into vectors. Swappable; production adapters (OpenAI, local, etc.) implement this. */\r\nexport interface EmbeddingPort {\r\n readonly dims: number;\r\n embed(texts: string[]): Promise<number[][]>;\r\n}\r\n\r\n/**\r\n * Deterministic test/dev embedding: sums char codes into fixed-dimension buckets.\r\n * Same text → same vector (so an exact-text query ranks its node first); no network.\r\n */\r\nexport class MockEmbeddingPort implements EmbeddingPort {\r\n constructor(readonly dims = 32) {}\r\n\r\n async embed(texts: string[]): Promise<number[][]> {\r\n return texts.map((t) => this.vectorize(t));\r\n }\r\n\r\n private vectorize(text: string): number[] {\r\n const v = new Array<number>(this.dims).fill(0);\r\n for (let i = 0; i < text.length; i++) {\r\n v[i % this.dims] += text.charCodeAt(i);\r\n }\r\n return v;\r\n }\r\n}\r\n"],"mappings":";AAUO,IAAM,oBAAN,MAAiD;AAAA,EACtD,YAAqB,OAAO,IAAI;AAAX;AAAA,EAAY;AAAA,EAAZ;AAAA,EAErB,MAAM,MAAM,OAAsC;AAChD,WAAO,MAAM,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC;AAAA,EAC3C;AAAA,EAEQ,UAAU,MAAwB;AACxC,UAAM,IAAI,IAAI,MAAc,KAAK,IAAI,EAAE,KAAK,CAAC;AAC7C,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAE,IAAI,KAAK,IAAI,KAAK,KAAK,WAAW,CAAC;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ import { ArbNode, Json } from './types.js';
2
+ import { TypeDef } from './type-registry.js';
3
+
4
+ /**
5
+ * The text used to embed a node, or null if the node is not embedded.
6
+ * A registered type's `embedText` wins; otherwise: string leaf → its value;
7
+ * opaque object/array leaf → its JSON; numeric/boolean/null leaves and all
8
+ * structural containers → null.
9
+ */
10
+ declare function toEmbeddingText(node: ArbNode, value: Json, typeDef?: TypeDef): string | null;
11
+ /** Deterministic 32-bit FNV-1a hash, hex-encoded — used to dedupe re-embedding. */
12
+ declare function textHash(text: string): string;
13
+
14
+ export { textHash, toEmbeddingText };
@@ -0,0 +1,21 @@
1
+ // src/embedding-text.ts
2
+ function toEmbeddingText(node, value, typeDef) {
3
+ if (typeDef?.embedText) return typeDef.embedText(value);
4
+ if (node.kind !== "leaf") return null;
5
+ if (typeof value === "string") return value;
6
+ if (value !== null && typeof value === "object") return JSON.stringify(value);
7
+ return null;
8
+ }
9
+ function textHash(text) {
10
+ let h = 2166136261 >>> 0;
11
+ for (let i = 0; i < text.length; i++) {
12
+ h ^= text.charCodeAt(i);
13
+ h = Math.imul(h, 16777619) >>> 0;
14
+ }
15
+ return h.toString(16);
16
+ }
17
+ export {
18
+ textHash,
19
+ toEmbeddingText
20
+ };
21
+ //# sourceMappingURL=embedding-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedding-text.ts"],"sourcesContent":["import type { ArbNode, Json } from \"./types\";\r\nimport type { TypeDef } from \"./type-registry\";\r\n\r\n/**\r\n * The text used to embed a node, or null if the node is not embedded.\r\n * A registered type's `embedText` wins; otherwise: string leaf → its value;\r\n * opaque object/array leaf → its JSON; numeric/boolean/null leaves and all\r\n * structural containers → null.\r\n */\r\nexport function toEmbeddingText(node: ArbNode, value: Json, typeDef?: TypeDef): string | null {\r\n if (typeDef?.embedText) return typeDef.embedText(value);\r\n if (node.kind !== \"leaf\") return null;\r\n if (typeof value === \"string\") return value;\r\n if (value !== null && typeof value === \"object\") return JSON.stringify(value);\r\n return null;\r\n}\r\n\r\n/** Deterministic 32-bit FNV-1a hash, hex-encoded — used to dedupe re-embedding. */\r\nexport function textHash(text: string): string {\r\n let h = 2166136261 >>> 0;\r\n for (let i = 0; i < text.length; i++) {\r\n h ^= text.charCodeAt(i);\r\n h = Math.imul(h, 16777619) >>> 0;\r\n }\r\n return h.toString(16);\r\n}\r\n"],"mappings":";AASO,SAAS,gBAAgB,MAAe,OAAa,SAAkC;AAC5F,MAAI,SAAS,UAAW,QAAO,QAAQ,UAAU,KAAK;AACtD,MAAI,KAAK,SAAS,OAAQ,QAAO;AACjC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC5E,SAAO;AACT;AAGO,SAAS,SAAS,MAAsB;AAC7C,MAAI,IAAI,eAAe;AACvB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,SAAK,KAAK,WAAW,CAAC;AACtB,QAAI,KAAK,KAAK,GAAG,QAAQ,MAAM;AAAA,EACjC;AACA,SAAO,EAAE,SAAS,EAAE;AACtB;","names":[]}
@@ -0,0 +1,37 @@
1
+ import { NodeId } from './types.js';
2
+
3
+ /** A reference to a node: by stable id or by JSON Pointer path. */
4
+ type Ref = {
5
+ id: NodeId;
6
+ } | {
7
+ path: string;
8
+ };
9
+ declare class ArborError extends Error {
10
+ readonly code: string;
11
+ constructor(code: string, message: string);
12
+ }
13
+ declare class NodeNotFoundError extends ArborError {
14
+ readonly ref: Ref;
15
+ constructor(ref: Ref);
16
+ }
17
+ declare class ScopeViolationError extends ArborError {
18
+ readonly targetPath: string;
19
+ readonly scope: string;
20
+ constructor(targetPath: string, scope: string);
21
+ }
22
+ declare class StaleVersionError extends ArborError {
23
+ readonly id: NodeId;
24
+ readonly expected: number;
25
+ readonly actual: number;
26
+ constructor(id: NodeId, expected: number, actual: number);
27
+ }
28
+ declare class InvalidOpError extends ArborError {
29
+ constructor(message: string);
30
+ }
31
+ declare class ValidationError extends ArborError {
32
+ readonly type: string | undefined;
33
+ readonly details: string;
34
+ constructor(type: string | undefined, details: string);
35
+ }
36
+
37
+ export { ArborError, InvalidOpError, NodeNotFoundError, type Ref, ScopeViolationError, StaleVersionError, ValidationError };
package/dist/errors.js ADDED
@@ -0,0 +1,59 @@
1
+ // src/errors.ts
2
+ var ArborError = class extends Error {
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = new.target.name;
7
+ }
8
+ code;
9
+ };
10
+ var NodeNotFoundError = class extends ArborError {
11
+ constructor(ref) {
12
+ super("NODE_NOT_FOUND", `Node not found: ${JSON.stringify(ref)}`);
13
+ this.ref = ref;
14
+ }
15
+ ref;
16
+ };
17
+ var ScopeViolationError = class extends ArborError {
18
+ constructor(targetPath, scope) {
19
+ super("SCOPE_VIOLATION", `Access outside scope: ${targetPath} (scope: ${scope})`);
20
+ this.targetPath = targetPath;
21
+ this.scope = scope;
22
+ }
23
+ targetPath;
24
+ scope;
25
+ };
26
+ var StaleVersionError = class extends ArborError {
27
+ constructor(id, expected, actual) {
28
+ super("STALE_VERSION", `Stale version for ${id}: expected ${expected}, actual ${actual}`);
29
+ this.id = id;
30
+ this.expected = expected;
31
+ this.actual = actual;
32
+ }
33
+ id;
34
+ expected;
35
+ actual;
36
+ };
37
+ var InvalidOpError = class extends ArborError {
38
+ constructor(message) {
39
+ super("INVALID_OP", message);
40
+ }
41
+ };
42
+ var ValidationError = class extends ArborError {
43
+ constructor(type, details) {
44
+ super("VALIDATION_ERROR", `Validation failed${type ? ` for type ${type}` : ""}: ${details}`);
45
+ this.type = type;
46
+ this.details = details;
47
+ }
48
+ type;
49
+ details;
50
+ };
51
+ export {
52
+ ArborError,
53
+ InvalidOpError,
54
+ NodeNotFoundError,
55
+ ScopeViolationError,
56
+ StaleVersionError,
57
+ ValidationError
58
+ };
59
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type { NodeId } from \"./types\";\r\n\r\n/** A reference to a node: by stable id or by JSON Pointer path. */\r\nexport type Ref = { id: NodeId } | { path: string };\r\n\r\nexport class ArborError extends Error {\r\n constructor(\r\n public readonly code: string,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = new.target.name;\r\n }\r\n}\r\n\r\nexport class NodeNotFoundError extends ArborError {\r\n constructor(public readonly ref: Ref) {\r\n super(\"NODE_NOT_FOUND\", `Node not found: ${JSON.stringify(ref)}`);\r\n }\r\n}\r\n\r\nexport class ScopeViolationError extends ArborError {\r\n constructor(\r\n public readonly targetPath: string,\r\n public readonly scope: string,\r\n ) {\r\n super(\"SCOPE_VIOLATION\", `Access outside scope: ${targetPath} (scope: ${scope})`);\r\n }\r\n}\r\n\r\nexport class StaleVersionError extends ArborError {\r\n constructor(\r\n public readonly id: NodeId,\r\n public readonly expected: number,\r\n public readonly actual: number,\r\n ) {\r\n super(\"STALE_VERSION\", `Stale version for ${id}: expected ${expected}, actual ${actual}`);\r\n }\r\n}\r\n\r\nexport class InvalidOpError extends ArborError {\r\n constructor(message: string) {\r\n super(\"INVALID_OP\", message);\r\n }\r\n}\r\n\r\nexport class ValidationError extends ArborError {\r\n constructor(\r\n public readonly type: string | undefined,\r\n public readonly details: string,\r\n ) {\r\n super(\"VALIDATION_ERROR\", `Validation failed${type ? ` for type ${type}` : \"\"}: ${details}`);\r\n }\r\n}\r\n"],"mappings":";AAKO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EALkB;AAMpB;AAEO,IAAM,oBAAN,cAAgC,WAAW;AAAA,EAChD,YAA4B,KAAU;AACpC,UAAM,kBAAkB,mBAAmB,KAAK,UAAU,GAAG,CAAC,EAAE;AADtC;AAAA,EAE5B;AAAA,EAF4B;AAG9B;AAEO,IAAM,sBAAN,cAAkC,WAAW;AAAA,EAClD,YACkB,YACA,OAChB;AACA,UAAM,mBAAmB,yBAAyB,UAAU,YAAY,KAAK,GAAG;AAHhE;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;AAEO,IAAM,oBAAN,cAAgC,WAAW;AAAA,EAChD,YACkB,IACA,UACA,QAChB;AACA,UAAM,iBAAiB,qBAAqB,EAAE,cAAc,QAAQ,YAAY,MAAM,EAAE;AAJxE;AACA;AACA;AAAA,EAGlB;AAAA,EALkB;AAAA,EACA;AAAA,EACA;AAIpB;AAEO,IAAM,iBAAN,cAA6B,WAAW;AAAA,EAC7C,YAAY,SAAiB;AAC3B,UAAM,cAAc,OAAO;AAAA,EAC7B;AACF;AAEO,IAAM,kBAAN,cAA8B,WAAW;AAAA,EAC9C,YACkB,MACA,SAChB;AACA,UAAM,oBAAoB,oBAAoB,OAAO,aAAa,IAAI,KAAK,EAAE,KAAK,OAAO,EAAE;AAH3E;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;","names":[]}
@@ -0,0 +1,75 @@
1
+ import { NodeId, Json } from './types.js';
2
+
3
+ type OpKind = "set" | "insert" | "remove" | "move";
4
+ /**
5
+ * A recorded mutation. Carries enough to reverse it later (M7 replay):
6
+ * - set: before = old subtree value, after = new value
7
+ * - insert: after = inserted value (inverse is remove of targetId)
8
+ * - remove: before = removed subtree value (inverse is insert at parentId/key)
9
+ * - move: from/to capture old and new (parentId, key)
10
+ */
11
+ interface MutationEvent {
12
+ seq: number;
13
+ kind: OpKind;
14
+ targetId: NodeId;
15
+ parentId: NodeId | null;
16
+ key: string | number | null;
17
+ before?: Json;
18
+ after?: Json;
19
+ from?: {
20
+ parentId: NodeId | null;
21
+ key: string | number | null;
22
+ };
23
+ to?: {
24
+ parentId: NodeId | null;
25
+ key: string | number | null;
26
+ };
27
+ /** JSON Pointer of the affected node (set/insert: its path; remove: its pre-removal path). */
28
+ path?: string;
29
+ /** move: source path (before the move). */
30
+ fromPath?: string;
31
+ /** move: destination path (after the move). */
32
+ toPath?: string;
33
+ /** set/remove: the node's type BEFORE the op; insert/set: `nodeType` = type AFTER.
34
+ * `null` = explicitly untyped; ABSENT = pre-M10 event (unknown — replay keeps the current type). */
35
+ nodeTypeBefore?: string | null;
36
+ nodeType?: string | null;
37
+ /** set/remove: the node's tags BEFORE the op ([] = untagged).
38
+ * ABSENT = pre-M14 event (unknown — revert leaves the current tags alone). */
39
+ tagsBefore?: string[];
40
+ /** set/insert: the node's tags AFTER the op ([] = untagged). ABSENT = pre-M14. */
41
+ tags?: string[];
42
+ actor?: string;
43
+ ts: number;
44
+ }
45
+ /** Append-only log of mutations with monotonic, absolute seq. Supports compaction:
46
+ * events before `baseSeq` are dropped, but retained events keep their absolute seq
47
+ * and `length()` stays the absolute next-seq, so versions never shift. */
48
+ declare class EventLog {
49
+ private readonly events;
50
+ private baseSeq;
51
+ append(event: Omit<MutationEvent, "seq">): MutationEvent;
52
+ entries(): readonly MutationEvent[];
53
+ /** Absolute seq of the oldest retained event (0 until compaction). Versions below
54
+ * this have been compacted away and are no longer reconstructable. */
55
+ baseSeqValue(): number;
56
+ /** The event at absolute `seq`, or undefined if compacted away / past the end. */
57
+ at(seq: number): MutationEvent | undefined;
58
+ since(seq: number): MutationEvent[];
59
+ /** Absolute next-seq / current version (unchanged across compaction). */
60
+ length(): number;
61
+ /** Drop events past absolute `length` — used to roll back a failed transaction.
62
+ * Throws below the compaction floor: that history is gone and the log cannot roll
63
+ * back past it — `compactTo` must never run inside a transaction. */
64
+ truncateTo(length: number): void;
65
+ /** Compaction: drop every retained event with seq < `floorSeq` (history before it
66
+ * becomes unreconstructable). `floorSeq` is clamped to [baseSeq, length()].
67
+ * Returns the number of events dropped. Must NOT be called inside a Mutator
68
+ * transaction (rollback would need the dropped events). */
69
+ compactTo(floorSeq: number): number;
70
+ /** Rebuild a log from previously serialized events, preserving their seq + the
71
+ * compaction floor. */
72
+ static fromStored(events: MutationEvent[], baseSeq?: number): EventLog;
73
+ }
74
+
75
+ export { EventLog, type MutationEvent, type OpKind };
@@ -0,0 +1,82 @@
1
+ // src/errors.ts
2
+ var ArborError = class extends Error {
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = new.target.name;
7
+ }
8
+ code;
9
+ };
10
+ var InvalidOpError = class extends ArborError {
11
+ constructor(message) {
12
+ super("INVALID_OP", message);
13
+ }
14
+ };
15
+
16
+ // src/event-log.ts
17
+ var EventLog = class _EventLog {
18
+ events = [];
19
+ baseSeq = 0;
20
+ // count of compacted-away events; events[0].seq === baseSeq
21
+ append(event) {
22
+ const full = { ...event, seq: this.baseSeq + this.events.length };
23
+ this.events.push(full);
24
+ return full;
25
+ }
26
+ entries() {
27
+ return this.events;
28
+ }
29
+ /** Absolute seq of the oldest retained event (0 until compaction). Versions below
30
+ * this have been compacted away and are no longer reconstructable. */
31
+ baseSeqValue() {
32
+ return this.baseSeq;
33
+ }
34
+ /** The event at absolute `seq`, or undefined if compacted away / past the end. */
35
+ at(seq) {
36
+ const i = seq - this.baseSeq;
37
+ return i >= 0 && i < this.events.length ? this.events[i] : void 0;
38
+ }
39
+ since(seq) {
40
+ return this.events.filter((e) => e.seq >= seq);
41
+ }
42
+ /** Absolute next-seq / current version (unchanged across compaction). */
43
+ length() {
44
+ return this.baseSeq + this.events.length;
45
+ }
46
+ /** Drop events past absolute `length` — used to roll back a failed transaction.
47
+ * Throws below the compaction floor: that history is gone and the log cannot roll
48
+ * back past it — `compactTo` must never run inside a transaction. */
49
+ truncateTo(length) {
50
+ if (length < this.baseSeq) {
51
+ throw new InvalidOpError(
52
+ `cannot truncate to ${length}: events before ${this.baseSeq} were compacted away (compactTo must not run inside a transaction)`
53
+ );
54
+ }
55
+ this.events.length = length - this.baseSeq;
56
+ }
57
+ /** Compaction: drop every retained event with seq < `floorSeq` (history before it
58
+ * becomes unreconstructable). `floorSeq` is clamped to [baseSeq, length()].
59
+ * Returns the number of events dropped. Must NOT be called inside a Mutator
60
+ * transaction (rollback would need the dropped events). */
61
+ compactTo(floorSeq) {
62
+ const floor = Math.max(this.baseSeq, Math.min(floorSeq, this.length()));
63
+ const drop = floor - this.baseSeq;
64
+ if (drop > 0) {
65
+ this.events.splice(0, drop);
66
+ this.baseSeq = floor;
67
+ }
68
+ return drop;
69
+ }
70
+ /** Rebuild a log from previously serialized events, preserving their seq + the
71
+ * compaction floor. */
72
+ static fromStored(events, baseSeq = 0) {
73
+ const log = new _EventLog();
74
+ log.baseSeq = baseSeq;
75
+ for (const e of events) log.events.push({ ...e });
76
+ return log;
77
+ }
78
+ };
79
+ export {
80
+ EventLog
81
+ };
82
+ //# sourceMappingURL=event-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/event-log.ts"],"sourcesContent":["import type { NodeId } from \"./types\";\r\n\r\n/** A reference to a node: by stable id or by JSON Pointer path. */\r\nexport type Ref = { id: NodeId } | { path: string };\r\n\r\nexport class ArborError extends Error {\r\n constructor(\r\n public readonly code: string,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = new.target.name;\r\n }\r\n}\r\n\r\nexport class NodeNotFoundError extends ArborError {\r\n constructor(public readonly ref: Ref) {\r\n super(\"NODE_NOT_FOUND\", `Node not found: ${JSON.stringify(ref)}`);\r\n }\r\n}\r\n\r\nexport class ScopeViolationError extends ArborError {\r\n constructor(\r\n public readonly targetPath: string,\r\n public readonly scope: string,\r\n ) {\r\n super(\"SCOPE_VIOLATION\", `Access outside scope: ${targetPath} (scope: ${scope})`);\r\n }\r\n}\r\n\r\nexport class StaleVersionError extends ArborError {\r\n constructor(\r\n public readonly id: NodeId,\r\n public readonly expected: number,\r\n public readonly actual: number,\r\n ) {\r\n super(\"STALE_VERSION\", `Stale version for ${id}: expected ${expected}, actual ${actual}`);\r\n }\r\n}\r\n\r\nexport class InvalidOpError extends ArborError {\r\n constructor(message: string) {\r\n super(\"INVALID_OP\", message);\r\n }\r\n}\r\n\r\nexport class ValidationError extends ArborError {\r\n constructor(\r\n public readonly type: string | undefined,\r\n public readonly details: string,\r\n ) {\r\n super(\"VALIDATION_ERROR\", `Validation failed${type ? ` for type ${type}` : \"\"}: ${details}`);\r\n }\r\n}\r\n","import type { Json, NodeId } from \"./types\";\r\nimport { InvalidOpError } from \"./errors\";\r\n\r\nexport type OpKind = \"set\" | \"insert\" | \"remove\" | \"move\";\r\n\r\n/**\r\n * A recorded mutation. Carries enough to reverse it later (M7 replay):\r\n * - set: before = old subtree value, after = new value\r\n * - insert: after = inserted value (inverse is remove of targetId)\r\n * - remove: before = removed subtree value (inverse is insert at parentId/key)\r\n * - move: from/to capture old and new (parentId, key)\r\n */\r\nexport interface MutationEvent {\r\n seq: number;\r\n kind: OpKind;\r\n targetId: NodeId;\r\n parentId: NodeId | null;\r\n key: string | number | null;\r\n before?: Json;\r\n after?: Json;\r\n from?: { parentId: NodeId | null; key: string | number | null };\r\n to?: { parentId: NodeId | null; key: string | number | null };\r\n /** JSON Pointer of the affected node (set/insert: its path; remove: its pre-removal path). */\r\n path?: string;\r\n /** move: source path (before the move). */\r\n fromPath?: string;\r\n /** move: destination path (after the move). */\r\n toPath?: string;\r\n /** set/remove: the node's type BEFORE the op; insert/set: `nodeType` = type AFTER.\r\n * `null` = explicitly untyped; ABSENT = pre-M10 event (unknown — replay keeps the current type). */\r\n nodeTypeBefore?: string | null;\r\n nodeType?: string | null;\r\n /** set/remove: the node's tags BEFORE the op ([] = untagged).\r\n * ABSENT = pre-M14 event (unknown — revert leaves the current tags alone). */\r\n tagsBefore?: string[];\r\n /** set/insert: the node's tags AFTER the op ([] = untagged). ABSENT = pre-M14. */\r\n tags?: string[];\r\n actor?: string;\r\n ts: number;\r\n}\r\n\r\n/** Append-only log of mutations with monotonic, absolute seq. Supports compaction:\r\n * events before `baseSeq` are dropped, but retained events keep their absolute seq\r\n * and `length()` stays the absolute next-seq, so versions never shift. */\r\nexport class EventLog {\r\n private readonly events: MutationEvent[] = [];\r\n private baseSeq = 0; // count of compacted-away events; events[0].seq === baseSeq\r\n\r\n append(event: Omit<MutationEvent, \"seq\">): MutationEvent {\r\n const full: MutationEvent = { ...event, seq: this.baseSeq + this.events.length };\r\n this.events.push(full);\r\n return full;\r\n }\r\n\r\n entries(): readonly MutationEvent[] {\r\n return this.events;\r\n }\r\n\r\n /** Absolute seq of the oldest retained event (0 until compaction). Versions below\r\n * this have been compacted away and are no longer reconstructable. */\r\n baseSeqValue(): number {\r\n return this.baseSeq;\r\n }\r\n\r\n /** The event at absolute `seq`, or undefined if compacted away / past the end. */\r\n at(seq: number): MutationEvent | undefined {\r\n const i = seq - this.baseSeq;\r\n return i >= 0 && i < this.events.length ? this.events[i] : undefined;\r\n }\r\n\r\n since(seq: number): MutationEvent[] {\r\n return this.events.filter((e) => e.seq >= seq);\r\n }\r\n\r\n /** Absolute next-seq / current version (unchanged across compaction). */\r\n length(): number {\r\n return this.baseSeq + this.events.length;\r\n }\r\n\r\n /** Drop events past absolute `length` — used to roll back a failed transaction.\r\n * Throws below the compaction floor: that history is gone and the log cannot roll\r\n * back past it — `compactTo` must never run inside a transaction. */\r\n truncateTo(length: number): void {\r\n if (length < this.baseSeq) {\r\n throw new InvalidOpError(\r\n `cannot truncate to ${length}: events before ${this.baseSeq} were compacted away (compactTo must not run inside a transaction)`,\r\n );\r\n }\r\n this.events.length = length - this.baseSeq;\r\n }\r\n\r\n /** Compaction: drop every retained event with seq < `floorSeq` (history before it\r\n * becomes unreconstructable). `floorSeq` is clamped to [baseSeq, length()].\r\n * Returns the number of events dropped. Must NOT be called inside a Mutator\r\n * transaction (rollback would need the dropped events). */\r\n compactTo(floorSeq: number): number {\r\n const floor = Math.max(this.baseSeq, Math.min(floorSeq, this.length()));\r\n const drop = floor - this.baseSeq;\r\n if (drop > 0) {\r\n this.events.splice(0, drop);\r\n this.baseSeq = floor;\r\n }\r\n return drop;\r\n }\r\n\r\n /** Rebuild a log from previously serialized events, preserving their seq + the\r\n * compaction floor. */\r\n static fromStored(events: MutationEvent[], baseSeq = 0): EventLog {\r\n const log = new EventLog();\r\n log.baseSeq = baseSeq;\r\n for (const e of events) log.events.push({ ...e });\r\n return log;\r\n }\r\n}\r\n"],"mappings":";AAKO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EALkB;AAMpB;AA2BO,IAAM,iBAAN,cAA6B,WAAW;AAAA,EAC7C,YAAY,SAAiB;AAC3B,UAAM,cAAc,OAAO;AAAA,EAC7B;AACF;;;ACAO,IAAM,WAAN,MAAM,UAAS;AAAA,EACH,SAA0B,CAAC;AAAA,EACpC,UAAU;AAAA;AAAA,EAElB,OAAO,OAAkD;AACvD,UAAM,OAAsB,EAAE,GAAG,OAAO,KAAK,KAAK,UAAU,KAAK,OAAO,OAAO;AAC/E,SAAK,OAAO,KAAK,IAAI;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,UAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAIA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,GAAG,KAAwC;AACzC,UAAM,IAAI,MAAM,KAAK;AACrB,WAAO,KAAK,KAAK,IAAI,KAAK,OAAO,SAAS,KAAK,OAAO,CAAC,IAAI;AAAA,EAC7D;AAAA,EAEA,MAAM,KAA8B;AAClC,WAAO,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG;AAAA,EAC/C;AAAA;AAAA,EAGA,SAAiB;AACf,WAAO,KAAK,UAAU,KAAK,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,QAAsB;AAC/B,QAAI,SAAS,KAAK,SAAS;AACzB,YAAM,IAAI;AAAA,QACR,sBAAsB,MAAM,mBAAmB,KAAK,OAAO;AAAA,MAC7D;AAAA,IACF;AACA,SAAK,OAAO,SAAS,SAAS,KAAK;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,UAA0B;AAClC,UAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,KAAK,IAAI,UAAU,KAAK,OAAO,CAAC,CAAC;AACtE,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,OAAO,GAAG;AACZ,WAAK,OAAO,OAAO,GAAG,IAAI;AAC1B,WAAK,UAAU;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,OAAO,WAAW,QAAyB,UAAU,GAAa;AAChE,UAAM,MAAM,IAAI,UAAS;AACzB,QAAI,UAAU;AACd,eAAW,KAAK,OAAQ,KAAI,OAAO,KAAK,EAAE,GAAG,EAAE,CAAC;AAChD,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,22 @@
1
+ import { StoragePort, StoredArtifact } from './storage.js';
2
+ import './types.js';
3
+ import './artifact-tree.js';
4
+ import './ids.js';
5
+ import './clock.js';
6
+ import './decompose.js';
7
+ import './event-log.js';
8
+ import './vector-index-port.js';
9
+
10
+ /**
11
+ * File-backed StoragePort: one JSON file per artifact. `load` returns null if the
12
+ * file is absent. Saves are atomic (write tmp, then rename) so a crash mid-write
13
+ * never corrupts an existing artifact; loads validate the parsed shape.
14
+ */
15
+ declare class FileStorage implements StoragePort {
16
+ private readonly path;
17
+ constructor(path: string);
18
+ save(artifact: StoredArtifact): Promise<void>;
19
+ load(): Promise<StoredArtifact | null>;
20
+ }
21
+
22
+ export { FileStorage };
@@ -0,0 +1,42 @@
1
+ // src/file-storage.ts
2
+ import { readFile, writeFile, rename } from "fs/promises";
3
+ function isStoredArtifact(v) {
4
+ if (typeof v !== "object" || v === null) return false;
5
+ const a = v;
6
+ return (a["version"] === 1 || a["version"] === 2) && typeof a["rootId"] === "string" && Array.isArray(a["nodes"]) && Array.isArray(a["events"]) && Array.isArray(a["vectors"]);
7
+ }
8
+ var FileStorage = class {
9
+ constructor(path) {
10
+ this.path = path;
11
+ }
12
+ path;
13
+ async save(artifact) {
14
+ const tmp = this.path + ".tmp";
15
+ await writeFile(tmp, JSON.stringify(artifact), "utf8");
16
+ await rename(tmp, this.path);
17
+ }
18
+ async load() {
19
+ let text;
20
+ try {
21
+ text = await readFile(this.path, "utf8");
22
+ } catch (err) {
23
+ if (err.code === "ENOENT") return null;
24
+ throw err;
25
+ }
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(text);
29
+ } catch (err) {
30
+ const detail = err instanceof Error ? err.message : String(err);
31
+ throw new Error(`FileStorage: corrupt artifact file ${this.path}: ${detail}`);
32
+ }
33
+ if (!isStoredArtifact(parsed)) {
34
+ throw new Error(`FileStorage: invalid artifact file ${this.path} (unrecognized shape)`);
35
+ }
36
+ return parsed;
37
+ }
38
+ };
39
+ export {
40
+ FileStorage
41
+ };
42
+ //# sourceMappingURL=file-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/file-storage.ts"],"sourcesContent":["import { readFile, writeFile, rename } from \"node:fs/promises\";\r\nimport type { StoredArtifact, StoragePort } from \"./storage\";\r\n\r\nfunction isStoredArtifact(v: unknown): v is StoredArtifact {\r\n if (typeof v !== \"object\" || v === null) return false;\r\n const a = v as Record<string, unknown>;\r\n return (\r\n (a[\"version\"] === 1 || a[\"version\"] === 2) &&\r\n typeof a[\"rootId\"] === \"string\" &&\r\n Array.isArray(a[\"nodes\"]) &&\r\n Array.isArray(a[\"events\"]) &&\r\n Array.isArray(a[\"vectors\"])\r\n );\r\n}\r\n\r\n/**\r\n * File-backed StoragePort: one JSON file per artifact. `load` returns null if the\r\n * file is absent. Saves are atomic (write tmp, then rename) so a crash mid-write\r\n * never corrupts an existing artifact; loads validate the parsed shape.\r\n */\r\nexport class FileStorage implements StoragePort {\r\n constructor(private readonly path: string) {}\r\n\r\n async save(artifact: StoredArtifact): Promise<void> {\r\n const tmp = this.path + \".tmp\";\r\n await writeFile(tmp, JSON.stringify(artifact), \"utf8\");\r\n await rename(tmp, this.path);\r\n }\r\n\r\n async load(): Promise<StoredArtifact | null> {\r\n let text: string;\r\n try {\r\n text = await readFile(this.path, \"utf8\");\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\r\n throw err;\r\n }\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(text);\r\n } catch (err) {\r\n const detail = err instanceof Error ? err.message : String(err);\r\n throw new Error(`FileStorage: corrupt artifact file ${this.path}: ${detail}`);\r\n }\r\n if (!isStoredArtifact(parsed)) {\r\n throw new Error(`FileStorage: invalid artifact file ${this.path} (unrecognized shape)`);\r\n }\r\n return parsed;\r\n }\r\n}\r\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,cAAc;AAG5C,SAAS,iBAAiB,GAAiC;AACzD,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,UACG,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,MAAM,MACxC,OAAO,EAAE,QAAQ,MAAM,YACvB,MAAM,QAAQ,EAAE,OAAO,CAAC,KACxB,MAAM,QAAQ,EAAE,QAAQ,CAAC,KACzB,MAAM,QAAQ,EAAE,SAAS,CAAC;AAE9B;AAOO,IAAM,cAAN,MAAyC;AAAA,EAC9C,YAA6B,MAAc;AAAd;AAAA,EAAe;AAAA,EAAf;AAAA,EAE7B,MAAM,KAAK,UAAyC;AAClD,UAAM,MAAM,KAAK,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK,UAAU,QAAQ,GAAG,MAAM;AACrD,UAAM,OAAO,KAAK,KAAK,IAAI;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAuC;AAC3C,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,SAAS,KAAK,MAAM,MAAM;AAAA,IACzC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,YAAM;AAAA,IACR;AACA,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI,MAAM,sCAAsC,KAAK,IAAI,KAAK,MAAM,EAAE;AAAA,IAC9E;AACA,QAAI,CAAC,iBAAiB,MAAM,GAAG;AAC7B,YAAM,IAAI,MAAM,sCAAsC,KAAK,IAAI,uBAAuB;AAAA,IACxF;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/ids.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { NodeId } from './types.js';
2
+
3
+ interface IdGen {
4
+ next(): NodeId;
5
+ }
6
+ declare class UuidIdGen implements IdGen {
7
+ next(): NodeId;
8
+ }
9
+ /** Deterministic test double: n0, n1, n2, ... */
10
+ declare class SeqIdGen implements IdGen {
11
+ private readonly prefix;
12
+ private n;
13
+ constructor(prefix?: string);
14
+ next(): NodeId;
15
+ }
16
+
17
+ export { type IdGen, SeqIdGen, UuidIdGen };
package/dist/ids.js ADDED
@@ -0,0 +1,22 @@
1
+ // src/ids.ts
2
+ import { randomUUID } from "crypto";
3
+ var UuidIdGen = class {
4
+ next() {
5
+ return randomUUID();
6
+ }
7
+ };
8
+ var SeqIdGen = class {
9
+ constructor(prefix = "n") {
10
+ this.prefix = prefix;
11
+ }
12
+ prefix;
13
+ n = 0;
14
+ next() {
15
+ return `${this.prefix}${this.n++}`;
16
+ }
17
+ };
18
+ export {
19
+ SeqIdGen,
20
+ UuidIdGen
21
+ };
22
+ //# sourceMappingURL=ids.js.map