{"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../../../src/harness/session/fork.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAoC,KAAK,WAAW,EAAqB,MAAM,aAAa,CAAC;AAEpG,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,yFAAyF;IACzF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5B,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CAChB;AAYD,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,WAAW,GAAG,uBAAuB,CAsC5G;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,cAAc,EAAE,CActF","sourcesContent":["import type { CommittedWrite } from \"./commit.ts\";\nimport { classifyForkAddress } from \"./fork-policy.ts\";\nimport type { Entry, ForkOptions } from \"./types.ts\";\nimport { branchTip, laneConfig, laneState, type StoredValue, type Value, value } from \"./values.ts\";\n\nexport interface ForkSourceSnapshot {\n\tentries: Entry[];\n\tscalarValues: StoredValue<unknown>[];\n\t/** False when a backend supplied only the requested branch rather than the full tree. */\n\tentriesComplete?: boolean;\n}\n\nexport interface ForkDestinationSnapshot {\n\tentries: Map<string, Entry>;\n\tscalarValues: StoredValue<unknown>[];\n\tnextSeq: number;\n}\n\nfunction storedValuesInNamespace<T>(values: readonly StoredValue<unknown>[], address: Value<T>): StoredValue<T>[] {\n\treturn values.filter((stored) => stored.address.namespace === address.namespace) as StoredValue<T>[];\n}\n\nfunction findStoredValue<T>(values: readonly StoredValue<unknown>[], address: Value<T>): StoredValue<T> | undefined {\n\treturn values.find(\n\t\t(stored) => stored.address.namespace === address.namespace && stored.address.key === address.key,\n\t) as StoredValue<T> | undefined;\n}\n\n/** Build the complete logical state for a forked destination session. */\nexport function createForkSnapshot(source: ForkSourceSnapshot, options: ForkOptions): ForkDestinationSnapshot {\n\tconst sourceEntries = new Map(source.entries.map((entry) => [entry.id, entry]));\n\tconst sourceTips = storedValuesInNamespace(source.scalarValues, branchTip(\"\"));\n\tvalidateForkSourceSnapshot(source, sourceEntries, sourceTips, options);\n\n\tconst { entryIds, destinationTips } = selectForkContents(sourceEntries, sourceTips, options);\n\tconst entries = new Map<string, Entry>();\n\tfor (const id of entryIds) entries.set(id, sourceEntries.get(id)!);\n\n\tconst scalarValues: StoredValue<unknown>[] = [];\n\tlet nextSeq = Math.max(0, ...[...entries.values()].map((entry) => entry.seq)) + 1;\n\tconst store = <T>(address: Value<T>, storedValue: T): void => {\n\t\tscalarValues.push({\n\t\t\taddress: value<unknown>(address.namespace, address.key),\n\t\t\tvalue: storedValue,\n\t\t\tseq: nextSeq++,\n\t\t});\n\t};\n\tfor (const [branch, tipId] of destinationTips) {\n\t\tconst configuration = findStoredValue(source.scalarValues, laneConfig(branch));\n\t\tstore(branchTip(branch), tipId);\n\t\tif (configuration !== undefined) {\n\t\t\tstore(laneConfig(branch), configuration.value);\n\t\t\tstore(laneState(branch), { currentOperationId: null, lastOperationId: null, inbox: [] });\n\t\t}\n\t}\n\tfor (const stored of source.scalarValues) {\n\t\tswitch (classifyForkAddress(stored.address, options.scope, (entryId) => entryIds.has(entryId))) {\n\t\t\tcase \"copy\":\n\t\t\t\tstore(stored.address, stored.value);\n\t\t\t\tbreak;\n\t\t\tcase \"exclude\":\n\t\t\tcase \"reconstruct\":\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn { entries, scalarValues, nextSeq };\n}\n\nexport function forkSnapshotWrites(snapshot: ForkDestinationSnapshot): CommittedWrite[] {\n\tconst writes: CommittedWrite[] = [];\n\tfor (const entry of snapshot.entries.values()) writes.push({ kind: \"entry\", ...entry });\n\tfor (const stored of snapshot.scalarValues) {\n\t\twrites.push({\n\t\t\tkind: \"value\",\n\t\t\top: \"set\",\n\t\t\tseq: stored.seq,\n\t\t\tnamespace: stored.address.namespace,\n\t\t\tkey: stored.address.key,\n\t\t\tvalue: stored.value,\n\t\t});\n\t}\n\treturn writes.sort((left, right) => left.seq - right.seq);\n}\n\nfunction selectForkContents(\n\tsourceEntries: Map<string, Entry>,\n\tsourceTips: StoredValue<string | null>[],\n\toptions: ForkOptions,\n): {\n\tentryIds: Set<string>;\n\tdestinationTips: Map<string, string | null>;\n} {\n\tconst entryIds = new Set<string>();\n\tconst destinationTips = new Map<string, string | null>();\n\tif (options.scope === \"tree\") {\n\t\tfor (const id of sourceEntries.keys()) entryIds.add(id);\n\t\tfor (const stored of sourceTips) destinationTips.set(stored.address.key, stored.value);\n\t} else {\n\t\tconst sourceTip = sourceTips.find((stored) => stored.address.key === options.branch);\n\t\tif (sourceTip === undefined) throw new Error(`Unknown source branch: ${options.branch}`);\n\n\t\tconst requested = options.entryId ?? sourceTip.value;\n\t\tlet found = requested === null;\n\t\tlet tipId: string | null = null;\n\t\tlet entryId = sourceTip.value;\n\t\twhile (entryId !== null) {\n\t\t\tconst entry = sourceEntries.get(entryId);\n\t\t\tif (entry === undefined) throw new Error(`Corrupt source branch: missing parent ${entryId}`);\n\t\t\tif (entry.id === requested) {\n\t\t\t\tfound = true;\n\t\t\t\ttipId = options.position === \"before\" ? entry.parentId : entry.id;\n\t\t\t\tif (options.position !== \"before\") entryIds.add(entry.id);\n\t\t\t} else if (found) {\n\t\t\t\tentryIds.add(entry.id);\n\t\t\t}\n\t\t\tentryId = entry.parentId;\n\t\t}\n\t\tif (!found) {\n\t\t\tthrow new Error(`Fork entry ${requested} is not on source branch ${JSON.stringify(options.branch)}`);\n\t\t}\n\t\tdestinationTips.set(options.branch, tipId);\n\t}\n\treturn { entryIds, destinationTips };\n}\n\nfunction validateForkSourceSnapshot(\n\tsource: ForkSourceSnapshot,\n\tsourceEntries: Map<string, Entry>,\n\tsourceTips: StoredValue<string | null>[],\n\toptions: ForkOptions,\n): void {\n\tconst sourceTipKeys = new Set(sourceTips.map((stored) => stored.address.key));\n\n\tfor (const stored of source.scalarValues) {\n\t\tif (\n\t\t\t(stored.address.namespace === laneConfig(\"\").namespace ||\n\t\t\t\tstored.address.namespace === laneState(\"\").namespace) &&\n\t\t\t!sourceTipKeys.has(stored.address.key)\n\t\t) {\n\t\t\tthrow new Error(`Source session branch ${JSON.stringify(stored.address.key)} is missing branch.tip`);\n\t\t}\n\t}\n\tfor (const tip of sourceTips) {\n\t\tconst configuration = findStoredValue(source.scalarValues, laneConfig(tip.address.key));\n\t\tconst state = findStoredValue(source.scalarValues, laneState(tip.address.key));\n\t\tif ((configuration === undefined) !== (state === undefined)) {\n\t\t\tthrow new Error(`Source session branch ${JSON.stringify(tip.address.key)} has incomplete lane state`);\n\t\t}\n\t\tif (options.scope === \"branch\" && tip.address.key === options.branch && configuration === undefined) {\n\t\t\tthrow new Error(`Source branch ${JSON.stringify(options.branch)} is not a configured AgentLane`);\n\t\t}\n\t\tif (\n\t\t\t(source.entriesComplete !== false || options.scope === \"tree\") &&\n\t\t\ttip.value !== null &&\n\t\t\t!sourceEntries.has(tip.value)\n\t\t) {\n\t\t\tthrow new Error(`Source session branch ${JSON.stringify(tip.address.key)} has an unknown tip`);\n\t\t}\n\t}\n}\n"]}