openclaw-cortex-memory 0.1.0-Alpha.30 → 0.1.0-Alpha.32
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/LICENSE +21 -0
- package/README.md +46 -12
- package/SIGNATURE.md +7 -0
- package/SKILL.md +18 -3
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -6
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +120 -4
- package/dist/src/engine/memory_engine.d.ts +5 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +116 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +417 -102
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +17 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +23 -1
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +743 -70
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/quality/llm_output_validator.d.ts +20 -2
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -1
- package/dist/src/quality/llm_output_validator.js +296 -41
- package/dist/src/quality/llm_output_validator.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +8 -0
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +244 -84
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +72 -2
- package/dist/src/store/graph_memory_store.d.ts.map +1 -1
- package/dist/src/store/graph_memory_store.js +723 -50
- package/dist/src/store/graph_memory_store.js.map +1 -1
- package/dist/src/store/read_store.d.ts +3 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1004 -209
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +1 -0
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +1 -0
- package/dist/src/store/vector_store.js.map +1 -1
- package/dist/src/store/write_store.d.ts +2 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +45 -3
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +20 -1
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +1810 -161
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/wiki/wiki_linter.d.ts +25 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +268 -0
- package/dist/src/wiki/wiki_linter.js.map +1 -0
- package/dist/src/wiki/wiki_logger.d.ts +10 -0
- package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
- package/dist/src/wiki/wiki_logger.js +78 -0
- package/dist/src/wiki/wiki_logger.js.map +1 -0
- package/dist/src/wiki/wiki_maintainer.d.ts +36 -0
- package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
- package/dist/src/wiki/wiki_maintainer.js +38 -0
- package/dist/src/wiki/wiki_maintainer.js.map +1 -0
- package/dist/src/wiki/wiki_projector.d.ts +33 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +633 -0
- package/dist/src/wiki/wiki_projector.js.map +1 -0
- package/dist/src/wiki/wiki_queue.d.ts +29 -0
- package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
- package/dist/src/wiki/wiki_queue.js +137 -0
- package/dist/src/wiki/wiki_queue.js.map +1 -0
- package/openclaw.plugin.json +120 -4
- package/package.json +8 -4
- package/schema/graph.schema.yaml +188 -33
- package/skills/cortex-memory/SKILL.md +49 -0
- package/skills/cortex-memory/references/agent-manual.md +115 -0
- package/skills/cortex-memory/references/configuration.md +92 -0
- package/skills/cortex-memory/references/publish-checklist.md +46 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +181 -0
- package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
|
@@ -39,6 +39,15 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const crypto = __importStar(require("crypto"));
|
|
40
40
|
const ontology_1 = require("../graph/ontology");
|
|
41
41
|
const llm_output_validator_1 = require("../quality/llm_output_validator");
|
|
42
|
+
const wiki_queue_1 = require("../wiki/wiki_queue");
|
|
43
|
+
const wiki_logger_1 = require("../wiki/wiki_logger");
|
|
44
|
+
const wiki_maintainer_1 = require("../wiki/wiki_maintainer");
|
|
45
|
+
const SINGLE_VALUE_RELATION_TYPES = new Set([
|
|
46
|
+
"birthday_on",
|
|
47
|
+
"anniversary_on",
|
|
48
|
+
"has_spouse",
|
|
49
|
+
"lives_in",
|
|
50
|
+
]);
|
|
42
51
|
function ensureDirForFile(filePath) {
|
|
43
52
|
const dir = path.dirname(filePath);
|
|
44
53
|
if (!fs.existsSync(dir)) {
|
|
@@ -48,10 +57,362 @@ function ensureDirForFile(filePath) {
|
|
|
48
57
|
function generateGraphId() {
|
|
49
58
|
return `gph_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
50
59
|
}
|
|
60
|
+
function generateConflictId() {
|
|
61
|
+
return `gcf_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
62
|
+
}
|
|
63
|
+
function relationKey(relation) {
|
|
64
|
+
const source = (relation.source || "").trim().toLowerCase();
|
|
65
|
+
const type = (relation.type || "related_to").trim().toLowerCase();
|
|
66
|
+
const target = (relation.target || "").trim().toLowerCase();
|
|
67
|
+
return `${source}|${type}|${target}`;
|
|
68
|
+
}
|
|
69
|
+
function relationBucketKey(relation) {
|
|
70
|
+
const source = (relation.source || "").trim().toLowerCase();
|
|
71
|
+
const type = (relation.type || "related_to").trim().toLowerCase();
|
|
72
|
+
return `${source}|${type}`;
|
|
73
|
+
}
|
|
74
|
+
function normalizeSummary(value) {
|
|
75
|
+
return String(value || "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
function buildStructureSignature(record) {
|
|
78
|
+
const entities = (record.entities || [])
|
|
79
|
+
.map(item => String(item || "").trim().toLowerCase())
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.sort();
|
|
82
|
+
const relations = (record.relations || [])
|
|
83
|
+
.map(item => {
|
|
84
|
+
const source = String(item.source || "").trim().toLowerCase();
|
|
85
|
+
const type = String(item.type || "").trim().toLowerCase();
|
|
86
|
+
const target = String(item.target || "").trim().toLowerCase();
|
|
87
|
+
const evidence = String(item.evidence_span || "").trim().toLowerCase();
|
|
88
|
+
const context = String(item.context_chunk || "").trim().toLowerCase();
|
|
89
|
+
return `${source}|${type}|${target}|${evidence}|${context}`;
|
|
90
|
+
})
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.sort();
|
|
93
|
+
return `${entities.join("||")}#${relations.join("||")}`;
|
|
94
|
+
}
|
|
95
|
+
function buildFallbackSummary(entities, relations) {
|
|
96
|
+
const entityText = (entities || []).map(item => String(item || "").trim()).filter(Boolean).join(", ");
|
|
97
|
+
const relationText = (relations || [])
|
|
98
|
+
.slice(0, 3)
|
|
99
|
+
.map(item => `${item.source} ${item.type} ${item.target}`)
|
|
100
|
+
.join("; ");
|
|
101
|
+
return `Graph memory update covering entities [${entityText || "n/a"}] with relations [${relationText || "n/a"}].`;
|
|
102
|
+
}
|
|
103
|
+
function toConflictSummary(record) {
|
|
104
|
+
return {
|
|
105
|
+
conflict_id: record.conflict_id,
|
|
106
|
+
status: record.status,
|
|
107
|
+
created_at: record.created_at,
|
|
108
|
+
updated_at: record.updated_at,
|
|
109
|
+
source_event_id: record.source_event_id,
|
|
110
|
+
session_id: record.session_id,
|
|
111
|
+
conflict_reason: record.conflict_reason,
|
|
112
|
+
conflict_types: record.conflict_types,
|
|
113
|
+
existing_relation_keys: record.existing_relation_keys,
|
|
114
|
+
candidate_relations: (record.candidate.relations || []).map(rel => ({
|
|
115
|
+
source: rel.source,
|
|
116
|
+
type: rel.type,
|
|
117
|
+
target: rel.target,
|
|
118
|
+
})),
|
|
119
|
+
resolution: record.resolution,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
51
122
|
function createGraphMemoryStore(options) {
|
|
52
123
|
const graphMemoryPath = path.join(options.memoryRoot, "graph", "memory.jsonl");
|
|
53
124
|
const mutationLogPath = path.join(options.memoryRoot, "graph", "mutation_log.jsonl");
|
|
125
|
+
const conflictQueuePath = path.join(options.memoryRoot, "graph", "conflict_queue.jsonl");
|
|
126
|
+
const supersededRelationPath = path.join(options.memoryRoot, "graph", "superseded_relations.jsonl");
|
|
54
127
|
const graphSchema = (0, ontology_1.loadGraphSchema)(options.projectRoot);
|
|
128
|
+
const wikiProjectionEnabled = options.wikiProjection?.enabled === true && options.wikiProjection?.mode !== "off";
|
|
129
|
+
const wikiProjectionMaxBatch = typeof options.wikiProjection?.maxBatch === "number" && Number.isFinite(options.wikiProjection.maxBatch)
|
|
130
|
+
? Math.max(1, Math.min(1000, Math.floor(options.wikiProjection.maxBatch)))
|
|
131
|
+
: 100;
|
|
132
|
+
function triggerWikiMaintenance(input) {
|
|
133
|
+
if (!wikiProjectionEnabled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
(0, wiki_queue_1.appendWikiRebuildEvent)({
|
|
138
|
+
memoryRoot: options.memoryRoot,
|
|
139
|
+
type: input.type,
|
|
140
|
+
source_event_id: input.sourceEventId,
|
|
141
|
+
conflict_id: input.conflictId,
|
|
142
|
+
entities: input.entities,
|
|
143
|
+
relation_types: input.relationTypes,
|
|
144
|
+
});
|
|
145
|
+
(0, wiki_logger_1.appendWikiLog)({
|
|
146
|
+
memoryRoot: options.memoryRoot,
|
|
147
|
+
type: input.type,
|
|
148
|
+
source_event_id: input.sourceEventId,
|
|
149
|
+
conflict_id: input.conflictId,
|
|
150
|
+
message: input.message,
|
|
151
|
+
});
|
|
152
|
+
const graphView = exportGraphView();
|
|
153
|
+
(0, wiki_maintainer_1.maintainWikiProjection)({
|
|
154
|
+
memoryRoot: options.memoryRoot,
|
|
155
|
+
graphView,
|
|
156
|
+
maxBatch: wikiProjectionMaxBatch,
|
|
157
|
+
logger: options.logger,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
options.logger.warn(`wiki_projection_maintenance_failed reason=${String(error)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function loadSupersededRelationEntries() {
|
|
165
|
+
if (!fs.existsSync(supersededRelationPath)) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const lines = fs.readFileSync(supersededRelationPath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
169
|
+
const entries = [];
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(line);
|
|
173
|
+
const relationKeyRaw = typeof parsed.relation_key === "string" ? parsed.relation_key.trim().toLowerCase() : "";
|
|
174
|
+
if (!relationKeyRaw) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
entries.push({
|
|
178
|
+
relation_key: relationKeyRaw,
|
|
179
|
+
superseded_at: typeof parsed.superseded_at === "string" ? parsed.superseded_at : "",
|
|
180
|
+
conflict_id: typeof parsed.conflict_id === "string" ? parsed.conflict_id : undefined,
|
|
181
|
+
note: typeof parsed.note === "string" ? parsed.note : undefined,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
options.logger.warn(`graph_superseded_parse_error line=${line.slice(0, 120)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return entries;
|
|
189
|
+
}
|
|
190
|
+
function loadSupersededRelationKeys() {
|
|
191
|
+
const keys = new Set();
|
|
192
|
+
for (const entry of loadSupersededRelationEntries()) {
|
|
193
|
+
if (entry.relation_key) {
|
|
194
|
+
keys.add(entry.relation_key);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return keys;
|
|
198
|
+
}
|
|
199
|
+
function appendSupersededRelationEntries(entries) {
|
|
200
|
+
if (entries.length === 0) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
ensureDirForFile(supersededRelationPath);
|
|
204
|
+
const lines = entries.map(item => JSON.stringify(item));
|
|
205
|
+
fs.appendFileSync(supersededRelationPath, `${lines.join("\n")}\n`, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
function filterRecordBySuperseded(record, supersededKeys) {
|
|
208
|
+
const relations = Array.isArray(record.relations)
|
|
209
|
+
? record.relations.filter(rel => !supersededKeys.has(relationKey(rel)))
|
|
210
|
+
: [];
|
|
211
|
+
if (relations.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
...record,
|
|
216
|
+
relations,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function loadAllRaw() {
|
|
220
|
+
if (!fs.existsSync(graphMemoryPath)) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const content = fs.readFileSync(graphMemoryPath, "utf-8");
|
|
224
|
+
const lines = content.split(/\r?\n/).filter(Boolean);
|
|
225
|
+
const records = [];
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(line);
|
|
229
|
+
if (parsed && parsed.id && (parsed.source_event_id || parsed.archive_event_id)) {
|
|
230
|
+
if (!parsed.source_event_id && parsed.archive_event_id) {
|
|
231
|
+
parsed.source_event_id = parsed.archive_event_id;
|
|
232
|
+
parsed.source_layer = "archive_event";
|
|
233
|
+
}
|
|
234
|
+
records.push(parsed);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
options.logger.warn(`graph_memory_parse_error line=${line.slice(0, 100)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return records;
|
|
242
|
+
}
|
|
243
|
+
function loadAllEffective() {
|
|
244
|
+
const supersededKeys = loadSupersededRelationKeys();
|
|
245
|
+
const raw = loadAllRaw();
|
|
246
|
+
const output = [];
|
|
247
|
+
for (const record of raw) {
|
|
248
|
+
const filtered = filterRecordBySuperseded(record, supersededKeys);
|
|
249
|
+
if (filtered) {
|
|
250
|
+
output.push(filtered);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return output;
|
|
254
|
+
}
|
|
255
|
+
function shouldRejectStaleSummary(record) {
|
|
256
|
+
const sameSourceRecords = loadAllEffective()
|
|
257
|
+
.filter(item => item.source_event_id === record.source_event_id)
|
|
258
|
+
.sort((a, b) => Date.parse(b.timestamp || "") - Date.parse(a.timestamp || ""));
|
|
259
|
+
if (sameSourceRecords.length === 0) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const latest = sameSourceRecords[0];
|
|
263
|
+
const previousSummary = normalizeSummary(latest.summary);
|
|
264
|
+
const nextSummary = normalizeSummary(record.summary);
|
|
265
|
+
if (!previousSummary || !nextSummary || previousSummary !== nextSummary) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const previousSignature = buildStructureSignature(latest);
|
|
269
|
+
const nextSignature = buildStructureSignature(record);
|
|
270
|
+
return previousSignature !== nextSignature;
|
|
271
|
+
}
|
|
272
|
+
function loadConflictQueue() {
|
|
273
|
+
if (!fs.existsSync(conflictQueuePath)) {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
const lines = fs.readFileSync(conflictQueuePath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
277
|
+
const records = [];
|
|
278
|
+
for (const line of lines) {
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(line);
|
|
281
|
+
if (parsed && typeof parsed.conflict_id === "string" && parsed.conflict_id.trim()) {
|
|
282
|
+
records.push(parsed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
options.logger.warn(`graph_conflict_parse_error line=${line.slice(0, 120)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return records;
|
|
290
|
+
}
|
|
291
|
+
function saveConflictQueue(records) {
|
|
292
|
+
ensureDirForFile(conflictQueuePath);
|
|
293
|
+
const lines = records.map(item => JSON.stringify(item));
|
|
294
|
+
fs.writeFileSync(conflictQueuePath, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, "utf-8");
|
|
295
|
+
}
|
|
296
|
+
function appendGraphRecord(args) {
|
|
297
|
+
const line = JSON.stringify(args.record);
|
|
298
|
+
const qualityCheck = (0, llm_output_validator_1.validateGraphJsonlLine)(line);
|
|
299
|
+
if (!qualityCheck.valid) {
|
|
300
|
+
throw new Error(`graph_record_quality_invalid:${qualityCheck.errors.join("|")}`);
|
|
301
|
+
}
|
|
302
|
+
if (qualityCheck.warnings.length > 0) {
|
|
303
|
+
options.logger.warn(`graph_quality_warning source_event_id=${args.record.source_event_id} warnings=${qualityCheck.warnings.join("|")}`);
|
|
304
|
+
}
|
|
305
|
+
const mutationEntry = {
|
|
306
|
+
op: args.conflictId ? "insert_graph_conflict_resolved" : "insert_graph",
|
|
307
|
+
id: args.record.id,
|
|
308
|
+
source_event_id: args.record.source_event_id,
|
|
309
|
+
source_layer: args.record.source_layer,
|
|
310
|
+
archive_event_id: args.record.archive_event_id,
|
|
311
|
+
timestamp: args.record.timestamp,
|
|
312
|
+
session_id: args.record.session_id,
|
|
313
|
+
gate_source: args.record.gate_source,
|
|
314
|
+
entity_count: args.record.entities.length,
|
|
315
|
+
relation_count: args.record.relations.length,
|
|
316
|
+
conflict_id: args.conflictId,
|
|
317
|
+
note: args.note,
|
|
318
|
+
};
|
|
319
|
+
ensureDirForFile(graphMemoryPath);
|
|
320
|
+
ensureDirForFile(mutationLogPath);
|
|
321
|
+
fs.appendFileSync(graphMemoryPath, `${line}\n`, "utf-8");
|
|
322
|
+
fs.appendFileSync(mutationLogPath, `${JSON.stringify(mutationEntry)}\n`, "utf-8");
|
|
323
|
+
}
|
|
324
|
+
function appendSupersedeMutationLogs(entries, conflictId, sourceEventId) {
|
|
325
|
+
if (entries.length === 0) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
ensureDirForFile(mutationLogPath);
|
|
329
|
+
const nowIso = new Date().toISOString();
|
|
330
|
+
const lines = entries.map(item => JSON.stringify({
|
|
331
|
+
op: "supersede_relation",
|
|
332
|
+
id: `sup_${crypto.createHash("sha1").update(`${item.relation_key}|${item.superseded_at}`).digest("hex").slice(0, 12)}`,
|
|
333
|
+
source_event_id: sourceEventId || "conflict_resolution",
|
|
334
|
+
source_layer: "active_only",
|
|
335
|
+
timestamp: nowIso,
|
|
336
|
+
session_id: "system",
|
|
337
|
+
gate_source: "manual",
|
|
338
|
+
entity_count: 0,
|
|
339
|
+
relation_count: 0,
|
|
340
|
+
relation_key: item.relation_key,
|
|
341
|
+
conflict_id: conflictId,
|
|
342
|
+
note: item.note,
|
|
343
|
+
}));
|
|
344
|
+
fs.appendFileSync(mutationLogPath, `${lines.join("\n")}\n`, "utf-8");
|
|
345
|
+
}
|
|
346
|
+
function detectConflicts(candidate) {
|
|
347
|
+
const existing = loadAllEffective();
|
|
348
|
+
const candidateSingleValued = (candidate.relations || []).filter(rel => SINGLE_VALUE_RELATION_TYPES.has((rel.type || "").trim().toLowerCase()));
|
|
349
|
+
if (candidateSingleValued.length === 0) {
|
|
350
|
+
return {
|
|
351
|
+
hasConflict: false,
|
|
352
|
+
reason: "",
|
|
353
|
+
conflictTypes: [],
|
|
354
|
+
existingRelations: [],
|
|
355
|
+
existingRelationKeys: [],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const existingByBucket = new Map();
|
|
359
|
+
for (const record of existing) {
|
|
360
|
+
for (const rel of record.relations || []) {
|
|
361
|
+
const type = (rel.type || "").trim().toLowerCase();
|
|
362
|
+
if (!SINGLE_VALUE_RELATION_TYPES.has(type)) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const key = relationKey(rel);
|
|
366
|
+
const bucket = relationBucketKey(rel);
|
|
367
|
+
if (!existingByBucket.has(bucket)) {
|
|
368
|
+
existingByBucket.set(bucket, []);
|
|
369
|
+
}
|
|
370
|
+
existingByBucket.get(bucket)?.push({ key, relation: rel });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const conflictTypes = new Set();
|
|
374
|
+
const existingRelations = [];
|
|
375
|
+
const existingRelationKeySet = new Set();
|
|
376
|
+
for (const rel of candidateSingleValued) {
|
|
377
|
+
const candidateKey = relationKey(rel);
|
|
378
|
+
const bucket = relationBucketKey(rel);
|
|
379
|
+
const sameBucket = existingByBucket.get(bucket) || [];
|
|
380
|
+
for (const existingItem of sameBucket) {
|
|
381
|
+
if (existingItem.key === candidateKey) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
existingRelationKeySet.add(existingItem.key);
|
|
385
|
+
existingRelations.push(existingItem);
|
|
386
|
+
conflictTypes.add((rel.type || "").trim().toLowerCase());
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const existingRelationKeys = [...existingRelationKeySet].sort();
|
|
390
|
+
if (existingRelationKeys.length === 0) {
|
|
391
|
+
return {
|
|
392
|
+
hasConflict: false,
|
|
393
|
+
reason: "",
|
|
394
|
+
conflictTypes: [],
|
|
395
|
+
existingRelations: [],
|
|
396
|
+
existingRelationKeys: [],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
hasConflict: true,
|
|
401
|
+
reason: `single_value_relation_conflict:${[...conflictTypes].sort().join(",")}`,
|
|
402
|
+
conflictTypes: [...conflictTypes].sort(),
|
|
403
|
+
existingRelations,
|
|
404
|
+
existingRelationKeys,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function buildConflictFingerprint(input) {
|
|
408
|
+
const payload = JSON.stringify({
|
|
409
|
+
source_event_id: input.sourceEventId,
|
|
410
|
+
conflict_types: [...input.conflictTypes].sort(),
|
|
411
|
+
existing_relation_keys: [...input.existingRelationKeys].sort(),
|
|
412
|
+
candidate_relation_keys: input.candidateRelations.map(relationKey).sort(),
|
|
413
|
+
});
|
|
414
|
+
return crypto.createHash("sha1").update(payload).digest("hex");
|
|
415
|
+
}
|
|
55
416
|
options.logger.info(`Graph memory store initialized at ${graphMemoryPath}`);
|
|
56
417
|
async function append(input) {
|
|
57
418
|
const validation = (0, ontology_1.validateGraphPayload)({
|
|
@@ -60,6 +421,8 @@ function createGraphMemoryStore(options) {
|
|
|
60
421
|
archiveEventId: input.archiveEventId,
|
|
61
422
|
sessionId: input.sessionId,
|
|
62
423
|
sourceFile: input.sourceFile,
|
|
424
|
+
source_text_nav: input.source_text_nav,
|
|
425
|
+
summary: input.summary,
|
|
63
426
|
eventType: input.eventType,
|
|
64
427
|
entities: input.entities,
|
|
65
428
|
entity_types: input.entity_types,
|
|
@@ -80,66 +443,98 @@ function createGraphMemoryStore(options) {
|
|
|
80
443
|
const record = validation.normalized;
|
|
81
444
|
record.id = generateGraphId();
|
|
82
445
|
record.timestamp = new Date().toISOString();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
446
|
+
if (shouldRejectStaleSummary(record)) {
|
|
447
|
+
options.logger.info(`graph_skip_reason=stale_summary_after_update source_event_id=${record.source_event_id}`);
|
|
448
|
+
return { success: false, reason: "stale_summary_after_update" };
|
|
449
|
+
}
|
|
450
|
+
const conflictDetection = detectConflicts(record);
|
|
451
|
+
if (conflictDetection.hasConflict) {
|
|
452
|
+
const queue = loadConflictQueue();
|
|
453
|
+
const fingerprint = buildConflictFingerprint({
|
|
454
|
+
sourceEventId: record.source_event_id,
|
|
455
|
+
conflictTypes: conflictDetection.conflictTypes,
|
|
456
|
+
existingRelationKeys: conflictDetection.existingRelationKeys,
|
|
457
|
+
candidateRelations: record.relations,
|
|
458
|
+
});
|
|
459
|
+
const existingPending = queue.find(item => item.status === "pending" && item.fingerprint === fingerprint);
|
|
460
|
+
if (existingPending) {
|
|
461
|
+
options.logger.info(`graph_conflict_pending_reuse conflict_id=${existingPending.conflict_id} source_event_id=${record.source_event_id}`);
|
|
462
|
+
return { success: false, reason: `graph_conflict_pending:${existingPending.conflict_id}` };
|
|
463
|
+
}
|
|
464
|
+
const now = new Date().toISOString();
|
|
465
|
+
const conflictRecord = {
|
|
466
|
+
conflict_id: generateConflictId(),
|
|
467
|
+
status: "pending",
|
|
468
|
+
created_at: now,
|
|
469
|
+
updated_at: now,
|
|
470
|
+
source_event_id: record.source_event_id,
|
|
471
|
+
source_layer: record.source_layer,
|
|
472
|
+
session_id: record.session_id,
|
|
473
|
+
source_file: record.source_file,
|
|
474
|
+
conflict_reason: conflictDetection.reason,
|
|
475
|
+
conflict_types: conflictDetection.conflictTypes,
|
|
476
|
+
existing_relation_keys: conflictDetection.existingRelationKeys,
|
|
477
|
+
existing_relations: conflictDetection.existingRelations,
|
|
478
|
+
candidate: {
|
|
479
|
+
summary: record.summary,
|
|
480
|
+
source_text_nav: record.source_text_nav,
|
|
481
|
+
entities: record.entities,
|
|
482
|
+
entity_types: record.entity_types,
|
|
483
|
+
relations: record.relations,
|
|
484
|
+
event_type: record.event_type,
|
|
485
|
+
gate_source: record.gate_source,
|
|
486
|
+
confidence: record.confidence,
|
|
487
|
+
source_text: input.sourceText,
|
|
488
|
+
},
|
|
489
|
+
fingerprint,
|
|
490
|
+
};
|
|
491
|
+
queue.push(conflictRecord);
|
|
492
|
+
saveConflictQueue(queue);
|
|
493
|
+
options.logger.info(`graph_conflict_pending id=${conflictRecord.conflict_id} source_event_id=${record.source_event_id} types=${conflictRecord.conflict_types.join(",")}`);
|
|
494
|
+
triggerWikiMaintenance({
|
|
495
|
+
type: "conflict_pending",
|
|
496
|
+
message: "Conflict pending and awaiting confirmation",
|
|
497
|
+
sourceEventId: record.source_event_id,
|
|
498
|
+
conflictId: conflictRecord.conflict_id,
|
|
499
|
+
entities: record.entities,
|
|
500
|
+
relationTypes: (record.relations || []).map(item => item.type),
|
|
501
|
+
});
|
|
502
|
+
return { success: false, reason: `graph_conflict_pending:${conflictRecord.conflict_id}` };
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
appendGraphRecord({ record });
|
|
506
|
+
options.logger.info(`graph_write id=${record.id} source_event_id=${record.source_event_id} source_layer=${record.source_layer} entities=${record.entities.length} relations=${record.relations.length} gate_source=${record.gate_source}`);
|
|
507
|
+
triggerWikiMaintenance({
|
|
508
|
+
type: "graph_append",
|
|
509
|
+
message: "Graph relation appended",
|
|
510
|
+
sourceEventId: record.source_event_id,
|
|
511
|
+
entities: record.entities,
|
|
512
|
+
relationTypes: (record.relations || []).map(item => item.type),
|
|
513
|
+
});
|
|
514
|
+
return { success: true, record };
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const reason = String(error);
|
|
87
518
|
options.logger.warn(`graph_skip_reason=${reason} source_event_id=${input.sourceEventId}`);
|
|
88
519
|
return { success: false, reason };
|
|
89
520
|
}
|
|
90
|
-
if (qualityCheck.warnings.length > 0) {
|
|
91
|
-
options.logger.warn(`graph_quality_warning source_event_id=${input.sourceEventId} warnings=${qualityCheck.warnings.join("|")}`);
|
|
92
|
-
}
|
|
93
|
-
const mutationEntry = {
|
|
94
|
-
op: "insert_graph",
|
|
95
|
-
id: record.id,
|
|
96
|
-
source_event_id: record.source_event_id,
|
|
97
|
-
source_layer: record.source_layer,
|
|
98
|
-
archive_event_id: record.archive_event_id,
|
|
99
|
-
timestamp: record.timestamp,
|
|
100
|
-
session_id: record.session_id,
|
|
101
|
-
gate_source: record.gate_source,
|
|
102
|
-
entity_count: record.entities.length,
|
|
103
|
-
relation_count: record.relations.length,
|
|
104
|
-
};
|
|
105
|
-
const mutationLine = JSON.stringify(mutationEntry);
|
|
106
|
-
ensureDirForFile(graphMemoryPath);
|
|
107
|
-
ensureDirForFile(mutationLogPath);
|
|
108
|
-
fs.appendFileSync(graphMemoryPath, `${line}\n`, "utf-8");
|
|
109
|
-
fs.appendFileSync(mutationLogPath, `${mutationLine}\n`, "utf-8");
|
|
110
|
-
options.logger.info(`graph_write id=${record.id} source_event_id=${record.source_event_id} source_layer=${record.source_layer} entities=${record.entities.length} relations=${record.relations.length} gate_source=${record.gate_source}`);
|
|
111
|
-
return { success: true, record };
|
|
112
521
|
}
|
|
113
522
|
function loadAll() {
|
|
114
|
-
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
|
-
const content = fs.readFileSync(graphMemoryPath, "utf-8");
|
|
118
|
-
const lines = content.split(/\r?\n/).filter(Boolean);
|
|
119
|
-
const records = [];
|
|
120
|
-
for (const line of lines) {
|
|
121
|
-
try {
|
|
122
|
-
const parsed = JSON.parse(line);
|
|
123
|
-
if (parsed && parsed.id && (parsed.source_event_id || parsed.archive_event_id)) {
|
|
124
|
-
if (!parsed.source_event_id && parsed.archive_event_id) {
|
|
125
|
-
parsed.source_event_id = parsed.archive_event_id;
|
|
126
|
-
parsed.source_layer = "archive_event";
|
|
127
|
-
}
|
|
128
|
-
records.push(parsed);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
options.logger.warn(`graph_memory_parse_error line=${line.slice(0, 100)}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return records;
|
|
523
|
+
return loadAllEffective();
|
|
136
524
|
}
|
|
137
525
|
function loadByArchiveEventId(archiveEventId) {
|
|
138
526
|
return loadAll().filter(record => record.archive_event_id === archiveEventId || record.source_event_id === archiveEventId);
|
|
139
527
|
}
|
|
140
528
|
function loadByEntity(entityName) {
|
|
141
|
-
const normalized = entityName.trim()
|
|
142
|
-
|
|
529
|
+
const normalized = entityName.trim();
|
|
530
|
+
const queryKeySet = new Set((0, ontology_1.getEntityMatchKeys)(normalized, graphSchema));
|
|
531
|
+
if (queryKeySet.size === 0) {
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
return loadAll().filter(record => record.entities.some(entity => {
|
|
535
|
+
const entityKeys = (0, ontology_1.getEntityMatchKeys)(entity, graphSchema);
|
|
536
|
+
return entityKeys.some(key => queryKeySet.has(key));
|
|
537
|
+
}));
|
|
143
538
|
}
|
|
144
539
|
function getStats() {
|
|
145
540
|
const all = loadAll();
|
|
@@ -157,12 +552,290 @@ function createGraphMemoryStore(options) {
|
|
|
157
552
|
totalRelations,
|
|
158
553
|
};
|
|
159
554
|
}
|
|
555
|
+
function parseRelationKey(key) {
|
|
556
|
+
const parts = key.split("|");
|
|
557
|
+
return {
|
|
558
|
+
source: parts[0] || "",
|
|
559
|
+
type: parts[1] || "related_to",
|
|
560
|
+
target: parts.slice(2).join("|") || "",
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function exportGraphView() {
|
|
564
|
+
const rawRecords = loadAllRaw();
|
|
565
|
+
const activeRecords = loadAllEffective();
|
|
566
|
+
const conflictQueue = loadConflictQueue();
|
|
567
|
+
const supersededEntries = loadSupersededRelationEntries();
|
|
568
|
+
const nodes = new Map();
|
|
569
|
+
const edges = [];
|
|
570
|
+
const dedupe = new Set();
|
|
571
|
+
let latestTimestampMs = 0;
|
|
572
|
+
function updateLatest(iso) {
|
|
573
|
+
const ms = Date.parse(iso || "");
|
|
574
|
+
if (Number.isFinite(ms) && ms > latestTimestampMs) {
|
|
575
|
+
latestTimestampMs = ms;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function addNode(id) {
|
|
579
|
+
const trimmed = id.trim();
|
|
580
|
+
if (!trimmed) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const key = trimmed.toLowerCase();
|
|
584
|
+
if (!nodes.has(key)) {
|
|
585
|
+
nodes.set(key, { id: trimmed, type: "entity" });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function addEdge(input) {
|
|
589
|
+
const source = input.source.trim();
|
|
590
|
+
const target = input.target.trim();
|
|
591
|
+
const type = (input.type || "related_to").trim().toLowerCase();
|
|
592
|
+
if (!source || !target || !type) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const relation_key = `${source.toLowerCase()}|${type}|${target.toLowerCase()}`;
|
|
596
|
+
const dedupeKey = `${relation_key}|${input.status}|${(input.conflict_id || "").trim().toLowerCase()}`;
|
|
597
|
+
if (dedupe.has(dedupeKey)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
dedupe.add(dedupeKey);
|
|
601
|
+
addNode(source);
|
|
602
|
+
addNode(target);
|
|
603
|
+
edges.push({
|
|
604
|
+
source,
|
|
605
|
+
target,
|
|
606
|
+
type,
|
|
607
|
+
status: input.status,
|
|
608
|
+
relation_key,
|
|
609
|
+
source_event_id: input.source_event_id,
|
|
610
|
+
evidence_span: input.evidence_span,
|
|
611
|
+
confidence: input.confidence,
|
|
612
|
+
conflict_id: input.conflict_id,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
const relationMetaByKey = new Map();
|
|
616
|
+
for (const record of rawRecords) {
|
|
617
|
+
updateLatest(record.timestamp);
|
|
618
|
+
for (const rel of record.relations || []) {
|
|
619
|
+
const key = relationKey(rel);
|
|
620
|
+
const prev = relationMetaByKey.get(key);
|
|
621
|
+
if (!prev || Date.parse(record.timestamp || "") >= Date.parse(prev.timestamp || "")) {
|
|
622
|
+
relationMetaByKey.set(key, {
|
|
623
|
+
source_event_id: record.source_event_id,
|
|
624
|
+
evidence_span: rel.evidence_span,
|
|
625
|
+
confidence: rel.confidence,
|
|
626
|
+
timestamp: record.timestamp,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
for (const record of activeRecords) {
|
|
632
|
+
updateLatest(record.timestamp);
|
|
633
|
+
for (const rel of record.relations || []) {
|
|
634
|
+
addEdge({
|
|
635
|
+
source: rel.source,
|
|
636
|
+
target: rel.target,
|
|
637
|
+
type: rel.type,
|
|
638
|
+
status: "active",
|
|
639
|
+
source_event_id: record.source_event_id,
|
|
640
|
+
evidence_span: rel.evidence_span,
|
|
641
|
+
confidence: rel.confidence,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (const entry of supersededEntries) {
|
|
646
|
+
updateLatest(entry.superseded_at);
|
|
647
|
+
const parsed = parseRelationKey(entry.relation_key);
|
|
648
|
+
const meta = relationMetaByKey.get(entry.relation_key);
|
|
649
|
+
addEdge({
|
|
650
|
+
source: parsed.source,
|
|
651
|
+
target: parsed.target,
|
|
652
|
+
type: parsed.type,
|
|
653
|
+
status: "superseded",
|
|
654
|
+
source_event_id: meta?.source_event_id,
|
|
655
|
+
evidence_span: meta?.evidence_span,
|
|
656
|
+
confidence: meta?.confidence,
|
|
657
|
+
conflict_id: entry.conflict_id,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
for (const item of conflictQueue) {
|
|
661
|
+
updateLatest(item.updated_at);
|
|
662
|
+
if (item.status !== "pending" && item.status !== "rejected") {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const status = item.status === "pending" ? "pending_conflict" : "rejected";
|
|
666
|
+
for (const rel of item.candidate.relations || []) {
|
|
667
|
+
addEdge({
|
|
668
|
+
source: rel.source,
|
|
669
|
+
target: rel.target,
|
|
670
|
+
type: rel.type,
|
|
671
|
+
status,
|
|
672
|
+
source_event_id: item.source_event_id,
|
|
673
|
+
evidence_span: rel.evidence_span,
|
|
674
|
+
confidence: rel.confidence,
|
|
675
|
+
conflict_id: item.conflict_id,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const status_counts = {
|
|
680
|
+
active: 0,
|
|
681
|
+
pending_conflict: 0,
|
|
682
|
+
superseded: 0,
|
|
683
|
+
rejected: 0,
|
|
684
|
+
};
|
|
685
|
+
for (const edge of edges) {
|
|
686
|
+
status_counts[edge.status] += 1;
|
|
687
|
+
}
|
|
688
|
+
edges.sort((a, b) => {
|
|
689
|
+
const left = `${a.status}|${a.source.toLowerCase()}|${a.type}|${a.target.toLowerCase()}|${a.conflict_id || ""}`;
|
|
690
|
+
const right = `${b.status}|${b.source.toLowerCase()}|${b.type}|${b.target.toLowerCase()}|${b.conflict_id || ""}`;
|
|
691
|
+
return left.localeCompare(right);
|
|
692
|
+
});
|
|
693
|
+
const nodeList = [...nodes.values()].sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
|
|
694
|
+
return {
|
|
695
|
+
nodes: nodeList,
|
|
696
|
+
edges,
|
|
697
|
+
status_counts,
|
|
698
|
+
updated_at: latestTimestampMs > 0 ? new Date(latestTimestampMs).toISOString() : new Date().toISOString(),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
function listConflicts(args) {
|
|
702
|
+
const status = args?.status || "pending";
|
|
703
|
+
const limit = typeof args?.limit === "number" && Number.isFinite(args.limit) && args.limit > 0
|
|
704
|
+
? Math.min(500, Math.floor(args.limit))
|
|
705
|
+
: 50;
|
|
706
|
+
const queue = loadConflictQueue();
|
|
707
|
+
const filtered = queue
|
|
708
|
+
.filter(item => status === "all" ? true : item.status === status)
|
|
709
|
+
.sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
|
|
710
|
+
return filtered.slice(0, limit).map(toConflictSummary);
|
|
711
|
+
}
|
|
712
|
+
function getConflictStats() {
|
|
713
|
+
const queue = loadConflictQueue();
|
|
714
|
+
let pending = 0;
|
|
715
|
+
let accepted = 0;
|
|
716
|
+
let rejected = 0;
|
|
717
|
+
for (const item of queue) {
|
|
718
|
+
if (item.status === "pending")
|
|
719
|
+
pending += 1;
|
|
720
|
+
if (item.status === "accepted")
|
|
721
|
+
accepted += 1;
|
|
722
|
+
if (item.status === "rejected")
|
|
723
|
+
rejected += 1;
|
|
724
|
+
}
|
|
725
|
+
return { pending, accepted, rejected };
|
|
726
|
+
}
|
|
727
|
+
async function resolveConflict(args) {
|
|
728
|
+
const conflictId = (args.conflictId || "").trim();
|
|
729
|
+
if (!conflictId) {
|
|
730
|
+
return { success: false, reason: "conflict_id_empty" };
|
|
731
|
+
}
|
|
732
|
+
const queue = loadConflictQueue();
|
|
733
|
+
const index = queue.findIndex(item => item.conflict_id === conflictId);
|
|
734
|
+
if (index < 0) {
|
|
735
|
+
return { success: false, reason: "conflict_not_found" };
|
|
736
|
+
}
|
|
737
|
+
const current = queue[index];
|
|
738
|
+
if (current.status !== "pending") {
|
|
739
|
+
return { success: false, reason: `conflict_already_${current.status}` };
|
|
740
|
+
}
|
|
741
|
+
const now = new Date().toISOString();
|
|
742
|
+
if (args.action === "reject") {
|
|
743
|
+
current.status = "rejected";
|
|
744
|
+
current.updated_at = now;
|
|
745
|
+
current.resolution = {
|
|
746
|
+
action: "reject",
|
|
747
|
+
note: args.note,
|
|
748
|
+
resolved_at: now,
|
|
749
|
+
};
|
|
750
|
+
queue[index] = current;
|
|
751
|
+
saveConflictQueue(queue);
|
|
752
|
+
options.logger.info(`graph_conflict_rejected id=${conflictId}`);
|
|
753
|
+
triggerWikiMaintenance({
|
|
754
|
+
type: "conflict_rejected",
|
|
755
|
+
message: "Conflict rejected; canonical graph unchanged",
|
|
756
|
+
sourceEventId: current.source_event_id,
|
|
757
|
+
conflictId,
|
|
758
|
+
entities: current.candidate.entities,
|
|
759
|
+
relationTypes: (current.candidate.relations || []).map(item => item.type),
|
|
760
|
+
});
|
|
761
|
+
return { success: true };
|
|
762
|
+
}
|
|
763
|
+
const supersedeEntries = current.existing_relation_keys.map(key => ({
|
|
764
|
+
relation_key: key,
|
|
765
|
+
superseded_at: now,
|
|
766
|
+
conflict_id: conflictId,
|
|
767
|
+
note: args.note || "conflict_accept_replace",
|
|
768
|
+
}));
|
|
769
|
+
appendSupersededRelationEntries(supersedeEntries);
|
|
770
|
+
appendSupersedeMutationLogs(supersedeEntries, conflictId, current.source_event_id);
|
|
771
|
+
const record = {
|
|
772
|
+
id: generateGraphId(),
|
|
773
|
+
summary: (current.candidate.summary || "").trim() || buildFallbackSummary(current.candidate.entities, current.candidate.relations),
|
|
774
|
+
source_text_nav: current.candidate.source_text_nav || {
|
|
775
|
+
layer: current.source_layer,
|
|
776
|
+
session_id: current.session_id,
|
|
777
|
+
source_file: current.source_file || "unknown",
|
|
778
|
+
source_memory_id: current.source_event_id,
|
|
779
|
+
source_event_id: current.source_event_id,
|
|
780
|
+
},
|
|
781
|
+
source_event_id: current.source_event_id,
|
|
782
|
+
source_layer: current.source_layer,
|
|
783
|
+
archive_event_id: current.source_layer === "archive_event" ? current.source_event_id : undefined,
|
|
784
|
+
session_id: current.session_id,
|
|
785
|
+
source_file: current.source_file,
|
|
786
|
+
timestamp: now,
|
|
787
|
+
entities: current.candidate.entities,
|
|
788
|
+
entity_types: current.candidate.entity_types,
|
|
789
|
+
relations: current.candidate.relations,
|
|
790
|
+
gate_source: current.candidate.gate_source,
|
|
791
|
+
event_type: current.candidate.event_type,
|
|
792
|
+
schema_version: "1.0.0",
|
|
793
|
+
confidence: current.candidate.confidence,
|
|
794
|
+
};
|
|
795
|
+
if (shouldRejectStaleSummary(record)) {
|
|
796
|
+
return { success: false, reason: "stale_summary_after_update" };
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
appendGraphRecord({
|
|
800
|
+
record,
|
|
801
|
+
conflictId,
|
|
802
|
+
note: args.note,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
return { success: false, reason: String(error) };
|
|
807
|
+
}
|
|
808
|
+
current.status = "accepted";
|
|
809
|
+
current.updated_at = now;
|
|
810
|
+
current.resolution = {
|
|
811
|
+
action: "accept",
|
|
812
|
+
note: args.note,
|
|
813
|
+
resolved_at: now,
|
|
814
|
+
applied_record_id: record.id,
|
|
815
|
+
};
|
|
816
|
+
queue[index] = current;
|
|
817
|
+
saveConflictQueue(queue);
|
|
818
|
+
options.logger.info(`graph_conflict_accepted id=${conflictId} record_id=${record.id}`);
|
|
819
|
+
triggerWikiMaintenance({
|
|
820
|
+
type: "conflict_accepted",
|
|
821
|
+
message: "Conflict accepted; canonical graph updated",
|
|
822
|
+
sourceEventId: current.source_event_id,
|
|
823
|
+
conflictId,
|
|
824
|
+
entities: current.candidate.entities,
|
|
825
|
+
relationTypes: (current.candidate.relations || []).map(item => item.type),
|
|
826
|
+
});
|
|
827
|
+
return { success: true, appliedRecordId: record.id };
|
|
828
|
+
}
|
|
160
829
|
return {
|
|
161
830
|
append,
|
|
162
831
|
loadAll,
|
|
163
832
|
loadByArchiveEventId,
|
|
164
833
|
loadByEntity,
|
|
165
834
|
getStats,
|
|
835
|
+
exportGraphView,
|
|
836
|
+
listConflicts,
|
|
837
|
+
resolveConflict,
|
|
838
|
+
getConflictStats,
|
|
166
839
|
};
|
|
167
840
|
}
|
|
168
841
|
//# sourceMappingURL=graph_memory_store.js.map
|