@co-engram/core 0.1.0 → 0.1.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/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts +1 -0
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +1 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/merge/anomaly-detector.d.ts +63 -0
- package/dist/merge/anomaly-detector.d.ts.map +1 -0
- package/dist/merge/anomaly-detector.js +128 -0
- package/dist/merge/anomaly-detector.js.map +1 -0
- package/dist/merge/arbitration.d.ts +18 -0
- package/dist/merge/arbitration.d.ts.map +1 -0
- package/dist/merge/arbitration.js +27 -0
- package/dist/merge/arbitration.js.map +1 -0
- package/dist/merge/auto-onboard.d.ts +56 -0
- package/dist/merge/auto-onboard.d.ts.map +1 -0
- package/dist/merge/auto-onboard.js +81 -0
- package/dist/merge/auto-onboard.js.map +1 -0
- package/dist/merge/backup.d.ts +27 -0
- package/dist/merge/backup.d.ts.map +1 -0
- package/dist/merge/backup.js +49 -0
- package/dist/merge/backup.js.map +1 -0
- package/dist/merge/content.d.ts +50 -0
- package/dist/merge/content.d.ts.map +1 -0
- package/dist/merge/content.js +137 -0
- package/dist/merge/content.js.map +1 -0
- package/dist/merge/cross-file-coordinator.d.ts +52 -0
- package/dist/merge/cross-file-coordinator.d.ts.map +1 -0
- package/dist/merge/cross-file-coordinator.js +222 -0
- package/dist/merge/cross-file-coordinator.js.map +1 -0
- package/dist/merge/data-root.d.ts +9 -0
- package/dist/merge/data-root.d.ts.map +1 -0
- package/dist/merge/data-root.js +42 -0
- package/dist/merge/data-root.js.map +1 -0
- package/dist/merge/driver-llm.d.ts +92 -0
- package/dist/merge/driver-llm.d.ts.map +1 -0
- package/dist/merge/driver-llm.js +174 -0
- package/dist/merge/driver-llm.js.map +1 -0
- package/dist/merge/driver-main.d.ts +29 -0
- package/dist/merge/driver-main.d.ts.map +1 -0
- package/dist/merge/driver-main.js +220 -0
- package/dist/merge/driver-main.js.map +1 -0
- package/dist/merge/evidence-union.d.ts +35 -0
- package/dist/merge/evidence-union.d.ts.map +1 -0
- package/dist/merge/evidence-union.js +88 -0
- package/dist/merge/evidence-union.js.map +1 -0
- package/dist/merge/frontmatter-rules.d.ts +38 -0
- package/dist/merge/frontmatter-rules.d.ts.map +1 -0
- package/dist/merge/frontmatter-rules.js +77 -0
- package/dist/merge/frontmatter-rules.js.map +1 -0
- package/dist/merge/frontmatter.d.ts +52 -0
- package/dist/merge/frontmatter.d.ts.map +1 -0
- package/dist/merge/frontmatter.js +211 -0
- package/dist/merge/frontmatter.js.map +1 -0
- package/dist/merge/index.d.ts +31 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +31 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/llm-arbiter.d.ts +85 -0
- package/dist/merge/llm-arbiter.d.ts.map +1 -0
- package/dist/merge/llm-arbiter.js +177 -0
- package/dist/merge/llm-arbiter.js.map +1 -0
- package/dist/merge/llm-contract.d.ts +86 -0
- package/dist/merge/llm-contract.d.ts.map +1 -0
- package/dist/merge/llm-contract.js +134 -0
- package/dist/merge/llm-contract.js.map +1 -0
- package/dist/merge/llm-prompt.d.ts +25 -0
- package/dist/merge/llm-prompt.d.ts.map +1 -0
- package/dist/merge/llm-prompt.js +73 -0
- package/dist/merge/llm-prompt.js.map +1 -0
- package/dist/merge/merge-engram.d.ts +33 -0
- package/dist/merge/merge-engram.d.ts.map +1 -0
- package/dist/merge/merge-engram.js +164 -0
- package/dist/merge/merge-engram.js.map +1 -0
- package/dist/merge/merge-stats.d.ts +68 -0
- package/dist/merge/merge-stats.d.ts.map +1 -0
- package/dist/merge/merge-stats.js +152 -0
- package/dist/merge/merge-stats.js.map +1 -0
- package/dist/merge/onboard.d.ts +72 -0
- package/dist/merge/onboard.d.ts.map +1 -0
- package/dist/merge/onboard.js +146 -0
- package/dist/merge/onboard.js.map +1 -0
- package/dist/merge/post-merge-hook.d.ts +77 -0
- package/dist/merge/post-merge-hook.d.ts.map +1 -0
- package/dist/merge/post-merge-hook.js +211 -0
- package/dist/merge/post-merge-hook.js.map +1 -0
- package/dist/merge/resolution-state.d.ts +41 -0
- package/dist/merge/resolution-state.d.ts.map +1 -0
- package/dist/merge/resolution-state.js +100 -0
- package/dist/merge/resolution-state.js.map +1 -0
- package/dist/merge/synapse-merger.d.ts +63 -0
- package/dist/merge/synapse-merger.d.ts.map +1 -0
- package/dist/merge/synapse-merger.js +277 -0
- package/dist/merge/synapse-merger.js.map +1 -0
- package/dist/merge/synapse-rules.d.ts +66 -0
- package/dist/merge/synapse-rules.d.ts.map +1 -0
- package/dist/merge/synapse-rules.js +112 -0
- package/dist/merge/synapse-rules.js.map +1 -0
- package/dist/merge/version.d.ts +10 -0
- package/dist/merge/version.d.ts.map +1 -0
- package/dist/merge/version.js +10 -0
- package/dist/merge/version.js.map +1 -0
- package/dist/merge-driver.cjs +9371 -0
- package/dist/observability/audit-log.d.ts +4 -1
- package/dist/observability/audit-log.d.ts.map +1 -1
- package/dist/observability/audit-log.js +3 -0
- package/dist/observability/audit-log.js.map +1 -1
- package/dist/tools/tool.d.ts +7 -0
- package/dist/tools/tool.d.ts.map +1 -1
- package/dist/tools/tool.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynapseMerger — orchestrator for synapse file 3-way merge (spec §6).
|
|
3
|
+
*
|
|
4
|
+
* Composes:
|
|
5
|
+
* - synapse-rules: immutable / updatedAt_arbitration / max_updatedAt
|
|
6
|
+
* - evidence-union: array_union
|
|
7
|
+
* - resolution-state: state_machine
|
|
8
|
+
* - passthrough: unknown fields kept from ours
|
|
9
|
+
*
|
|
10
|
+
* Identity fields (id/from/to/kind/createdBy/createdAt) — divergent edits
|
|
11
|
+
* escalate per spec §6.4 "kind 改变 → driver 不介入". Note that since
|
|
12
|
+
* kind is part of the file path (`synapses/{kind}/syn-{id}.yaml`), kind
|
|
13
|
+
* changes show up as rename/add in git itself, not as content conflict.
|
|
14
|
+
*
|
|
15
|
+
* `mergeSynapseFile` 是 sync 版本,只走 Layer A(机械规则)。
|
|
16
|
+
* `mergeSynapseFileAsync` 在 updatedAt_arbitration 字段 escalate 时追加
|
|
17
|
+
* Layer B(LLM)兜底;immutable 字段保持 escalate(spec §6.4)。
|
|
18
|
+
*
|
|
19
|
+
* @module @co-engram-core/merge
|
|
20
|
+
*/
|
|
21
|
+
import { parseSynapseFile, serializeSynapseFile, } from "../storage/synapse-store.js";
|
|
22
|
+
import { classifySynapseField } from "./synapse-rules.js";
|
|
23
|
+
import { mergeSynapseImmutableField } from "./synapse-rules.js";
|
|
24
|
+
import { mergeSynapseUpdatedAtField } from "./synapse-rules.js";
|
|
25
|
+
import { mergeSynapseMaxUpdatedAt } from "./synapse-rules.js";
|
|
26
|
+
import { ImmutableSynapseViolationError } from "./synapse-rules.js";
|
|
27
|
+
import { mergeEvidence } from "./evidence-union.js";
|
|
28
|
+
import { mergeResolutionState } from "./resolution-state.js";
|
|
29
|
+
export function mergeSynapseFile(params) {
|
|
30
|
+
const { baseRaw, oursRaw, theirsRaw, preResolvedFields } = params;
|
|
31
|
+
const base = parseSynapseFile(baseRaw);
|
|
32
|
+
const ours = parseSynapseFile(oursRaw);
|
|
33
|
+
const theirs = parseSynapseFile(theirsRaw);
|
|
34
|
+
const escalatedFields = [];
|
|
35
|
+
let arbitratedWinner = null;
|
|
36
|
+
// Mutable copy
|
|
37
|
+
const merged = {};
|
|
38
|
+
// Collect all keys from all sides
|
|
39
|
+
const allKeys = new Set([
|
|
40
|
+
...Object.keys(base),
|
|
41
|
+
...Object.keys(ours),
|
|
42
|
+
...Object.keys(theirs),
|
|
43
|
+
]);
|
|
44
|
+
for (const key of allKeys) {
|
|
45
|
+
const klass = classifySynapseField(key);
|
|
46
|
+
const baseV = base[key];
|
|
47
|
+
const oursV = ours[key];
|
|
48
|
+
const theirsV = theirs[key];
|
|
49
|
+
// Layer B 注入:如果字段已在 preResolvedFields 中,直接采用,跳过 Layer A
|
|
50
|
+
if (preResolvedFields && key in preResolvedFields) {
|
|
51
|
+
merged[key] = preResolvedFields[key];
|
|
52
|
+
// 不更新 arbitratedWinner(preResolvedFields 来源是 LLM,不是 updatedAt)
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
switch (klass) {
|
|
56
|
+
case "immutable": {
|
|
57
|
+
try {
|
|
58
|
+
merged[key] = mergeSynapseImmutableField({
|
|
59
|
+
base: baseV,
|
|
60
|
+
ours: oursV,
|
|
61
|
+
theirs: theirsV,
|
|
62
|
+
fieldName: key,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
if (e instanceof ImmutableSynapseViolationError) {
|
|
67
|
+
escalatedFields.push(key);
|
|
68
|
+
merged[key] = oursV; // provisional; will be overwritten by markers
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "updatedAt_arbitration": {
|
|
77
|
+
const r = mergeSynapseUpdatedAtField({
|
|
78
|
+
base: baseV,
|
|
79
|
+
ours: oursV,
|
|
80
|
+
theirs: theirsV,
|
|
81
|
+
oursUpdatedAt: ours.updatedAt,
|
|
82
|
+
theirsUpdatedAt: theirs.updatedAt,
|
|
83
|
+
});
|
|
84
|
+
if ("escalated" in r) {
|
|
85
|
+
escalatedFields.push(key);
|
|
86
|
+
merged[key] = oursV;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
merged[key] = r.value;
|
|
90
|
+
if (r.winner)
|
|
91
|
+
arbitratedWinner = r.winner;
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "max_updatedAt": {
|
|
96
|
+
merged[key] = mergeSynapseMaxUpdatedAt(ours.updatedAt, theirs.updatedAt);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "array_union": {
|
|
100
|
+
// Spec §6.1: only evidence is array_union.
|
|
101
|
+
merged[key] = mergeEvidence({
|
|
102
|
+
base: baseV ?? [],
|
|
103
|
+
ours: oursV ?? [],
|
|
104
|
+
theirs: theirsV ?? [],
|
|
105
|
+
});
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "state_machine": {
|
|
109
|
+
// resolutionState
|
|
110
|
+
const r = mergeResolutionState({
|
|
111
|
+
base: baseV,
|
|
112
|
+
ours: oursV,
|
|
113
|
+
theirs: theirsV,
|
|
114
|
+
});
|
|
115
|
+
if (r.merged)
|
|
116
|
+
merged[key] = r.merged;
|
|
117
|
+
if (r.strategy === "tie-keep-ours" ||
|
|
118
|
+
r.strategy === "higher-phase" ||
|
|
119
|
+
r.strategy === "higher-priority-status") {
|
|
120
|
+
// Surfaced for downstream evidence append (loser rationale)
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case "recomputed": {
|
|
125
|
+
// retrievalWeight is recomputed downstream; keep base value as placeholder.
|
|
126
|
+
merged[key] = baseV ?? oursV;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "passthrough":
|
|
130
|
+
default: {
|
|
131
|
+
// Unknown fields: keep ours if both present
|
|
132
|
+
merged[key] = oursV ?? theirsV ?? baseV;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (escalatedFields.length > 0) {
|
|
138
|
+
const wrapped = `<<<<<<< ours\n${oursRaw}\n=======\n${theirsRaw}\n>>>>>>> theirs\n`;
|
|
139
|
+
return {
|
|
140
|
+
merged: ours, // provisional; caller should use mergedContent
|
|
141
|
+
mergedContent: wrapped,
|
|
142
|
+
escalated: true,
|
|
143
|
+
strategy: `synapse escalated: ${escalatedFields.join(", ")}`,
|
|
144
|
+
arbitratedWinner: null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// Reconstruct Synapse from merged record.
|
|
148
|
+
const mergedSynapse = reconstructSynapse(merged);
|
|
149
|
+
const serialized = serializeSynapseFile(mergedSynapse, "en");
|
|
150
|
+
const strategyBits = [];
|
|
151
|
+
if (arbitratedWinner) {
|
|
152
|
+
strategyBits.push(`arbitrated(${arbitratedWinner})`);
|
|
153
|
+
}
|
|
154
|
+
strategyBits.push(`evidence[${mergedSynapse.evidence.length}]`, `updatedAt-max`);
|
|
155
|
+
return {
|
|
156
|
+
merged: mergedSynapse,
|
|
157
|
+
mergedContent: serialized,
|
|
158
|
+
escalated: false,
|
|
159
|
+
strategy: `synapse: ${strategyBits.join(" + ")}`,
|
|
160
|
+
arbitratedWinner,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Async 版 synapse 合并,在 Layer A escalate 后追加 Layer B(LLM)兜底。
|
|
165
|
+
*
|
|
166
|
+
* 触发条件(spec §5.7):
|
|
167
|
+
* - updatedAt_arbitration 字段(weight/direction/sourceSemantic/targetSemantic)
|
|
168
|
+
* 双方都改 + updatedAt 一致 → LLM 决定
|
|
169
|
+
*
|
|
170
|
+
* 不触发:
|
|
171
|
+
* - immutable 字段 escalate(spec §6.4: identity 字段冲突必须人工)
|
|
172
|
+
* - Layer A 已解决
|
|
173
|
+
*/
|
|
174
|
+
export async function mergeSynapseFileAsync(params) {
|
|
175
|
+
// Step 1: Layer A (sync)
|
|
176
|
+
const layerA = mergeSynapseFile(params);
|
|
177
|
+
if (!layerA.escalated) {
|
|
178
|
+
return layerA;
|
|
179
|
+
}
|
|
180
|
+
// Parse escalated field names from strategy string ("synapse escalated: a, b")
|
|
181
|
+
const fieldsList = layerA.strategy.replace(/^synapse escalated:\s*/, "");
|
|
182
|
+
const escalatedFields = fieldsList
|
|
183
|
+
.split(", ")
|
|
184
|
+
.map((s) => s.trim())
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
// Step 2: 仅对 updatedAt_arbitration 类型字段调 LLM
|
|
187
|
+
const base = parseSynapseFile(params.baseRaw);
|
|
188
|
+
const ours = parseSynapseFile(params.oursRaw);
|
|
189
|
+
const theirs = parseSynapseFile(params.theirsRaw);
|
|
190
|
+
const baseRec = base;
|
|
191
|
+
const oursRec = ours;
|
|
192
|
+
const theirsRec = theirs;
|
|
193
|
+
const preResolvedFields = {};
|
|
194
|
+
let resolvedCount = 0;
|
|
195
|
+
let llmAttempted = 0;
|
|
196
|
+
const stillEscalatedImmutable = [];
|
|
197
|
+
for (const fieldName of escalatedFields) {
|
|
198
|
+
const klass = classifySynapseField(fieldName);
|
|
199
|
+
if (klass !== "updatedAt_arbitration") {
|
|
200
|
+
// immutable / state_machine 等 — 不让 LLM 介入
|
|
201
|
+
stillEscalatedImmutable.push(fieldName);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
llmAttempted++;
|
|
205
|
+
const llmInput = {
|
|
206
|
+
conflictType: "synapse_field",
|
|
207
|
+
path: params.path,
|
|
208
|
+
fieldName,
|
|
209
|
+
base: baseRec[fieldName],
|
|
210
|
+
ours: oursRec[fieldName],
|
|
211
|
+
theirs: theirsRec[fieldName],
|
|
212
|
+
meta: {
|
|
213
|
+
oursUpdatedAt: ours.updatedAt,
|
|
214
|
+
theirsUpdatedAt: theirs.updatedAt,
|
|
215
|
+
oursUpdatedBy: oursRec.createdBy ?? "unknown",
|
|
216
|
+
theirsUpdatedBy: theirsRec.createdBy ?? "unknown",
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
const result = await params.arbiter.arbitrate(llmInput);
|
|
220
|
+
if (result.verdict.kind === "resolved") {
|
|
221
|
+
const output = result.verdict.output;
|
|
222
|
+
if (output.verdict === "merge" && "mergedValue" in output) {
|
|
223
|
+
preResolvedFields[fieldName] = output.mergedValue;
|
|
224
|
+
resolvedCount++;
|
|
225
|
+
}
|
|
226
|
+
else if (output.verdict === "ours") {
|
|
227
|
+
preResolvedFields[fieldName] = oursRec[fieldName];
|
|
228
|
+
resolvedCount++;
|
|
229
|
+
}
|
|
230
|
+
else if (output.verdict === "theirs") {
|
|
231
|
+
preResolvedFields[fieldName] = theirsRec[fieldName];
|
|
232
|
+
resolvedCount++;
|
|
233
|
+
}
|
|
234
|
+
// verdict=escalate → 不填,该字段继续走 Layer A escalate
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (resolvedCount === 0) {
|
|
238
|
+
// LLM 一无所获 — 返回原 escalate 结果
|
|
239
|
+
return layerA;
|
|
240
|
+
}
|
|
241
|
+
// Step 3: 用 preResolvedFields 重跑合并
|
|
242
|
+
const merged2 = mergeSynapseFile({
|
|
243
|
+
...params,
|
|
244
|
+
preResolvedFields,
|
|
245
|
+
});
|
|
246
|
+
// 注:如果仍有 immutable 字段 escalate,merged2.escalated 仍为 true
|
|
247
|
+
// 在 strategy 中标注 LLM 解决了多少
|
|
248
|
+
if (merged2.escalated) {
|
|
249
|
+
return merged2; // immutable 字段仍 escalate,strategy 不变
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
...merged2,
|
|
253
|
+
strategy: `${merged2.strategy} + llm:${resolvedCount}/${llmAttempted} resolved`,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function reconstructSynapse(record) {
|
|
257
|
+
// Ensure required fields are present (they are immutable so always exist).
|
|
258
|
+
return {
|
|
259
|
+
id: record.id,
|
|
260
|
+
from: record.from,
|
|
261
|
+
to: record.to,
|
|
262
|
+
kind: record.kind,
|
|
263
|
+
weight: typeof record.weight === "number" ? record.weight : 0.5,
|
|
264
|
+
direction: record.direction ?? "directional",
|
|
265
|
+
evidence: record.evidence ?? [],
|
|
266
|
+
createdBy: record.createdBy ?? "",
|
|
267
|
+
createdAt: record.createdAt ?? "",
|
|
268
|
+
updatedAt: record.updatedAt ?? "",
|
|
269
|
+
retrievalWeight: typeof record.retrievalWeight === "number"
|
|
270
|
+
? record.retrievalWeight
|
|
271
|
+
: 0.5,
|
|
272
|
+
sourceSemantic: record.sourceSemantic,
|
|
273
|
+
targetSemantic: record.targetSemantic,
|
|
274
|
+
resolutionState: record.resolutionState,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=synapse-merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synapse-merger.js","sourceRoot":"","sources":["../../src/merge/synapse-merger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synapse field classification and simple merge rules (spec §6.1).
|
|
3
|
+
*
|
|
4
|
+
* Field classes:
|
|
5
|
+
* - immutable : id/from/to/kind/createdBy/createdAt — identity
|
|
6
|
+
* - updatedAt_arbitration: weight/direction/sourceSemantic/targetSemantic
|
|
7
|
+
* - max_updatedAt : updatedAt itself
|
|
8
|
+
* - array_union : evidence (handled in evidence-union.ts)
|
|
9
|
+
* - recomputed : retrievalWeight (system-derived)
|
|
10
|
+
* - state_machine : resolutionState (handled in resolution-state.ts)
|
|
11
|
+
* - passthrough : unknown fields — keep base if unchanged, else ours
|
|
12
|
+
*
|
|
13
|
+
* @module @co-engram/core/merge
|
|
14
|
+
*/
|
|
15
|
+
export type SynapseFieldClass = "immutable" | "updatedAt_arbitration" | "max_updatedAt" | "array_union" | "recomputed" | "state_machine" | "passthrough";
|
|
16
|
+
export declare function classifySynapseField(fieldName: string): SynapseFieldClass;
|
|
17
|
+
/** Thrown when an immutable synapse field is divergently edited by both sides. */
|
|
18
|
+
export declare class ImmutableSynapseViolationError extends Error {
|
|
19
|
+
readonly fieldName: string;
|
|
20
|
+
readonly ours: unknown;
|
|
21
|
+
readonly theirs: unknown;
|
|
22
|
+
readonly base: unknown;
|
|
23
|
+
constructor(params: {
|
|
24
|
+
fieldName: string;
|
|
25
|
+
base: unknown;
|
|
26
|
+
ours: unknown;
|
|
27
|
+
theirs: unknown;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Merge an immutable synapse field.
|
|
32
|
+
*
|
|
33
|
+
* - base == ours == theirs → base
|
|
34
|
+
* - one side changed → that side's value
|
|
35
|
+
* - both sides changed to the same value → that value
|
|
36
|
+
* - both sides changed differently → throw ImmutableSynapseViolationError
|
|
37
|
+
*/
|
|
38
|
+
export declare function mergeSynapseImmutableField<T>(params: {
|
|
39
|
+
base: T;
|
|
40
|
+
ours: T;
|
|
41
|
+
theirs: T;
|
|
42
|
+
fieldName: string;
|
|
43
|
+
}): T;
|
|
44
|
+
/**
|
|
45
|
+
* Merge a field whose value is chosen by updatedAt arbitration.
|
|
46
|
+
*
|
|
47
|
+
* Returns:
|
|
48
|
+
* - { value, winner } on clean arbitration (winner is 'ours' | 'theirs')
|
|
49
|
+
* - { value, winner: null } when neither side changed
|
|
50
|
+
* - { escalated: true } when updatedAt ties (caller should escalate)
|
|
51
|
+
*/
|
|
52
|
+
export declare function mergeSynapseUpdatedAtField<T>(params: {
|
|
53
|
+
base: T;
|
|
54
|
+
ours: T;
|
|
55
|
+
theirs: T;
|
|
56
|
+
oursUpdatedAt: string;
|
|
57
|
+
theirsUpdatedAt: string;
|
|
58
|
+
}): {
|
|
59
|
+
value: T;
|
|
60
|
+
winner: "ours" | "theirs" | null;
|
|
61
|
+
} | {
|
|
62
|
+
escalated: true;
|
|
63
|
+
};
|
|
64
|
+
/** max(ours, theirs) for ISO timestamps (lexicographic compare works for UTC). */
|
|
65
|
+
export declare function mergeSynapseMaxUpdatedAt(oursUpdatedAt: string, theirsUpdatedAt: string): string;
|
|
66
|
+
//# sourceMappingURL=synapse-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synapse-rules.d.ts","sourceRoot":"","sources":["../../src/merge/synapse-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,uBAAuB,GACvB,eAAe,GACf,aAAa,GACb,YAAY,GACZ,eAAe,GACf,aAAa,CAAC;AAkBlB,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,CASzE;AAED,kFAAkF;AAClF,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,MAAM,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,OAAO,CAAC;QACd,IAAI,EAAE,OAAO,CAAC;QACd,MAAM,EAAE,OAAO,CAAC;KACjB;CAUF;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,CAAC,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,CAAC,CAWJ;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,CAAC,CAAC;IACV,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,CAYvE;AAED,kFAAkF;AAClF,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,MAAM,CAER"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synapse field classification and simple merge rules (spec §6.1).
|
|
3
|
+
*
|
|
4
|
+
* Field classes:
|
|
5
|
+
* - immutable : id/from/to/kind/createdBy/createdAt — identity
|
|
6
|
+
* - updatedAt_arbitration: weight/direction/sourceSemantic/targetSemantic
|
|
7
|
+
* - max_updatedAt : updatedAt itself
|
|
8
|
+
* - array_union : evidence (handled in evidence-union.ts)
|
|
9
|
+
* - recomputed : retrievalWeight (system-derived)
|
|
10
|
+
* - state_machine : resolutionState (handled in resolution-state.ts)
|
|
11
|
+
* - passthrough : unknown fields — keep base if unchanged, else ours
|
|
12
|
+
*
|
|
13
|
+
* @module @co-engram/core/merge
|
|
14
|
+
*/
|
|
15
|
+
const IMMUTABLE_SYNASPSE_FIELDS = new Set([
|
|
16
|
+
"id",
|
|
17
|
+
"from",
|
|
18
|
+
"to",
|
|
19
|
+
"kind",
|
|
20
|
+
"createdBy",
|
|
21
|
+
"createdAt",
|
|
22
|
+
]);
|
|
23
|
+
const UPDATED_AT_ARBITRATION_FIELDS = new Set([
|
|
24
|
+
"weight",
|
|
25
|
+
"direction",
|
|
26
|
+
"sourceSemantic",
|
|
27
|
+
"targetSemantic",
|
|
28
|
+
]);
|
|
29
|
+
export function classifySynapseField(fieldName) {
|
|
30
|
+
if (IMMUTABLE_SYNASPSE_FIELDS.has(fieldName))
|
|
31
|
+
return "immutable";
|
|
32
|
+
if (UPDATED_AT_ARBITRATION_FIELDS.has(fieldName))
|
|
33
|
+
return "updatedAt_arbitration";
|
|
34
|
+
if (fieldName === "updatedAt")
|
|
35
|
+
return "max_updatedAt";
|
|
36
|
+
if (fieldName === "evidence")
|
|
37
|
+
return "array_union";
|
|
38
|
+
if (fieldName === "retrievalWeight")
|
|
39
|
+
return "recomputed";
|
|
40
|
+
if (fieldName === "resolutionState")
|
|
41
|
+
return "state_machine";
|
|
42
|
+
return "passthrough";
|
|
43
|
+
}
|
|
44
|
+
/** Thrown when an immutable synapse field is divergently edited by both sides. */
|
|
45
|
+
export class ImmutableSynapseViolationError extends Error {
|
|
46
|
+
fieldName;
|
|
47
|
+
ours;
|
|
48
|
+
theirs;
|
|
49
|
+
base;
|
|
50
|
+
constructor(params) {
|
|
51
|
+
super(`Immutable synapse field "${params.fieldName}" divergently edited: ours=${JSON.stringify(params.ours)} theirs=${JSON.stringify(params.theirs)} base=${JSON.stringify(params.base)}`);
|
|
52
|
+
this.name = "ImmutableSynapseViolationError";
|
|
53
|
+
this.fieldName = params.fieldName;
|
|
54
|
+
this.ours = params.ours;
|
|
55
|
+
this.theirs = params.theirs;
|
|
56
|
+
this.base = params.base;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Merge an immutable synapse field.
|
|
61
|
+
*
|
|
62
|
+
* - base == ours == theirs → base
|
|
63
|
+
* - one side changed → that side's value
|
|
64
|
+
* - both sides changed to the same value → that value
|
|
65
|
+
* - both sides changed differently → throw ImmutableSynapseViolationError
|
|
66
|
+
*/
|
|
67
|
+
export function mergeSynapseImmutableField(params) {
|
|
68
|
+
const { base, ours, theirs, fieldName } = params;
|
|
69
|
+
if (ours === theirs)
|
|
70
|
+
return ours;
|
|
71
|
+
if (base === ours)
|
|
72
|
+
return theirs;
|
|
73
|
+
if (base === theirs)
|
|
74
|
+
return ours;
|
|
75
|
+
throw new ImmutableSynapseViolationError({
|
|
76
|
+
fieldName,
|
|
77
|
+
base,
|
|
78
|
+
ours,
|
|
79
|
+
theirs,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Merge a field whose value is chosen by updatedAt arbitration.
|
|
84
|
+
*
|
|
85
|
+
* Returns:
|
|
86
|
+
* - { value, winner } on clean arbitration (winner is 'ours' | 'theirs')
|
|
87
|
+
* - { value, winner: null } when neither side changed
|
|
88
|
+
* - { escalated: true } when updatedAt ties (caller should escalate)
|
|
89
|
+
*/
|
|
90
|
+
export function mergeSynapseUpdatedAtField(params) {
|
|
91
|
+
const { base, ours, theirs, oursUpdatedAt, theirsUpdatedAt } = params;
|
|
92
|
+
if (ours === theirs)
|
|
93
|
+
return { value: ours, winner: null };
|
|
94
|
+
if (base === ours && base === theirs)
|
|
95
|
+
return { value: base, winner: null };
|
|
96
|
+
// Only one side changed → take it without arbitration
|
|
97
|
+
if (base === ours)
|
|
98
|
+
return { value: theirs, winner: "theirs" };
|
|
99
|
+
if (base === theirs)
|
|
100
|
+
return { value: ours, winner: "ours" };
|
|
101
|
+
// Both sides changed differently → arbitrate by updatedAt
|
|
102
|
+
if (oursUpdatedAt === theirsUpdatedAt)
|
|
103
|
+
return { escalated: true };
|
|
104
|
+
return oursUpdatedAt > theirsUpdatedAt
|
|
105
|
+
? { value: ours, winner: "ours" }
|
|
106
|
+
: { value: theirs, winner: "theirs" };
|
|
107
|
+
}
|
|
108
|
+
/** max(ours, theirs) for ISO timestamps (lexicographic compare works for UTC). */
|
|
109
|
+
export function mergeSynapseMaxUpdatedAt(oursUpdatedAt, theirsUpdatedAt) {
|
|
110
|
+
return oursUpdatedAt >= theirsUpdatedAt ? oursUpdatedAt : theirsUpdatedAt;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=synapse-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synapse-rules.js","sourceRoot":"","sources":["../../src/merge/synapse-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driver bundle version (single source of truth).
|
|
3
|
+
*
|
|
4
|
+
* Bumped on any change to driver behavior. Onboard uses this to decide
|
|
5
|
+
* whether to overwrite ~/.co-engram/merge-driver.js.
|
|
6
|
+
*
|
|
7
|
+
* @module @co-engram/core/merge
|
|
8
|
+
*/
|
|
9
|
+
export declare const DRIVER_BUNDLE_VERSION = "0.1.0";
|
|
10
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/merge/version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,qBAAqB,UAAU,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driver bundle version (single source of truth).
|
|
3
|
+
*
|
|
4
|
+
* Bumped on any change to driver behavior. Onboard uses this to decide
|
|
5
|
+
* whether to overwrite ~/.co-engram/merge-driver.js.
|
|
6
|
+
*
|
|
7
|
+
* @module @co-engram/core/merge
|
|
8
|
+
*/
|
|
9
|
+
export const DRIVER_BUNDLE_VERSION = "0.1.0";
|
|
10
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/merge/version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC"}
|