import { b as getLogger } from './paths-2M_UxD_H.js';
import { e as getBrainDb, d as getBrainNativeDb, i as isBrainVecLoaded } from './sqlite-Dsbe0wec.js';
import { t as typedAll, a as typedGet } from './typed-query-l1D_QElt.js';

//#region ../core/dist/memory/temporal-supersession.js
/**
* Temporal Supersession — audit-trail-preserving memory supersession for CLEO BRAIN.
*
* When new information contradicts or replaces old memory, the old entry is
* marked as SUPERSEDED (via `invalid_at`) and a directed `supersedes` graph
* edge is created from the new entry to the old one. Nothing is deleted —
* the full chain is preserved for temporal reasoning.
*
* Key design properties:
*   - All writes are BEST-EFFORT where indicated — failures never block callers.
*   - Supersession only operates on currently-valid entries (invalid_at IS NULL).
*   - Embedding similarity is used when sqlite-vec is available; keyword overlap
*     is used as a fallback.
*   - A single node ID convention is maintained:  '<type>:<entryId>'.
*
* Supported tables: brain_decisions, brain_patterns, brain_learnings, brain_observations.
*
* T739: detectSupersession now uses sqlite-vec ANN query as the primary similarity
*   path when embeddings are available (isEmbeddingAvailable() + isBrainVecLoaded()).
*   Falls back to Jaccard keyword overlap when vectors are absent. The final
*   candidate score is the max of the embedding cosine similarity and the keyword
*   Jaccard ratio, so the most informative signal wins.
*
* @epic T523
* @task T739
*/
var log = getLogger("temporal-supersession");
/** Minimum keyword overlap ratio to consider two entries related. */
var KEYWORD_OVERLAP_THRESHOLD = .8;
/** Minimum shared keyword count before similarity ratio is computed. */
var MIN_SHARED_KEYWORDS = 3;
/** Stop words excluded from keyword extraction. */
var STOP_WORDS = new Set([
	"the",
	"a",
	"an",
	"is",
	"are",
	"was",
	"were",
	"be",
	"been",
	"being",
	"have",
	"has",
	"had",
	"do",
	"does",
	"did",
	"will",
	"would",
	"could",
	"should",
	"may",
	"might",
	"shall",
	"can",
	"to",
	"of",
	"in",
	"for",
	"on",
	"with",
	"at",
	"by",
	"from",
	"as",
	"into",
	"through",
	"and",
	"but",
	"or",
	"nor",
	"so",
	"yet",
	"this",
	"that",
	"these",
	"those",
	"it",
	"its",
	"not",
	"no"
]);
/** Tables that support temporal supersession. */
var SUPERSEDABLE_TABLES = [
	{
		table: "brain_decisions",
		textCol: "decision",
		type: "decision"
	},
	{
		table: "brain_learnings",
		textCol: "insight",
		type: "learning"
	},
	{
		table: "brain_patterns",
		textCol: "pattern",
		type: "pattern"
	},
	{
		table: "brain_observations",
		textCol: "narrative",
		type: "observation"
	}
];
/**
* Extract significant keywords from text for overlap comparison.
* Filters stop words and tokens shorter than 4 characters.
*/
function extractKeywords(text) {
	const words = text.toLowerCase().replace(/[^a-z0-9\s\-_]/g, " ").split(/\s+/);
	const keywords = /* @__PURE__ */ new Set();
	for (const w of words) if (w.length >= 4 && !STOP_WORDS.has(w)) keywords.add(w);
	return keywords;
}
/**
* Compute keyword-based Jaccard similarity between two texts.
* Returns a value in [0, 1].
*/
function keywordSimilarity(textA, textB) {
	const kwA = extractKeywords(textA);
	const kwB = extractKeywords(textB);
	if (kwA.size === 0 || kwB.size === 0) return {
		similarity: 0,
		shared: []
	};
	const shared = [];
	for (const w of kwA) if (kwB.has(w)) shared.push(w);
	if (shared.length < MIN_SHARED_KEYWORDS) return {
		similarity: 0,
		shared: []
	};
	const union = kwA.size + kwB.size - shared.length;
	return {
		similarity: union > 0 ? shared.length / union : 0,
		shared
	};
}
/**
* Build the brain_page_nodes node ID for a typed entry.
* Mirrors the convention used throughout the codebase.
*/
function buildNodeId(type, entryId) {
	return `${type}:${entryId}`;
}
/**
* Find the TableConfig for a given entry ID by probing tables.
* Returns null if the entry cannot be located.
*/
async function locateEntry(projectRoot, entryId) {
	await getBrainDb(projectRoot);
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return null;
	for (const tc of SUPERSEDABLE_TABLES) if (typedGet(nativeDb.prepare(`SELECT id FROM ${tc.table} WHERE id = ? LIMIT 1`), entryId)) return {
		tableConfig: tc,
		nodeId: buildNodeId(tc.type, entryId)
	};
	return null;
}
/**
* Mark an existing memory entry as superseded by a newer one.
*
* Effects:
*   1. Sets `invalid_at` on the old entry (soft-eviction; the row is kept).
*   2. Inserts a `supersedes` edge in brain_page_edges: new → old.
*      The `provenance` field carries the reason for the supersession.
*   3. Creates/refreshes brain_page_nodes rows for both entries (best-effort).
*
* The old entry is NEVER deleted — it remains in the table as an audit record.
* Only entries currently valid (invalid_at IS NULL) can be superseded.
*
* @param projectRoot - Absolute path to the CLEO project root.
* @param oldId - ID of the entry being superseded.
* @param newId - ID of the newer entry that supersedes the old one.
* @param reason - Human-readable reason for the supersession (stored as edge provenance).
* @returns SupersedeResult describing the outcome.
* @throws When either entry cannot be located or the DB is unavailable.
*/
async function supersedeMemory(projectRoot, oldId, newId, reason) {
	if (!oldId?.trim()) throw new Error("oldId is required");
	if (!newId?.trim()) throw new Error("newId is required");
	if (!reason?.trim()) throw new Error("reason is required");
	if (oldId === newId) throw new Error("oldId and newId must be different entries");
	await getBrainDb(projectRoot);
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) throw new Error("brain.db is unavailable");
	const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
	const oldLocation = await locateEntry(projectRoot, oldId);
	if (!oldLocation) throw new Error(`Entry not found: ${oldId}`);
	const newLocation = await locateEntry(projectRoot, newId);
	if (!newLocation) throw new Error(`Entry not found: ${newId}`);
	const { tableConfig: oldTc, nodeId: oldNodeId } = oldLocation;
	const { nodeId: newNodeId } = newLocation;
	try {
		nativeDb.prepare(`UPDATE ${oldTc.table} SET invalid_at = ?, updated_at = ? WHERE id = ? AND invalid_at IS NULL`).run(now, now, oldId);
	} catch {}
	const provenanceText = reason.substring(0, 500);
	try {
		nativeDb.prepare(`INSERT OR IGNORE INTO brain_page_edges
           (from_id, to_id, edge_type, weight, provenance, created_at)
         VALUES (?, ?, 'supersedes', 1.0, ?, ?)`).run(newNodeId, oldNodeId, provenanceText, now);
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		throw new Error(`Failed to create supersedes edge: ${message}`);
	}
	return {
		success: true,
		oldId,
		newId,
		edgeType: "supersedes"
	};
}
/**
* Detect whether a new entry supersedes existing brain entries.
*
* Called automatically by the store functions (`storeDecision`, `storeLearning`,
* `storePattern`) after a new entry is written. Compares the new entry's content
* against existing valid entries in the same table using keyword-based Jaccard
* similarity (embedding similarity used when available via sqlite-vec).
* against existing valid entries in the same table using:
*
*   1. Embedding similarity (via sqlite-vec) when available and the entry has a
*      vector. Falls back to keyword-based Jaccard if vectors are absent.
*   2. Keyword Jaccard similarity ≥ KEYWORD_OVERLAP_THRESHOLD.
*
* An entry is only considered a supersession candidate when the new entry is
* temporally newer than the existing one (guaranteed for freshly-written entries).
*
* This function is BEST-EFFORT — it never throws. Errors are swallowed and
* logged with console.warn so the calling store function is not blocked.
*
* @param projectRoot - Absolute path to the CLEO project root.
* @param newEntry - The freshly-stored entry to check against existing entries.
* @returns List of candidate supersessions (empty if none found or on any error).
*/
async function detectSupersession(projectRoot, newEntry) {
	try {
		await getBrainDb(projectRoot);
		const nativeDb = getBrainNativeDb();
		if (!nativeDb) return [];
		const newLocation = await locateEntry(projectRoot, newEntry.id);
		if (!newLocation) return [];
		const { tableConfig } = newLocation;
		const existing = typedAll(nativeDb.prepare(`
        SELECT id, COALESCE(${tableConfig.textCol}, '') AS text,
               created_at, quality_score
        FROM ${tableConfig.table}
        WHERE invalid_at IS NULL
          AND id != ?
        ORDER BY created_at DESC
        LIMIT 200
      `), newEntry.id);
		if (existing.length === 0) return [];
		const embeddingScores = /* @__PURE__ */ new Map();
		if (isBrainVecLoaded()) try {
			const { isEmbeddingAvailable, embedText } = await import('./brain-embedding-Djw9ekXu.js');
			if (isEmbeddingAvailable()) {
				const queryVector = await embedText(newEntry.text);
				if (queryVector) {
					const knnRows = typedAll(nativeDb.prepare("SELECT id, distance FROM brain_embeddings WHERE embedding MATCH ? ORDER BY distance LIMIT 50"), new Float32Array(queryVector.buffer, queryVector.byteOffset, queryVector.length));
					for (const kr of knnRows) {
						const cosineSimilarity = Math.max(0, 1 - kr.distance / 2);
						const bareId = kr.id.includes(":") ? kr.id.split(":").slice(1).join(":") : kr.id;
						embeddingScores.set(bareId, cosineSimilarity);
					}
				}
			}
		} catch {}
		const candidates = [];
		for (const row of existing) {
			if (row.created_at >= newEntry.createdAt) continue;
			const { similarity: keywordSim, shared } = keywordSimilarity(newEntry.text, row.text);
			const embeddingScore = embeddingScores.get(row.id) ?? 0;
			const combinedSimilarity = Math.max(embeddingScore, keywordSim);
			if (combinedSimilarity >= KEYWORD_OVERLAP_THRESHOLD) candidates.push({
				existingId: row.id,
				similarity: combinedSimilarity,
				table: tableConfig.table,
				sharedKeywords: shared.slice(0, 10)
			});
		}
		candidates.sort((a, b) => b.similarity - a.similarity);
		return candidates;
	} catch (err) {
		log.warn({ err }, "detectSupersession failed");
		return [];
	}
}

export { detectSupersession as d, supersedeMemory as s };
//# sourceMappingURL=temporal-supersession-BZ46j30w.js.map
