import { b as getLogger } from './paths-2M_UxD_H.js';
import { g as getBrainAccessor } from './memory-accessor2-BzlgmY-Z.js';
import './memory-schema-Bi7cChwo.js';
import './sqlite-Dsbe0wec.js';
import 'node:fs';
import 'node:path';
import 'node:os';
import 'node:events';
import 'node:diagnostics_channel';
import 'fs';
import 'events';
import 'util';
import 'path';
import 'assert';
import 'worker_threads';
import 'module';
import 'node:url';
import 'url';
import 'buffer';
import 'node:module';
import 'node:child_process';
import 'node:crypto';
import 'node:process';
import 'node:async_hooks';
import './utils-BvHPJOVk.js';
import './logger-ChCwlV5k.js';
import './indexes-aYlULWG3.js';
import './chunk-DDP5xfzh.js';
import './unique-constraint-BM-Apx1H.js';
import './dual-scope-db-ATrwq0LH.js';
import './conduit-Ca_OFWR_.js';
import './agent-registry-BtE5uhMO.js';
import './nexus-sqlite2-DgTuv9BQ.js';
import './nexus-schema2-BEd-ol6e.js';
import 'node:sqlite';
import './lock-BoHF25mx.js';
import './index-IVoIz4D5.js';
import 'constants';
import 'stream';

//#region ../core/dist/sticky/convert.js
/**
* Promote a Tier-2 attention entry to a durable BRAIN observation — the
* dream-cycle's promotion conduit (E3 · Epic T11289 · Saga T11283).
*
* Stickies remain the conduit (council verdict): rather than build a parallel
* promotion path, this reuses the exact `convertStickyToMemory` shape — write a
* `brain_observations` row via `observeBrain`, then attach provenance — so the
* single salient-→-durable path is shared. The caller
* ({@link consolidateAttention}) is responsible for flipping the source row to
* `status='consolidated'` after a successful promotion (idempotency lives with
* the buffer-status transition, not here).
*
* ## Leakage preservation (Epic T11289 AC; T11383)
*
* The promoted observation carries the entry's scope as structured provenance:
*
* - `crossRef` records `attention:<id>` + `scope:<kind>:<id>` so the durable row
*   is traceable back to the exact scope key it came from.
* - `agent` records the writing agent's id so an agent-scoped jot's durable form
*   is attributed to that agent, never surfaced under another agent's identity.
* - `provenanceChain` records the source attention id.
*
* Because the scope key travels with the observation, agent A's promoted entry
* can never be mis-attributed to agent B's scope — the structural read-time
* guarantee of the buffer is carried forward into Tier-3.
*
* @param row - The attention entry to promote (already scored + verdict='promote').
* @param projectRoot - Project root path (brain.db resolution).
* @returns Result with the new durable observation id, or a structured error.
* @task T11383
* @epic T11289
*/
async function promoteAttentionToMemory(row, projectRoot) {
	const { observeBrain } = await import('./brain-retrieval2-CM6hsYog.js');
	const scopeRef = `scope:${row.scopeKind}:${row.scopeId}`;
	const attentionRef = `attention:${row.id}`;
	try {
		return {
			success: true,
			memoryId: (await observeBrain(projectRoot, {
				text: row.content,
				title: row.content.slice(0, 50),
				type: "discovery",
				sourceType: "agent",
				origin: "auto-extract",
				crossRef: [attentionRef, scopeRef],
				provenanceChain: [row.id],
				...row.agentId ? { agent: row.agentId } : {},
				...row.sessionId ? { sourceSessionId: row.sessionId } : {}
			})).id
		};
	} catch (error) {
		return {
			success: false,
			error: {
				code: "E_CONVERT_FAILED",
				message: String(error)
			}
		};
	}
}

//#region ../core/dist/memory/promotion-score.js
/**
* Composite 6-signal scorer for brain observation typed promotion.
*
* Replaces the legacy 3-rule OR union in runTierPromotion with a
* weighted composite score that is explainable and auditable.
*
* Signals:
* 1. citation_count   — retrieval frequency (normalised to 0.0–1.0 via tanh)
* 2. quality_score    — content richness computed at insert (0.0–1.0)
* 3. stability_score  — biological-analog consolidation metric (0.0–1.0)
* 4. recency          — inverse age decay (1.0 = today, approaches 0 over 90 days)
* 5. user_verified    — hard boost (0 or 1)
* 6. outcome_correlated — whether tied to a completed task outcome (0 or 1)
*
* Formula:
*   raw = w_citation × normalise(citation_count)
*       + w_quality  × (quality_score ?? 0.5)
*       + w_stability × (stability_score ?? 0.5)
*       + w_recency  × recencyFactor(created_at)
*       + w_verified × user_verified
*       + w_outcome  × outcome_correlated
*
*   composite_score = clamp(raw / sum(weights), 0.0, 1.0)
*
* Weights are designed so that a verified OR high-citation entry always
* crosses the default threshold (0.6), while pure noise (0 citations,
* 0.3 quality, 0.5 stability, old age, unverified, no outcome) scores ~0.3.
*
* @task T1001
* @epic T1000
*/
/** Weights for each signal. Must sum > 0 (they are normalised internally). */
var WEIGHTS = {
	citation: .2,
	quality: .2,
	stability: .15,
	recency: .15,
	verified: .2,
	outcome: .1
};
var WEIGHT_SUM = WEIGHTS.citation + WEIGHTS.quality + WEIGHTS.stability + WEIGHTS.recency + WEIGHTS.verified + WEIGHTS.outcome;
/**
* Normalise a raw citation count to 0–1 using tanh(count / 5).
*
* tanh(1/5) ≈ 0.20  (citation_count=1)
* tanh(3/5) ≈ 0.54  (citation_count=3)
* tanh(5/5) ≈ 0.76  (citation_count=5)
* tanh(10/5) ≈ 0.96  (citation_count=10)
*/
function normaliseCitation(count) {
	return Math.tanh(count / 5);
}
/**
* Compute a recency factor in 0–1 from an ISO date string.
*
* Uses: recency = exp(-age_days / 30)
*
* age_days=0  → 1.0
* age_days=30 → ~0.37
* age_days=90 → ~0.05
*/
function recencyFactor(createdAt) {
	if (!createdAt) return Math.exp(-1);
	const created = new Date(createdAt.replace(" ", "T"));
	if (Number.isNaN(created.getTime())) return Math.exp(-1);
	const ageDays = (Date.now() - created.getTime()) / (1e3 * 60 * 60 * 24);
	return Math.exp(-ageDays / 30);
}
/** Clamp a value to the [min, max] range. */
function clamp(value, min, max) {
	return Math.max(min, Math.min(max, value));
}
/**
* Compute the composite promotion score for a brain entry.
*
* Returns a value in [0.0, 1.0]. Entries above the promotion threshold
* (default 0.6) are eligible for typed promotion.
*
* @param signals - The 6 input signals for this entry
* @returns Composite score in [0.0, 1.0]
*/
function computePromotionScore(signals) {
	const citationSignal = normaliseCitation(signals.citationCount);
	const qualitySignal = signals.qualityScore ?? .5;
	const stabilitySignal = signals.stabilityScore ?? .5;
	const recencySignal = recencyFactor(signals.createdAt);
	const verifiedSignal = signals.userVerified > 0 ? 1 : 0;
	const outcomeSignal = signals.outcomeCorrelated > 0 ? 1 : 0;
	return clamp((WEIGHTS.citation * citationSignal + WEIGHTS.quality * qualitySignal + WEIGHTS.stability * stabilitySignal + WEIGHTS.recency * recencySignal + WEIGHTS.verified * verifiedSignal + WEIGHTS.outcome * outcomeSignal) / WEIGHT_SUM, 0, 1);
}
/**
* Default promotion threshold. Entries with composite_score >= this value
* are eligible for typed promotion via promoteObservationsToTyped.
*/
var PROMOTION_THRESHOLD = .6;
/**
* Compute the full rationale breakdown for a promotion decision.
*
* The rationale is serialised to JSON and stored in brain_promotion_log.rationale_json
* to make every promotion explainable (pairs with T997 promote-explain CLI).
*/
function computePromotionRationale(signals, threshold = PROMOTION_THRESHOLD) {
	const citationSignal = normaliseCitation(signals.citationCount);
	const qualitySignal = signals.qualityScore ?? .5;
	const stabilitySignal = signals.stabilityScore ?? .5;
	const recencySignal = recencyFactor(signals.createdAt);
	const verifiedSignal = 0;
	const outcomeSignal = 0;
	const composite = computePromotionScore(signals);
	return {
		signals: {
			citation_count: citationSignal,
			quality_score: qualitySignal,
			stability_score: stabilitySignal,
			recency: recencySignal,
			user_verified: verifiedSignal,
			outcome_correlated: outcomeSignal
		},
		weighted: {
			citation_count: WEIGHTS.citation * citationSignal,
			quality_score: WEIGHTS.quality * qualitySignal,
			stability_score: WEIGHTS.stability * stabilitySignal,
			recency: WEIGHTS.recency * recencySignal,
			user_verified: WEIGHTS.verified * verifiedSignal,
			outcome_correlated: WEIGHTS.outcome * outcomeSignal
		},
		composite_score: composite,
		threshold,
		decision: composite >= threshold ? "promote" : "skip"
	};
}
//#endregion
//#region ../core/dist/memory/attention-consolidate.js
/**
* Tier-2 attention consolidation — the dream-cycle's review of the working-memory
* buffer (E3 · Epic T11289 · Saga T11283).
*
* The biological loop closed by this module:
*
*   `jot` → Tier-2 `brain_attention` (scope-keyed, decays)
*        → **dream-cycle consolidates** (this module)
*        → promote salient → Tier-3 / BRAIN (durable observation)
*        | discard noise / expired
*        | keep mid-salience open for the next cycle.
*
* ## One scorer, one verdict (reconciles the split AC)
*
* Every reviewed attention entry receives EXACTLY ONE verdict per dream cycle —
* `promote` | `keep` | `discard` — from the SAME composite 6-signal scorer used
* for observation promotion ({@link computePromotionScore} in
* `promotion-score.ts`). There is NO parallel attention scorer: the epic's
* AC2/AC3/AC4 were a single criterion mis-split into three fragments, and
* {@link decideAttentionVerdict} reconciles them into one coherent decision
* (child T11385).
*
* ## Promotion is via the conduit, never a parallel path
*
* A `promote` verdict routes through the sticky-convert conduit shape
* ({@link promoteAttentionToMemory} in `sticky/convert.ts` → `observeBrain`),
* carrying the entry's `scope_kind`/`scope_id`/`agent_id` as provenance so a
* promoted entry from agent A can never surface under agent B's scope. The
* source row is then marked `status='consolidated'` (idempotent — re-running the
* dream cycle never double-promotes).
*
* ## Decay is via the homeostatic sweep, never reinvented
*
* `discard`/expiry reuses the accessor's in-SQL TTL + decay-floor sweep
* ({@link BrainDataAccessor.expireAttention}, the same `expireAttention` policy
* the CLI + focus path share) so Tier-2 never grows unbounded. Thresholds are
* honoured in SQL — never load-all-then-JS-filter.
*
* @task T11382 — ingest brain_attention into the dream cycle
* @task T11383 — promote salient entries via the conduit
* @task T11384 — decay/discard low-salience entries via the homeostatic sweep
* @task T11385 — single per-entry promote|keep|discard verdict from the scorer
* @epic T11289 EP-DREAM-CONSOLIDATE-TIER2
* @saga T11283 SG-COGNITIVE-SUBSTRATE
*/
/** Logger for the Tier-2 consolidation pass — routed through pino (never raw console.*). */
var log = getLogger("attention-consolidate");
/**
* Score at or above which an attention entry is PROMOTED to durable memory.
*
* The promotion SCORER is shared verbatim with observation promotion
* ({@link computePromotionScore} — one scorer, no parallel implementation). The
* promotion BAR, however, is calibrated to the attention signal space rather
* than reusing the observation bar ({@link PROMOTION_THRESHOLD} = 0.6).
*
* Three of the six signals are structurally unavailable to a raw working-memory
* jot: `citation_count` (a jot is not retrieved through the citation log),
* `user_verified` (jots are not owner-verified), and `outcome_correlated` (no
* task-outcome link). Those carry combined weight 0.5, so the maximum attainable
* composite for ANY jot is the remaining `quality + stability + recency` ≈ 0.5 —
* a jot can never reach the 0.6 observation bar. Calibrating to 0.35 cleanly
* separates a salient, detailed/tagged jot (~0.40) from a mid-salience note
* (~0.29) and noise (~0.23) within that 0..0.5 attention band. (See the verdict
* distribution test for the empirical separation.)
*/
var ATTENTION_PROMOTE_THRESHOLD = .35;
/**
* Decay floor below which an OPEN, unpromoted attention entry is DISCARDED.
*
* Matches {@link DEFAULT_DECAY_THRESHOLD} (0.1) used by the live-items query and
* the CLI sweep, so an item hidden from reads is also the item swept here.
*/
var ATTENTION_DISCARD_THRESHOLD = .1;
/**
* Derive the composite-scorer {@link PromotionSignals} for an attention entry.
*
* Attention rows are lightweight working-memory jots — they do not carry the
* citation/quality/stability columns a `brain_observations` row accrues. We map
* the available signal onto the EXISTING 6-signal vector rather than inventing a
* parallel scorer:
*
* - `citationCount`   — always 0 (a jot is not retrieved through the citation log).
* - `qualityScore`    — a content-richness proxy: longer + tagged jots read as
*   higher quality, normalised to `[0, 1]` (null would default to 0.5, which is
*   too generous for a one-word jot, so we compute an explicit proxy).
* - `stabilityScore`  — the entry's own `decay_score` when present (an entry that
*   has survived decay is more stable); null → scorer default (0.5).
* - `createdAt`       — drives the recency factor (fresh jots score higher).
* - `userVerified`    — 0 (jots are not owner-verified).
* - `outcomeCorrelated` — 0 (no task-outcome correlation for raw jots).
*
* @param row - The attention row to score.
* @returns The 6-signal vector consumed by {@link computePromotionScore}.
* @task T11382
* @task T11385
*/
function attentionToPromotionSignals(row) {
	const tagCount = Array.isArray(row.tags) ? row.tags.length : 0;
	const lengthFactor = Math.min(1, row.content.trim().length / 120);
	const tagFactor = Math.min(.3, tagCount * .1);
	const qualityScore = Math.min(1, lengthFactor * .7 + tagFactor);
	const createdAtIso = new Date(row.createdAt).toISOString();
	return {
		citationCount: 0,
		qualityScore,
		stabilityScore: row.decayScore,
		createdAt: createdAtIso,
		userVerified: 0,
		outcomeCorrelated: 0
	};
}
/**
* Decide the SINGLE verdict for one attention entry from its composite score and
* liveness — the reconciliation of the parent epic's split AC2/AC3/AC4.
*
* Exactly one of `promote` | `keep` | `discard` is returned:
*
* 1. `discard` — the entry is already past its TTL (`expires_at <= now`) OR its
*    `decay_score` is below {@link ATTENTION_DISCARD_THRESHOLD}. (The accessor
*    sweep flips these to `discarded`; deciding `discard` here keeps the verdict
*    audit consistent with what the sweep will do.)
* 2. `promote` — a live entry whose composite score ≥
*    {@link ATTENTION_PROMOTE_THRESHOLD}.
* 3. `keep` — everything else: a live, mid-salience entry that stays `open` for
*    the next cycle.
*
* @param row - The attention entry under review.
* @param score - Its composite 6-signal score (from {@link computePromotionScore}).
* @param now - Reference time (unix ms) for the TTL check; defaults to `Date.now()`.
* @param promoteThreshold - Promote bar (defaults to {@link ATTENTION_PROMOTE_THRESHOLD}).
* @param discardThreshold - Discard decay floor (defaults to {@link ATTENTION_DISCARD_THRESHOLD}).
* @returns The single verdict for this entry.
* @task T11385
*/
function decideAttentionVerdict(row, score, now = Date.now(), promoteThreshold = ATTENTION_PROMOTE_THRESHOLD, discardThreshold = ATTENTION_DISCARD_THRESHOLD) {
	const expired = typeof row.expiresAt === "number" && row.expiresAt <= now;
	const decayedOut = typeof row.decayScore === "number" && row.decayScore < discardThreshold;
	if (expired || decayedOut) return "discard";
	if (score >= promoteThreshold) return "promote";
	return "keep";
}
/**
* Review the live Tier-2 attention buffer and apply one promote|keep|discard
* verdict per entry — the dream-cycle's consolidation pass over working memory.
*
* Steps:
*
* 1. Read every LIVE (`open`, non-expired, above-decay-floor) attention entry
*    across ALL scopes via the accessor's leakage-safe `findAttention` (no scope
*    restriction → the whole buffer; JSONB `tags` read via `json(col)`). This is
*    the dream-cycle reviewing the buffer, NOT a per-agent read.
* 2. Score each entry with the EXISTING composite 6-signal scorer and decide one
*    verdict ({@link decideAttentionVerdict}).
* 3. `promote` → route through the conduit ({@link promoteAttentionToMemory}),
*    carrying scope provenance, then mark the source `consolidated` (idempotent).
* 4. `discard`/expiry → the homeostatic sweep
*    ({@link BrainDataAccessor.expireAttention}) flips TTL/decay-floor entries to
*    `discarded` in SQL.
* 5. `keep` → left `open` for the next cycle.
*
* Every verdict is audited via pino ({@link getLogger}) so a teardown-race never
* routes a deferred log through the vitest console interceptor.
*
* Leakage preservation: scope provenance is attached at promotion time, so agent
* A's promoted entry never surfaces under agent B's scope — the structural
* guarantee the buffer's scope-key already provides for reads is carried forward
* into durable memory.
*
* @param projectRoot - Absolute project root (brain.db resolution).
* @param options - Reference time, per-cycle limit, threshold overrides.
* @returns The aggregated consolidation result + per-entry audit.
* @task T11382
* @task T11383
* @task T11384
* @task T11385
*/
async function consolidateAttention(projectRoot, options = {}) {
	const now = options.now ?? Date.now();
	const limit = options.limit ?? 200;
	const promoteThreshold = options.promoteThreshold ?? .35;
	const discardThreshold = options.discardThreshold ?? .1;
	const result = {
		reviewed: 0,
		promoted: 0,
		kept: 0,
		discarded: 0,
		reviews: []
	};
	const accessor = await getBrainAccessor(projectRoot);
	const live = await accessor.findAttention({
		openOnly: true,
		decayThreshold: discardThreshold,
		now,
		limit
	});
	result.reviewed = live.length;
	for (const row of live) {
		const signals = attentionToPromotionSignals(row);
		const score = computePromotionScore(signals);
		const verdict = decideAttentionVerdict(row, score, now, promoteThreshold, discardThreshold);
		let promotedToId = null;
		if (verdict === "promote") {
			const promotion = await promoteAttentionToMemory(row, projectRoot);
			if (promotion.success && promotion.memoryId) {
				promotedToId = promotion.memoryId;
				await accessor.setAttentionStatus(row.id, "consolidated");
				result.promoted += 1;
			} else {
				log.warn({
					attentionId: row.id,
					err: promotion.error
				}, "Tier-2 promotion via conduit failed; leaving entry open");
				result.kept += 1;
			}
		} else if (verdict === "discard") result.discarded += 1;
		else result.kept += 1;
		const rationale = computePromotionRationale(signals, promoteThreshold);
		log.debug({
			attentionId: row.id,
			verdict,
			score,
			scopeKind: row.scopeKind,
			scopeId: row.scopeId,
			rationale: rationale.signals
		}, "Tier-2 attention verdict");
		result.reviews.push({
			id: row.id,
			verdict,
			score,
			scopeKind: row.scopeKind,
			scopeId: row.scopeId,
			promotedToId
		});
	}
	const swept = await accessor.expireAttention({
		now,
		decayThreshold: discardThreshold
	});
	result.discarded = Math.max(result.discarded, swept);
	log.info({
		reviewed: result.reviewed,
		promoted: result.promoted,
		kept: result.kept,
		discarded: result.discarded
	}, "Tier-2 attention consolidation complete");
	return result;
}

export { consolidateAttention };
//# sourceMappingURL=attention-consolidate-CHZm70iX.js.map
