@remnic/core 9.45.1 → 9.45.2
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.
- package/dist/access-operations.d.ts +3 -3
- package/dist/access-schema.d.ts +84 -84
- package/dist/{chunk-K442KOID.js → chunk-4L6HUREQ.js} +1 -1
- package/dist/chunk-4L6HUREQ.js.map +1 -0
- package/dist/index.d.ts +388 -388
- package/dist/reconcile/cursor.d.ts +11 -1
- package/dist/reconcile/cursor.js +54 -0
- package/dist/reconcile/cursor.js.map +1 -1
- package/dist/reconcile/manifest.d.ts +2 -2
- package/dist/reconcile/manifest.js +41 -4
- package/dist/reconcile/manifest.js.map +1 -1
- package/dist/reconcile/plan.d.ts +17 -1
- package/dist/reconcile/plan.js +1 -1
- package/dist/schemas.d.ts +76 -76
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/transfer/types.d.ts +66 -66
- package/package.json +2 -2
- package/src/reconcile/cursor.test.ts +30 -0
- package/src/reconcile/cursor.ts +63 -0
- package/src/reconcile/manifest.test.ts +93 -2
- package/src/reconcile/manifest.ts +56 -3
- package/src/reconcile/plan.ts +19 -0
- package/dist/chunk-K442KOID.js.map +0 -1
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { ReconcileSemanticAgreement, ReconcilePlanEntry } from './plan.js';
|
|
2
|
+
import '../offline-sync.js';
|
|
3
|
+
import '../types-rEwubvim.js';
|
|
4
|
+
import '../message-parts/index.js';
|
|
5
|
+
import '../bounded-jsonl-state.js';
|
|
6
|
+
import '../operator-doctor-types.js';
|
|
7
|
+
import '../types-continuity.js';
|
|
8
|
+
|
|
1
9
|
interface ConvergeCursorFileState {
|
|
2
10
|
path: string;
|
|
3
11
|
sha256: string;
|
|
@@ -10,12 +18,14 @@ interface ConvergeCursorState {
|
|
|
10
18
|
namespace: string;
|
|
11
19
|
lastConvergedAt?: string;
|
|
12
20
|
baseFiles: ConvergeCursorFileState[];
|
|
21
|
+
semanticAgreements?: ReconcileSemanticAgreement[];
|
|
13
22
|
completedPaths?: string[];
|
|
14
23
|
}
|
|
15
24
|
declare function hashPeerNamespace(peerUrl: string, namespace: string): string;
|
|
16
25
|
declare function defaultConvergeCursorPath(memoryDir: string, peerUrl: string, namespace: string): string;
|
|
26
|
+
declare function deriveConvergeCursorBase(entries: readonly ReconcilePlanEntry[], namespace: string, priorSemanticAgreements?: readonly ReconcileSemanticAgreement[]): Pick<ConvergeCursorState, "baseFiles" | "semanticAgreements">;
|
|
17
27
|
declare function normalizeConvergeCursor(input: unknown): ConvergeCursorState;
|
|
18
28
|
declare function readConvergeCursor(cursorPath: string): Promise<ConvergeCursorState | null>;
|
|
19
29
|
declare function writeConvergeCursor(cursorPath: string, cursor: ConvergeCursorState): Promise<void>;
|
|
20
30
|
|
|
21
|
-
export { type ConvergeCursorFileState, type ConvergeCursorState, defaultConvergeCursorPath, hashPeerNamespace, normalizeConvergeCursor, readConvergeCursor, writeConvergeCursor };
|
|
31
|
+
export { type ConvergeCursorFileState, type ConvergeCursorState, defaultConvergeCursorPath, deriveConvergeCursorBase, hashPeerNamespace, normalizeConvergeCursor, readConvergeCursor, writeConvergeCursor };
|
package/dist/reconcile/cursor.js
CHANGED
|
@@ -20,6 +20,48 @@ function defaultConvergeCursorPath(memoryDir, peerUrl, namespace) {
|
|
|
20
20
|
const key = hashPeerNamespace(peerUrl, namespace);
|
|
21
21
|
return path.join(path.resolve(memoryDir), ".remnic", "state", "converge-cursors", `${key}.json`);
|
|
22
22
|
}
|
|
23
|
+
function normalizeSemanticFileState(input) {
|
|
24
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
|
|
25
|
+
const file = input;
|
|
26
|
+
if (typeof file.path !== "string" || typeof file.sha256 !== "string") return void 0;
|
|
27
|
+
return { path: file.path, sha256: file.sha256 };
|
|
28
|
+
}
|
|
29
|
+
function digestAfterReconcile(entry) {
|
|
30
|
+
if (entry.action === "push") return entry.localSha256;
|
|
31
|
+
if (entry.action === "pull") return entry.peerSha256;
|
|
32
|
+
if (entry.action === "conflict") {
|
|
33
|
+
if (entry.resolution === "local-wins") return entry.localSha256;
|
|
34
|
+
if (entry.resolution === "peer-wins") return entry.peerSha256;
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
if (entry.action !== "identical") return void 0;
|
|
38
|
+
if (entry.localSha256 && entry.peerSha256 && entry.localSha256 !== entry.peerSha256) return void 0;
|
|
39
|
+
return entry.localSha256 ?? entry.peerSha256;
|
|
40
|
+
}
|
|
41
|
+
function semanticAgreementKey(agreement) {
|
|
42
|
+
return `${agreement.local.path}\0${agreement.peer.path}`;
|
|
43
|
+
}
|
|
44
|
+
function deriveConvergeCursorBase(entries, namespace, priorSemanticAgreements = []) {
|
|
45
|
+
const baseFiles = [];
|
|
46
|
+
const semanticAgreementsByPathPair = new Map(
|
|
47
|
+
priorSemanticAgreements.map((agreement) => [semanticAgreementKey(agreement), agreement])
|
|
48
|
+
);
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.namespace !== namespace) continue;
|
|
51
|
+
if (entry.semanticAgreement) {
|
|
52
|
+
semanticAgreementsByPathPair.set(semanticAgreementKey(entry.semanticAgreement), entry.semanticAgreement);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const sha256 = digestAfterReconcile(entry);
|
|
56
|
+
if (sha256) baseFiles.push({ path: entry.path, sha256 });
|
|
57
|
+
}
|
|
58
|
+
baseFiles.sort((left, right) => left.path.localeCompare(right.path));
|
|
59
|
+
const semanticAgreements = [...semanticAgreementsByPathPair.values()];
|
|
60
|
+
semanticAgreements.sort(
|
|
61
|
+
(left, right) => left.local.path.localeCompare(right.local.path) || left.peer.path.localeCompare(right.peer.path)
|
|
62
|
+
);
|
|
63
|
+
return { baseFiles, semanticAgreements };
|
|
64
|
+
}
|
|
23
65
|
function normalizeConvergeCursor(input) {
|
|
24
66
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
25
67
|
throw new Error("converge cursor must be an object");
|
|
@@ -50,6 +92,16 @@ function normalizeConvergeCursor(input) {
|
|
|
50
92
|
}
|
|
51
93
|
}
|
|
52
94
|
}
|
|
95
|
+
const semanticAgreements = [];
|
|
96
|
+
if (Array.isArray(obj.semanticAgreements)) {
|
|
97
|
+
for (const item of obj.semanticAgreements) {
|
|
98
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
99
|
+
const agreement = item;
|
|
100
|
+
const local = normalizeSemanticFileState(agreement.local);
|
|
101
|
+
const peer = normalizeSemanticFileState(agreement.peer);
|
|
102
|
+
if (local && peer) semanticAgreements.push({ local, peer });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
53
105
|
const completedPaths = [];
|
|
54
106
|
if (Array.isArray(obj.completedPaths)) {
|
|
55
107
|
for (const item of obj.completedPaths) {
|
|
@@ -64,6 +116,7 @@ function normalizeConvergeCursor(input) {
|
|
|
64
116
|
namespace: obj.namespace.trim(),
|
|
65
117
|
lastConvergedAt: typeof obj.lastConvergedAt === "string" ? obj.lastConvergedAt : void 0,
|
|
66
118
|
baseFiles,
|
|
119
|
+
semanticAgreements,
|
|
67
120
|
completedPaths
|
|
68
121
|
};
|
|
69
122
|
}
|
|
@@ -96,6 +149,7 @@ async function writeConvergeCursor(cursorPath, cursor) {
|
|
|
96
149
|
}
|
|
97
150
|
export {
|
|
98
151
|
defaultConvergeCursorPath,
|
|
152
|
+
deriveConvergeCursorBase,
|
|
99
153
|
hashPeerNamespace,
|
|
100
154
|
normalizeConvergeCursor,
|
|
101
155
|
readConvergeCursor,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/reconcile/cursor.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nexport interface ConvergeCursorFileState {\n path: string;\n sha256: string;\n mtimeMs?: number;\n bytes?: number;\n}\n\nexport interface ConvergeCursorState {\n version: 1;\n peerUrl: string;\n namespace: string;\n lastConvergedAt?: string;\n baseFiles: ConvergeCursorFileState[];\n completedPaths?: string[];\n}\n\nexport function hashPeerNamespace(peerUrl: string, namespace: string): string {\n let normalizedUrl: string;\n try {\n const url = new URL(peerUrl);\n const credentials =\n url.username || url.password\n ? `${url.username}${url.password ? `:${url.password}` : \"\"}@`\n : \"\";\n normalizedUrl =\n `${url.protocol.toLowerCase()}//${credentials}${url.hostname.toLowerCase()}` +\n `${url.port ? `:${url.port}` : \"\"}${url.pathname.replace(/\\/+$/, \"\")}${url.search}${url.hash}`;\n } catch {\n normalizedUrl = peerUrl.trim().replace(/\\/+$/, \"\").toLowerCase();\n }\n const normalizedNs = namespace.trim().toLowerCase();\n return createHash(\"sha256\")\n .update(`${normalizedUrl}\\0${normalizedNs}`)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\nexport function defaultConvergeCursorPath(\n memoryDir: string,\n peerUrl: string,\n namespace: string,\n): string {\n const key = hashPeerNamespace(peerUrl, namespace);\n return path.join(path.resolve(memoryDir), \".remnic\", \"state\", \"converge-cursors\", `${key}.json`);\n}\n\nexport function normalizeConvergeCursor(input: unknown): ConvergeCursorState {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) {\n throw new Error(\"converge cursor must be an object\");\n }\n const obj = input as Record<string, unknown>;\n if (obj.version !== 1) {\n throw new Error(\"converge cursor version must be 1\");\n }\n if (typeof obj.peerUrl !== \"string\" || !obj.peerUrl.trim()) {\n throw new Error(\"converge cursor missing peerUrl\");\n }\n if (typeof obj.namespace !== \"string\" || !obj.namespace.trim()) {\n throw new Error(\"converge cursor missing namespace\");\n }\n const baseFiles: ConvergeCursorFileState[] = [];\n if (Array.isArray(obj.baseFiles)) {\n for (const item of obj.baseFiles) {\n if (item && typeof item === \"object\") {\n const fileItem = item as Record<string, unknown>;\n if (typeof fileItem.path === \"string\" && typeof fileItem.sha256 === \"string\") {\n baseFiles.push({\n path: fileItem.path,\n sha256: fileItem.sha256,\n mtimeMs: typeof fileItem.mtimeMs === \"number\" ? fileItem.mtimeMs : undefined,\n bytes: typeof fileItem.bytes === \"number\" ? fileItem.bytes : undefined,\n });\n }\n }\n }\n }\n const completedPaths: string[] = [];\n if (Array.isArray(obj.completedPaths)) {\n for (const item of obj.completedPaths) {\n if (typeof item === \"string\") {\n completedPaths.push(item);\n }\n }\n }\n return {\n version: 1,\n peerUrl: obj.peerUrl.trim(),\n namespace: obj.namespace.trim(),\n lastConvergedAt: typeof obj.lastConvergedAt === \"string\" ? obj.lastConvergedAt : undefined,\n baseFiles,\n completedPaths,\n };\n}\n\nexport async function readConvergeCursor(\n cursorPath: string,\n): Promise<ConvergeCursorState | null> {\n try {\n const raw = await fs.readFile(path.resolve(cursorPath), \"utf-8\");\n const parsed = JSON.parse(raw);\n return normalizeConvergeCursor(parsed);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n return null;\n }\n}\n\nexport async function writeConvergeCursor(\n cursorPath: string,\n cursor: ConvergeCursorState,\n): Promise<void> {\n const normalized = normalizeConvergeCursor(cursor);\n const target = path.resolve(cursorPath);\n await fs.mkdir(path.dirname(target), { recursive: true });\n const tmp = path.join(\n path.dirname(target),\n `.converge-cursor.${process.pid}.${randomUUID()}.tmp`,\n );\n await fs.writeFile(tmp, JSON.stringify(normalized, null, 2) + \"\\n\", \"utf-8\");\n try {\n await fs.rename(tmp, target);\n } catch (error) {\n await fs.unlink(tmp).catch(() => {});\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/reconcile/cursor.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { ReconcilePlanEntry, ReconcileSemanticAgreement } from \"./plan.js\";\n\nexport interface ConvergeCursorFileState {\n path: string;\n sha256: string;\n mtimeMs?: number;\n bytes?: number;\n}\n\nexport interface ConvergeCursorState {\n version: 1;\n peerUrl: string;\n namespace: string;\n lastConvergedAt?: string;\n baseFiles: ConvergeCursorFileState[];\n semanticAgreements?: ReconcileSemanticAgreement[];\n completedPaths?: string[];\n}\n\nexport function hashPeerNamespace(peerUrl: string, namespace: string): string {\n let normalizedUrl: string;\n try {\n const url = new URL(peerUrl);\n const credentials =\n url.username || url.password\n ? `${url.username}${url.password ? `:${url.password}` : \"\"}@`\n : \"\";\n normalizedUrl =\n `${url.protocol.toLowerCase()}//${credentials}${url.hostname.toLowerCase()}` +\n `${url.port ? `:${url.port}` : \"\"}${url.pathname.replace(/\\/+$/, \"\")}${url.search}${url.hash}`;\n } catch {\n normalizedUrl = peerUrl.trim().replace(/\\/+$/, \"\").toLowerCase();\n }\n const normalizedNs = namespace.trim().toLowerCase();\n return createHash(\"sha256\")\n .update(`${normalizedUrl}\\0${normalizedNs}`)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\nexport function defaultConvergeCursorPath(\n memoryDir: string,\n peerUrl: string,\n namespace: string,\n): string {\n const key = hashPeerNamespace(peerUrl, namespace);\n return path.join(path.resolve(memoryDir), \".remnic\", \"state\", \"converge-cursors\", `${key}.json`);\n}\n\nfunction normalizeSemanticFileState(input: unknown): { path: string; sha256: string } | undefined {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) return undefined;\n const file = input as Record<string, unknown>;\n if (typeof file.path !== \"string\" || typeof file.sha256 !== \"string\") return undefined;\n return { path: file.path, sha256: file.sha256 };\n}\n\nfunction digestAfterReconcile(entry: ReconcilePlanEntry): string | undefined {\n if (entry.action === \"push\") return entry.localSha256;\n if (entry.action === \"pull\") return entry.peerSha256;\n if (entry.action === \"conflict\") {\n if (entry.resolution === \"local-wins\") return entry.localSha256;\n if (entry.resolution === \"peer-wins\") return entry.peerSha256;\n return undefined;\n }\n if (entry.action !== \"identical\") return undefined;\n if (entry.localSha256 && entry.peerSha256 && entry.localSha256 !== entry.peerSha256) return undefined;\n return entry.localSha256 ?? entry.peerSha256;\n}\n\nfunction semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {\n return `${agreement.local.path}\\0${agreement.peer.path}`;\n}\n\nexport function deriveConvergeCursorBase(\n entries: readonly ReconcilePlanEntry[],\n namespace: string,\n priorSemanticAgreements: readonly ReconcileSemanticAgreement[] = [],\n): Pick<ConvergeCursorState, \"baseFiles\" | \"semanticAgreements\"> {\n const baseFiles: ConvergeCursorFileState[] = [];\n const semanticAgreementsByPathPair = new Map(\n priorSemanticAgreements.map((agreement) => [semanticAgreementKey(agreement), agreement])\n );\n for (const entry of entries) {\n if (entry.namespace !== namespace) continue;\n if (entry.semanticAgreement) {\n semanticAgreementsByPathPair.set(semanticAgreementKey(entry.semanticAgreement), entry.semanticAgreement);\n continue;\n }\n const sha256 = digestAfterReconcile(entry);\n if (sha256) baseFiles.push({ path: entry.path, sha256 });\n }\n baseFiles.sort((left, right) => left.path.localeCompare(right.path));\n const semanticAgreements = [...semanticAgreementsByPathPair.values()];\n semanticAgreements.sort((left, right) =>\n left.local.path.localeCompare(right.local.path) || left.peer.path.localeCompare(right.peer.path)\n );\n return { baseFiles, semanticAgreements };\n}\n\nexport function normalizeConvergeCursor(input: unknown): ConvergeCursorState {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) {\n throw new Error(\"converge cursor must be an object\");\n }\n const obj = input as Record<string, unknown>;\n if (obj.version !== 1) {\n throw new Error(\"converge cursor version must be 1\");\n }\n if (typeof obj.peerUrl !== \"string\" || !obj.peerUrl.trim()) {\n throw new Error(\"converge cursor missing peerUrl\");\n }\n if (typeof obj.namespace !== \"string\" || !obj.namespace.trim()) {\n throw new Error(\"converge cursor missing namespace\");\n }\n const baseFiles: ConvergeCursorFileState[] = [];\n if (Array.isArray(obj.baseFiles)) {\n for (const item of obj.baseFiles) {\n if (item && typeof item === \"object\") {\n const fileItem = item as Record<string, unknown>;\n if (typeof fileItem.path === \"string\" && typeof fileItem.sha256 === \"string\") {\n baseFiles.push({\n path: fileItem.path,\n sha256: fileItem.sha256,\n mtimeMs: typeof fileItem.mtimeMs === \"number\" ? fileItem.mtimeMs : undefined,\n bytes: typeof fileItem.bytes === \"number\" ? fileItem.bytes : undefined,\n });\n }\n }\n }\n }\n const semanticAgreements: ReconcileSemanticAgreement[] = [];\n if (Array.isArray(obj.semanticAgreements)) {\n for (const item of obj.semanticAgreements) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n const agreement = item as Record<string, unknown>;\n const local = normalizeSemanticFileState(agreement.local);\n const peer = normalizeSemanticFileState(agreement.peer);\n if (local && peer) semanticAgreements.push({ local, peer });\n }\n }\n const completedPaths: string[] = [];\n if (Array.isArray(obj.completedPaths)) {\n for (const item of obj.completedPaths) {\n if (typeof item === \"string\") {\n completedPaths.push(item);\n }\n }\n }\n return {\n version: 1,\n peerUrl: obj.peerUrl.trim(),\n namespace: obj.namespace.trim(),\n lastConvergedAt: typeof obj.lastConvergedAt === \"string\" ? obj.lastConvergedAt : undefined,\n baseFiles,\n semanticAgreements,\n completedPaths,\n };\n}\n\nexport async function readConvergeCursor(\n cursorPath: string,\n): Promise<ConvergeCursorState | null> {\n try {\n const raw = await fs.readFile(path.resolve(cursorPath), \"utf-8\");\n const parsed = JSON.parse(raw);\n return normalizeConvergeCursor(parsed);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n return null;\n }\n}\n\nexport async function writeConvergeCursor(\n cursorPath: string,\n cursor: ConvergeCursorState,\n): Promise<void> {\n const normalized = normalizeConvergeCursor(cursor);\n const target = path.resolve(cursorPath);\n await fs.mkdir(path.dirname(target), { recursive: true });\n const tmp = path.join(\n path.dirname(target),\n `.converge-cursor.${process.pid}.${randomUUID()}.tmp`,\n );\n await fs.writeFile(tmp, JSON.stringify(normalized, null, 2) + \"\\n\", \"utf-8\");\n try {\n await fs.rename(tmp, target);\n } catch (error) {\n await fs.unlink(tmp).catch(() => {});\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { x as MemoryStatus } from '../types-rEwubvim.js';
|
|
2
|
-
import { ReconcileFileState, ReconcilePlan } from './plan.js';
|
|
2
|
+
import { ReconcileFileState, ReconcilePlan, ReconcileSemanticAgreement } from './plan.js';
|
|
3
3
|
import '../message-parts/index.js';
|
|
4
4
|
import '../bounded-jsonl-state.js';
|
|
5
5
|
import '../operator-doctor-types.js';
|
|
@@ -28,6 +28,6 @@ interface BuildReconcileManifestOptions {
|
|
|
28
28
|
cachedFiles?: Iterable<ReconcileManifestFile>;
|
|
29
29
|
}
|
|
30
30
|
declare function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest>;
|
|
31
|
-
declare function collapseActiveFactDuplicates(plan: ReconcilePlan, localManifests: ReadonlyMap<string, ReconcileManifest>, peerManifests: ReadonlyMap<string, ReconcileManifest>): ReconcilePlan;
|
|
31
|
+
declare function collapseActiveFactDuplicates(plan: ReconcilePlan, localManifests: ReadonlyMap<string, ReconcileManifest>, peerManifests: ReadonlyMap<string, ReconcileManifest>, priorSemanticAgreements?: ReadonlyMap<string, readonly ReconcileSemanticAgreement[]>): ReconcilePlan;
|
|
32
32
|
|
|
33
33
|
export { type BuildReconcileManifestOptions, RECONCILE_MANIFEST_FORMAT, RECONCILE_MANIFEST_SCHEMA_VERSION, type ReconcileManifest, type ReconcileManifestFile, type ReconcileMemoryIdentity, buildReconcileManifest, collapseActiveFactDuplicates };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
summarizeReconcilePlan
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-4L6HUREQ.js";
|
|
4
4
|
import "../chunk-HBVPYRDW.js";
|
|
5
5
|
import "../chunk-EGGE52UU.js";
|
|
6
6
|
import {
|
|
@@ -131,7 +131,19 @@ function comparePlanEntries(left, right) {
|
|
|
131
131
|
if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;
|
|
132
132
|
return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
|
|
133
133
|
}
|
|
134
|
-
function
|
|
134
|
+
function semanticAgreementKey(agreement) {
|
|
135
|
+
return `${agreement.local.path}\0${agreement.peer.path}`;
|
|
136
|
+
}
|
|
137
|
+
function classifySemanticChange(current, prior) {
|
|
138
|
+
if (!prior) return "unchanged";
|
|
139
|
+
const localChanged = current.local.sha256 !== prior.local.sha256;
|
|
140
|
+
const peerChanged = current.peer.sha256 !== prior.peer.sha256;
|
|
141
|
+
if (localChanged && peerChanged) return "both_modified";
|
|
142
|
+
if (localChanged) return "local_changed";
|
|
143
|
+
if (peerChanged) return "peer_changed";
|
|
144
|
+
return "unchanged";
|
|
145
|
+
}
|
|
146
|
+
function collapseActiveFactDuplicates(plan, localManifests, peerManifests, priorSemanticAgreements) {
|
|
135
147
|
const entriesByNamespace = /* @__PURE__ */ new Map();
|
|
136
148
|
for (const entry of plan.entries) {
|
|
137
149
|
const entries2 = entriesByNamespace.get(entry.namespace) ?? [];
|
|
@@ -147,6 +159,12 @@ function collapseActiveFactDuplicates(plan, localManifests, peerManifests) {
|
|
|
147
159
|
const peerByPath = activeFactByPath(peerManifest);
|
|
148
160
|
const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));
|
|
149
161
|
const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));
|
|
162
|
+
const priorSemanticByPathPair = new Map(
|
|
163
|
+
(priorSemanticAgreements?.get(namespace) ?? []).map((agreement) => [
|
|
164
|
+
semanticAgreementKey(agreement),
|
|
165
|
+
agreement
|
|
166
|
+
])
|
|
167
|
+
);
|
|
150
168
|
const localByHash = /* @__PURE__ */ new Map();
|
|
151
169
|
const peerByHash = /* @__PURE__ */ new Map();
|
|
152
170
|
for (const file of localByPath.values()) {
|
|
@@ -184,6 +202,18 @@ function collapseActiveFactDuplicates(plan, localManifests, peerManifests) {
|
|
|
184
202
|
...localCandidates.map((file) => file.path),
|
|
185
203
|
...peerCandidates.map((file) => file.path)
|
|
186
204
|
]);
|
|
205
|
+
const authoritativeSamePathEntries = new Set(entries2.filter(
|
|
206
|
+
(entry) => duplicatePaths.has(entry.path) && localFilesByPath.has(entry.path) && peerFilesByPath.has(entry.path) && entry.action !== "identical"
|
|
207
|
+
));
|
|
208
|
+
if (authoritativeSamePathEntries.size > 0) {
|
|
209
|
+
for (const entry of entries2) {
|
|
210
|
+
if (duplicatePaths.has(entry.path) && !authoritativeSamePathEntries.has(entry) && (entry.action === "pull" || entry.action === "push" || entry.action === "identical")) {
|
|
211
|
+
removed.add(entry);
|
|
212
|
+
changed = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
187
217
|
const unsafeEntry = entries2.some(
|
|
188
218
|
(entry) => duplicatePaths.has(entry.path) && (entry.action === "suppress" || entry.action === "conflict")
|
|
189
219
|
);
|
|
@@ -193,13 +223,20 @@ function collapseActiveFactDuplicates(plan, localManifests, peerManifests) {
|
|
|
193
223
|
removed.add(entry);
|
|
194
224
|
}
|
|
195
225
|
}
|
|
226
|
+
const semanticAgreement = {
|
|
227
|
+
local: { path: localPath, sha256: localFile.sha256 },
|
|
228
|
+
peer: { path: peerPath, sha256: peerFile.sha256 }
|
|
229
|
+
};
|
|
196
230
|
replacements.push({
|
|
197
231
|
path: localPath < peerPath ? localPath : peerPath,
|
|
198
232
|
namespace,
|
|
199
233
|
action: "identical",
|
|
200
234
|
reason: "semantic_duplicate",
|
|
201
|
-
|
|
202
|
-
|
|
235
|
+
semanticAgreement,
|
|
236
|
+
semanticChange: classifySemanticChange(
|
|
237
|
+
semanticAgreement,
|
|
238
|
+
priorSemanticByPathPair.get(semanticAgreementKey(semanticAgreement))
|
|
239
|
+
)
|
|
203
240
|
});
|
|
204
241
|
changed = true;
|
|
205
242
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/reconcile/manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { inferMemoryStatus } from \"../memory-lifecycle-ledger-utils.js\";\nimport { ContentHashIndex, type ContentHashPathEntry } from \"../storage/content-hash-index.js\";\nimport type { MemoryFrontmatter, MemoryStatus } from \"../types.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport {\n type ReconcileFileState,\n type ReconcilePlan,\n type ReconcilePlanEntry,\n summarizeReconcilePlan,\n} from \"./plan.js\";\n\nexport const RECONCILE_MANIFEST_FORMAT = \"remnic-reconcile-manifest\";\nexport const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;\n\nexport interface ReconcileMemoryIdentity {\n id: string;\n category: string;\n contentHash: string;\n status: MemoryStatus;\n}\n\nexport interface ReconcileManifestFile extends ReconcileFileState {\n memory?: ReconcileMemoryIdentity;\n}\n\ntype ActiveFactManifestFile = ReconcileManifestFile & { memory: ReconcileMemoryIdentity };\n\nexport interface ReconcileManifest {\n format: typeof RECONCILE_MANIFEST_FORMAT;\n schemaVersion: typeof RECONCILE_MANIFEST_SCHEMA_VERSION;\n files: ReconcileManifestFile[];\n}\n\nexport interface BuildReconcileManifestOptions {\n files: Iterable<ReconcileFileState>;\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>;\n cachedFiles?: Iterable<ReconcileManifestFile>;\n}\n\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/i;\nconst MEMORY_DIRS = new Set(RECALL_FALLBACK_DIRS);\n\nfunction isMemoryPath(filePath: string): boolean {\n if (!filePath.endsWith(\".md\")) return false;\n const segments = filePath.split(\"/\");\n let index = 0;\n if (segments[index] === \"cold\" || segments[index] === \"archive\") index += 1;\n return MEMORY_DIRS.has(segments[index] ?? \"\");\n}\n\nfunction parseScalar(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n try {\n const parsed = JSON.parse(trimmed) as unknown;\n return typeof parsed === \"string\" ? parsed : trimmed;\n } catch {\n return trimmed.slice(1, -1).replace(/\\\\\"/g, '\"');\n }\n }\n if (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\")) {\n return trimmed.slice(1, -1).replace(/''/g, \"'\");\n }\n return trimmed;\n}\n\nfunction parsedMemoryIdentity(filePath: string, raw: Buffer | string): ReconcileMemoryIdentity | undefined {\n if (!isMemoryPath(filePath)) return undefined;\n const match = (Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : raw).match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) return undefined;\n const fields = new Map<string, string>();\n for (const line of match[1].split(\"\\n\")) {\n const separator = line.indexOf(\":\");\n if (separator > 0) fields.set(line.slice(0, separator).trim(), line.slice(separator + 1).trim());\n }\n const id = parseScalar(fields.get(\"id\"));\n if (!id) return undefined;\n const category = parseScalar(fields.get(\"category\")) ?? \"fact\";\n const storedHash = parseScalar(fields.get(\"contentHash\"));\n const contentHash =\n storedHash && SHA256_PATTERN.test(storedHash)\n ? storedHash.toLowerCase()\n : ContentHashIndex.computeHash(match[2].trim());\n const status = inferMemoryStatus(\n {\n status: parseScalar(fields.get(\"status\")) as MemoryStatus | undefined,\n archivedAt: parseScalar(fields.get(\"archivedAt\")),\n } as MemoryFrontmatter,\n filePath\n );\n return { id, category, contentHash, status };\n}\n\nexport async function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest> {\n const cachedByPath = new Map<string, ReconcileManifestFile>();\n for (const cached of options.cachedFiles ?? []) {\n cachedByPath.set(cached.path, cached);\n }\n\n const files: ReconcileManifestFile[] = [];\n for (const file of options.files) {\n const cached = cachedByPath.get(file.path);\n if (cached?.sha256.toLowerCase() === file.sha256.toLowerCase()) {\n files.push({ ...file, ...(cached.memory ? { memory: cached.memory } : {}) });\n continue;\n }\n\n let raw: Buffer | string | null = null;\n if (isMemoryPath(file.path)) {\n try {\n raw = await options.readFile(file);\n } catch {\n raw = null;\n }\n }\n if (raw !== null && createHash(\"sha256\").update(raw).digest(\"hex\") !== file.sha256.toLowerCase()) {\n raw = null;\n }\n const memory = raw === null ? undefined : parsedMemoryIdentity(file.path, raw);\n files.push({ ...file, ...(memory ? { memory } : {}) });\n }\n files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));\n return {\n format: RECONCILE_MANIFEST_FORMAT,\n schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,\n files,\n };\n}\n\nfunction activeFactByPath(manifest: ReconcileManifest | undefined): Map<string, ActiveFactManifestFile> {\n const result = new Map<string, ActiveFactManifestFile>();\n for (const file of manifest?.files ?? []) {\n if (file.memory?.category === \"fact\" && file.memory.status === \"active\") {\n result.set(file.path, file as ActiveFactManifestFile);\n }\n }\n return result;\n}\n\nfunction contentHashRows(files: Iterable<ActiveFactManifestFile>): ContentHashPathEntry[] {\n const rows: ContentHashPathEntry[] = [];\n for (const file of files) {\n rows.push({ path: file.path, contentHash: file.memory.contentHash });\n }\n return rows;\n}\n\nfunction comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {\n if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n\nexport function collapseActiveFactDuplicates(\n plan: ReconcilePlan,\n localManifests: ReadonlyMap<string, ReconcileManifest>,\n peerManifests: ReadonlyMap<string, ReconcileManifest>\n): ReconcilePlan {\n const entriesByNamespace = new Map<string, ReconcilePlanEntry[]>();\n for (const entry of plan.entries) {\n const entries = entriesByNamespace.get(entry.namespace) ?? [];\n entries.push({ ...entry });\n entriesByNamespace.set(entry.namespace, entries);\n }\n\n let changed = false;\n for (const namespace of new Set([...localManifests.keys(), ...peerManifests.keys()])) {\n const entries = entriesByNamespace.get(namespace) ?? [];\n const localManifest = localManifests.get(namespace);\n const peerManifest = peerManifests.get(namespace);\n const localByPath = activeFactByPath(localManifest);\n const peerByPath = activeFactByPath(peerManifest);\n const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));\n const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));\n const localByHash = new Map<string, ActiveFactManifestFile[]>();\n const peerByHash = new Map<string, ActiveFactManifestFile[]>();\n\n for (const file of localByPath.values()) {\n const hash = file.memory.contentHash;\n const bucket = localByHash.get(hash) ?? [];\n bucket.push(file);\n localByHash.set(hash, bucket);\n }\n for (const file of peerByPath.values()) {\n const hash = file.memory.contentHash;\n const bucket = peerByHash.get(hash) ?? [];\n bucket.push(file);\n peerByHash.set(hash, bucket);\n }\n\n const removed = new Set<ReconcilePlanEntry>();\n const replacements: ReconcilePlanEntry[] = [];\n for (const hash of new Set([...localByHash.keys(), ...peerByHash.keys()])) {\n const localCandidates = (localByHash.get(hash) ?? []).filter((file) => {\n const opposite = peerFilesByPath.get(file.path);\n const activeOpposite = peerByPath.get(file.path);\n return opposite === undefined || activeOpposite?.memory.contentHash === hash;\n });\n const peerCandidates = (peerByHash.get(hash) ?? []).filter((file) => {\n const opposite = localFilesByPath.get(file.path);\n const activeOpposite = localByPath.get(file.path);\n return opposite === undefined || activeOpposite?.memory.contentHash === hash;\n });\n const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates));\n const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates));\n\n if (localPath && peerPath && localPath !== peerPath) {\n const localFile = localByPath.get(localPath);\n const peerFile = peerByPath.get(peerPath);\n if (!localFile || !peerFile) continue;\n const duplicatePaths = new Set([\n ...localCandidates.map((file) => file.path),\n ...peerCandidates.map((file) => file.path),\n ]);\n const unsafeEntry = entries.some(\n (entry) => duplicatePaths.has(entry.path) && (entry.action === \"suppress\" || entry.action === \"conflict\")\n );\n if (unsafeEntry) continue;\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path) &&\n (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n }\n }\n replacements.push({\n path: localPath < peerPath ? localPath : peerPath,\n namespace,\n action: \"identical\",\n reason: \"semantic_duplicate\",\n localSha256: localFile.sha256,\n peerSha256: peerFile.sha256,\n });\n changed = true;\n continue;\n }\n\n const sameSideEntries = entries.filter((entry) => {\n if (localPath && entry.reason === \"local_only\") {\n return localCandidates.some((file) => file.path === entry.path);\n }\n if (peerPath && entry.reason === \"peer_only\") {\n return peerCandidates.some((file) => file.path === entry.path);\n }\n return false;\n });\n const canonicalPath = localPath ?? peerPath;\n const hasSharedCanonical = localPath !== undefined && localPath === peerPath;\n if (!canonicalPath || sameSideEntries.length === 0 || (!hasSharedCanonical && sameSideEntries.length < 2)) {\n continue;\n }\n for (const entry of sameSideEntries) {\n if (entry.path !== canonicalPath) {\n removed.add(entry);\n changed = true;\n }\n }\n }\n\n if (removed.size > 0 || replacements.length > 0) {\n entriesByNamespace.set(\n namespace,\n [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)\n );\n }\n }\n\n if (!changed) return plan;\n const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);\n return {\n entries,\n byNamespace: summarizeReconcilePlan(entries),\n converged: entries.every((entry) => entry.action === \"identical\"),\n };\n}\n"],"mappings":"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","names":["entries"]}
|
|
1
|
+
{"version":3,"sources":["../../src/reconcile/manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { inferMemoryStatus } from \"../memory-lifecycle-ledger-utils.js\";\nimport { ContentHashIndex, type ContentHashPathEntry } from \"../storage/content-hash-index.js\";\nimport type { MemoryFrontmatter, MemoryStatus } from \"../types.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport {\n type ReconcileFileState,\n type ReconcilePlan,\n type ReconcilePlanEntry,\n type ReconcileSemanticAgreement,\n type ReconcileSemanticChange,\n summarizeReconcilePlan,\n} from \"./plan.js\";\n\nexport const RECONCILE_MANIFEST_FORMAT = \"remnic-reconcile-manifest\";\nexport const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;\n\nexport interface ReconcileMemoryIdentity {\n id: string;\n category: string;\n contentHash: string;\n status: MemoryStatus;\n}\n\nexport interface ReconcileManifestFile extends ReconcileFileState {\n memory?: ReconcileMemoryIdentity;\n}\n\ntype ActiveFactManifestFile = ReconcileManifestFile & { memory: ReconcileMemoryIdentity };\n\nexport interface ReconcileManifest {\n format: typeof RECONCILE_MANIFEST_FORMAT;\n schemaVersion: typeof RECONCILE_MANIFEST_SCHEMA_VERSION;\n files: ReconcileManifestFile[];\n}\n\nexport interface BuildReconcileManifestOptions {\n files: Iterable<ReconcileFileState>;\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>;\n cachedFiles?: Iterable<ReconcileManifestFile>;\n}\n\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/i;\nconst MEMORY_DIRS = new Set(RECALL_FALLBACK_DIRS);\n\nfunction isMemoryPath(filePath: string): boolean {\n if (!filePath.endsWith(\".md\")) return false;\n const segments = filePath.split(\"/\");\n let index = 0;\n if (segments[index] === \"cold\" || segments[index] === \"archive\") index += 1;\n return MEMORY_DIRS.has(segments[index] ?? \"\");\n}\n\nfunction parseScalar(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n try {\n const parsed = JSON.parse(trimmed) as unknown;\n return typeof parsed === \"string\" ? parsed : trimmed;\n } catch {\n return trimmed.slice(1, -1).replace(/\\\\\"/g, '\"');\n }\n }\n if (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\")) {\n return trimmed.slice(1, -1).replace(/''/g, \"'\");\n }\n return trimmed;\n}\n\nfunction parsedMemoryIdentity(filePath: string, raw: Buffer | string): ReconcileMemoryIdentity | undefined {\n if (!isMemoryPath(filePath)) return undefined;\n const match = (Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : raw).match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) return undefined;\n const fields = new Map<string, string>();\n for (const line of match[1].split(\"\\n\")) {\n const separator = line.indexOf(\":\");\n if (separator > 0) fields.set(line.slice(0, separator).trim(), line.slice(separator + 1).trim());\n }\n const id = parseScalar(fields.get(\"id\"));\n if (!id) return undefined;\n const category = parseScalar(fields.get(\"category\")) ?? \"fact\";\n const storedHash = parseScalar(fields.get(\"contentHash\"));\n const contentHash =\n storedHash && SHA256_PATTERN.test(storedHash)\n ? storedHash.toLowerCase()\n : ContentHashIndex.computeHash(match[2].trim());\n const status = inferMemoryStatus(\n {\n status: parseScalar(fields.get(\"status\")) as MemoryStatus | undefined,\n archivedAt: parseScalar(fields.get(\"archivedAt\")),\n } as MemoryFrontmatter,\n filePath\n );\n return { id, category, contentHash, status };\n}\n\nexport async function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest> {\n const cachedByPath = new Map<string, ReconcileManifestFile>();\n for (const cached of options.cachedFiles ?? []) {\n cachedByPath.set(cached.path, cached);\n }\n\n const files: ReconcileManifestFile[] = [];\n for (const file of options.files) {\n const cached = cachedByPath.get(file.path);\n if (cached?.sha256.toLowerCase() === file.sha256.toLowerCase()) {\n files.push({ ...file, ...(cached.memory ? { memory: cached.memory } : {}) });\n continue;\n }\n\n let raw: Buffer | string | null = null;\n if (isMemoryPath(file.path)) {\n try {\n raw = await options.readFile(file);\n } catch {\n raw = null;\n }\n }\n if (raw !== null && createHash(\"sha256\").update(raw).digest(\"hex\") !== file.sha256.toLowerCase()) {\n raw = null;\n }\n const memory = raw === null ? undefined : parsedMemoryIdentity(file.path, raw);\n files.push({ ...file, ...(memory ? { memory } : {}) });\n }\n files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));\n return {\n format: RECONCILE_MANIFEST_FORMAT,\n schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,\n files,\n };\n}\n\nfunction activeFactByPath(manifest: ReconcileManifest | undefined): Map<string, ActiveFactManifestFile> {\n const result = new Map<string, ActiveFactManifestFile>();\n for (const file of manifest?.files ?? []) {\n if (file.memory?.category === \"fact\" && file.memory.status === \"active\") {\n result.set(file.path, file as ActiveFactManifestFile);\n }\n }\n return result;\n}\n\nfunction contentHashRows(files: Iterable<ActiveFactManifestFile>): ContentHashPathEntry[] {\n const rows: ContentHashPathEntry[] = [];\n for (const file of files) {\n rows.push({ path: file.path, contentHash: file.memory.contentHash });\n }\n return rows;\n}\n\nfunction comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {\n if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n\nfunction semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {\n return `${agreement.local.path}\\0${agreement.peer.path}`;\n}\n\nfunction classifySemanticChange(\n current: ReconcileSemanticAgreement,\n prior: ReconcileSemanticAgreement | undefined\n): ReconcileSemanticChange {\n if (!prior) return \"unchanged\";\n const localChanged = current.local.sha256 !== prior.local.sha256;\n const peerChanged = current.peer.sha256 !== prior.peer.sha256;\n if (localChanged && peerChanged) return \"both_modified\";\n if (localChanged) return \"local_changed\";\n if (peerChanged) return \"peer_changed\";\n return \"unchanged\";\n}\n\nexport function collapseActiveFactDuplicates(\n plan: ReconcilePlan,\n localManifests: ReadonlyMap<string, ReconcileManifest>,\n peerManifests: ReadonlyMap<string, ReconcileManifest>,\n priorSemanticAgreements?: ReadonlyMap<string, readonly ReconcileSemanticAgreement[]>,\n): ReconcilePlan {\n const entriesByNamespace = new Map<string, ReconcilePlanEntry[]>();\n for (const entry of plan.entries) {\n const entries = entriesByNamespace.get(entry.namespace) ?? [];\n entries.push({ ...entry });\n entriesByNamespace.set(entry.namespace, entries);\n }\n\n let changed = false;\n for (const namespace of new Set([...localManifests.keys(), ...peerManifests.keys()])) {\n const entries = entriesByNamespace.get(namespace) ?? [];\n const localManifest = localManifests.get(namespace);\n const peerManifest = peerManifests.get(namespace);\n const localByPath = activeFactByPath(localManifest);\n const peerByPath = activeFactByPath(peerManifest);\n const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));\n const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));\n const priorSemanticByPathPair = new Map(\n (priorSemanticAgreements?.get(namespace) ?? []).map((agreement) => [\n semanticAgreementKey(agreement),\n agreement,\n ])\n );\n const localByHash = new Map<string, ActiveFactManifestFile[]>();\n const peerByHash = new Map<string, ActiveFactManifestFile[]>();\n\n for (const file of localByPath.values()) {\n const hash = file.memory.contentHash;\n const bucket = localByHash.get(hash) ?? [];\n bucket.push(file);\n localByHash.set(hash, bucket);\n }\n for (const file of peerByPath.values()) {\n const hash = file.memory.contentHash;\n const bucket = peerByHash.get(hash) ?? [];\n bucket.push(file);\n peerByHash.set(hash, bucket);\n }\n\n const removed = new Set<ReconcilePlanEntry>();\n const replacements: ReconcilePlanEntry[] = [];\n for (const hash of new Set([...localByHash.keys(), ...peerByHash.keys()])) {\n const localCandidates = (localByHash.get(hash) ?? []).filter((file) => {\n const opposite = peerFilesByPath.get(file.path);\n const activeOpposite = peerByPath.get(file.path);\n return opposite === undefined || activeOpposite?.memory.contentHash === hash;\n });\n const peerCandidates = (peerByHash.get(hash) ?? []).filter((file) => {\n const opposite = localFilesByPath.get(file.path);\n const activeOpposite = localByPath.get(file.path);\n return opposite === undefined || activeOpposite?.memory.contentHash === hash;\n });\n const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates));\n const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates));\n\n if (localPath && peerPath && localPath !== peerPath) {\n const localFile = localByPath.get(localPath);\n const peerFile = peerByPath.get(peerPath);\n if (!localFile || !peerFile) continue;\n const duplicatePaths = new Set([\n ...localCandidates.map((file) => file.path),\n ...peerCandidates.map((file) => file.path),\n ]);\n const authoritativeSamePathEntries = new Set(entries.filter(\n (entry) =>\n duplicatePaths.has(entry.path)\n && localFilesByPath.has(entry.path)\n && peerFilesByPath.has(entry.path)\n && entry.action !== \"identical\"\n ));\n if (authoritativeSamePathEntries.size > 0) {\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path)\n && !authoritativeSamePathEntries.has(entry)\n && (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n changed = true;\n }\n }\n continue;\n }\n const unsafeEntry = entries.some(\n (entry) => duplicatePaths.has(entry.path) && (entry.action === \"suppress\" || entry.action === \"conflict\")\n );\n if (unsafeEntry) continue;\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path) &&\n (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n }\n }\n const semanticAgreement: ReconcileSemanticAgreement = {\n local: { path: localPath, sha256: localFile.sha256 },\n peer: { path: peerPath, sha256: peerFile.sha256 },\n };\n replacements.push({\n path: localPath < peerPath ? localPath : peerPath,\n namespace,\n action: \"identical\",\n reason: \"semantic_duplicate\",\n semanticAgreement,\n semanticChange: classifySemanticChange(\n semanticAgreement,\n priorSemanticByPathPair.get(semanticAgreementKey(semanticAgreement))\n ),\n });\n changed = true;\n continue;\n }\n\n const sameSideEntries = entries.filter((entry) => {\n if (localPath && entry.reason === \"local_only\") {\n return localCandidates.some((file) => file.path === entry.path);\n }\n if (peerPath && entry.reason === \"peer_only\") {\n return peerCandidates.some((file) => file.path === entry.path);\n }\n return false;\n });\n const canonicalPath = localPath ?? peerPath;\n const hasSharedCanonical = localPath !== undefined && localPath === peerPath;\n if (!canonicalPath || sameSideEntries.length === 0 || (!hasSharedCanonical && sameSideEntries.length < 2)) {\n continue;\n }\n for (const entry of sameSideEntries) {\n if (entry.path !== canonicalPath) {\n removed.add(entry);\n changed = true;\n }\n }\n }\n\n if (removed.size > 0 || replacements.length > 0) {\n entriesByNamespace.set(\n namespace,\n [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)\n );\n }\n }\n\n if (!changed) return plan;\n const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);\n return {\n entries,\n byNamespace: summarizeReconcilePlan(entries),\n converged: entries.every((entry) => entry.action === \"identical\"),\n };\n}\n"],"mappings":"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","names":["entries"]}
|
package/dist/reconcile/plan.d.ts
CHANGED
|
@@ -31,6 +31,15 @@ type ReconcileAction = "pull" | "push" | "identical" | "conflict" | "suppress";
|
|
|
31
31
|
* can assign distinct durable identities to both revisions.
|
|
32
32
|
*/
|
|
33
33
|
type ReconcileResolution = "local-wins" | "peer-wins" | "supersede-link" | "unresolved";
|
|
34
|
+
interface ReconcileSemanticFileState {
|
|
35
|
+
path: string;
|
|
36
|
+
sha256: string;
|
|
37
|
+
}
|
|
38
|
+
interface ReconcileSemanticAgreement {
|
|
39
|
+
local: ReconcileSemanticFileState;
|
|
40
|
+
peer: ReconcileSemanticFileState;
|
|
41
|
+
}
|
|
42
|
+
type ReconcileSemanticChange = "unchanged" | "local_changed" | "peer_changed" | "both_modified";
|
|
34
43
|
interface ReconcilePlanEntry {
|
|
35
44
|
path: string;
|
|
36
45
|
namespace: string;
|
|
@@ -50,6 +59,13 @@ interface ReconcilePlanEntry {
|
|
|
50
59
|
* delete the live copy instead of the retracted one.
|
|
51
60
|
*/
|
|
52
61
|
suppressSide?: "local" | "peer" | "both";
|
|
62
|
+
/**
|
|
63
|
+
* Real per-side identities for a cross-path semantic agreement. Synthetic
|
|
64
|
+
* rows omit the top-level side digests because `path` cannot name both files.
|
|
65
|
+
*/
|
|
66
|
+
semanticAgreement?: ReconcileSemanticAgreement;
|
|
67
|
+
/** Each side's current digest compared with its own prior semantic digest. */
|
|
68
|
+
semanticChange?: ReconcileSemanticChange;
|
|
53
69
|
}
|
|
54
70
|
type ReconcileReason = "peer_only" | "local_only"
|
|
55
71
|
/** Cursor showed only that side moved since agreement; both still hold the path. */
|
|
@@ -167,4 +183,4 @@ declare function summarizeReconcilePlan(entries: readonly ReconcilePlanEntry[]):
|
|
|
167
183
|
*/
|
|
168
184
|
declare function planReconciliation(namespaces: readonly ReconcileNamespaceInput[], options?: ReconcileOptions): ReconcilePlan;
|
|
169
185
|
|
|
170
|
-
export { type ReconcileAction, type ReconcileFileState, type ReconcileNamespaceInput, type ReconcileNamespaceReport, type ReconcileOptions, type ReconcilePlan, type ReconcilePlanEntry, ReconcilePlanInputError, type ReconcileReason, type ReconcileResolution, planNamespaceReconciliation, planReconciliation, summarizeReconcilePlan };
|
|
186
|
+
export { type ReconcileAction, type ReconcileFileState, type ReconcileNamespaceInput, type ReconcileNamespaceReport, type ReconcileOptions, type ReconcilePlan, type ReconcilePlanEntry, ReconcilePlanInputError, type ReconcileReason, type ReconcileResolution, type ReconcileSemanticAgreement, type ReconcileSemanticChange, type ReconcileSemanticFileState, planNamespaceReconciliation, planReconciliation, summarizeReconcilePlan };
|
package/dist/reconcile/plan.js
CHANGED