chore: fork with local patches for Syslog Solution

Patches applied:
- enforceRelayTTL — auto-deletes expired relay nodes (>7d)
- updateNode — tenant enforcement (block updates on NULL-namespace nodes)
- buildCanonicalMetadata — reject nested objects, sanitize values
- createNode — source-based and title-based deduplication
- getContext — NPE-safe count queries, stale node detection
- repair_log integration on updateNode
- bridge-validation.js — auto-fill defaults for tenant/namespace

Origin: github.com/bradwmorris/ra-h_os@70e95e7
This commit is contained in:
2026-06-01 09:37:19 -04:00
parent 70e95e7d29
commit 88228a6104
5 changed files with 417 additions and 86 deletions
@@ -21,7 +21,10 @@ function getEdges(filters = {}) {
const rows = query(sql, params);
return rows.map(formatEdgeRow);
return rows.map(row => ({
...row,
context: parseContext(row.context)
}));
}
/**
@@ -31,7 +34,11 @@ function getEdgeById(id) {
const rows = query('SELECT * FROM edges WHERE id = ?', [id]);
if (rows.length === 0) return null;
return formatEdgeRow(rows[0]);
const row = rows[0];
return {
...row,
context: parseContext(row.context)
};
}
/**
@@ -52,7 +59,21 @@ function createEdge(edgeData) {
throw new Error('Edge explanation is required');
}
const cleanExplanation = explanation.trim();
// P1 — Dedup check before edge creation
const existing = db.prepare(
`SELECT id FROM edges
WHERE from_node_id = ?
AND to_node_id = ?
AND (explanation = ? OR json_extract(context, '$.explanation') = ?)`
).get(from_node_id, to_node_id, explanation.trim(), explanation.trim());
if (existing) {
return {
id: existing.id,
status: 'existing',
warning: 'duplicate_edge_skipped'
};
}
// Simple context without AI inference
// The main app can re-infer types when it loads
@@ -60,13 +81,13 @@ function createEdge(edgeData) {
type: 'related_to',
confidence: 0.5,
inferred_at: now,
explanation: cleanExplanation,
explanation: explanation.trim(),
created_via: 'mcp'
};
const stmt = db.prepare(`
INSERT INTO edges (from_node_id, to_node_id, context, source, created_at, explanation)
VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO edges (from_node_id, to_node_id, context, source, created_at)
VALUES (?, ?, ?, ?, ?)
`);
const result = runWithBusyRetry(() => stmt.run(
@@ -74,13 +95,18 @@ function createEdge(edgeData) {
to_node_id,
JSON.stringify(context),
source,
now,
cleanExplanation
now
),
'createEdge'
);
// P0 — After writing edge to DB, sync explanation column
const edgeId = Number(result.lastInsertRowid);
if (context.explanation) {
const syncStmt = db.prepare('UPDATE edges SET explanation = ? WHERE id = ?');
runWithBusyRetry(() => syncStmt.run(context.explanation, edgeId), 'createEdge_explanation_sync');
}
return getEdgeById(edgeId);
}
@@ -96,36 +122,29 @@ function updateEdge(id, updates) {
throw new Error(`Edge with ID ${id} not found. Use rah_query_edges to find edges by node ID.`);
}
const cleanExplanation = typeof explanation === 'string' ? explanation.trim() : '';
// If explanation changed, update both the normal field and the compatibility copy.
if (cleanExplanation) {
// If explanation changed, update context
if (explanation && explanation.trim()) {
const now = new Date().toISOString();
const newContext = {
...existing.context,
explanation: cleanExplanation,
explanation: explanation.trim(),
inferred_at: now,
created_via: 'mcp'
};
const stmt = db.prepare('UPDATE edges SET context = ?, explanation = ? WHERE id = ?');
runWithBusyRetry(() => stmt.run(JSON.stringify(newContext), cleanExplanation, id), 'updateEdge');
const stmt = db.prepare('UPDATE edges SET context = ? WHERE id = ?');
runWithBusyRetry(() => stmt.run(JSON.stringify(newContext), id), 'updateEdge');
// P0 — After updating edge, sync explanation column
const syncStmt = db.prepare('UPDATE edges SET explanation = ? WHERE id = ?');
runWithBusyRetry(() => syncStmt.run(explanation.trim(), id), 'updateEdge_explanation_sync');
} else if (contextUpdates) {
const contextExplanation = typeof contextUpdates.explanation === 'string'
? contextUpdates.explanation.trim()
: '';
const newContext = {
...existing.context,
...contextUpdates,
...(contextExplanation ? { explanation: contextExplanation } : {})
...contextUpdates
};
if (contextExplanation) {
const stmt = db.prepare('UPDATE edges SET context = ?, explanation = ? WHERE id = ?');
runWithBusyRetry(() => stmt.run(JSON.stringify(newContext), contextExplanation, id), 'updateEdge');
} else {
const stmt = db.prepare('UPDATE edges SET context = ? WHERE id = ?');
runWithBusyRetry(() => stmt.run(JSON.stringify(newContext), id), 'updateEdge');
}
const stmt = db.prepare('UPDATE edges SET context = ? WHERE id = ?');
runWithBusyRetry(() => stmt.run(JSON.stringify(newContext), id), 'updateEdge');
}
return getEdgeById(id);
@@ -211,15 +230,6 @@ function getEdgeCount() {
return Number(rows[0].count);
}
function formatEdgeRow(row) {
const context = parseContext(row.context);
return {
...row,
context,
explanation: row.explanation || context?.explanation || null
};
}
/**
* Parse context JSON safely.
*/