import { b as getLogger } from './paths-2M_UxD_H.js';
import { r as resolveLeaseMode, d as acquireWriterLease, f as assertWriterLeaseHeld } from './dual-scope-db-ATrwq0LH.js';
import { existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
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 'url';
import 'buffer';
import 'node:module';
import 'node:child_process';
import 'node:crypto';
import 'node:process';
import 'node:async_hooks';
import './indexes-aYlULWG3.js';
import './utils-BvHPJOVk.js';

//#region ../core/dist/memory/brain-writer-thread.js
/**
* BRAIN single-writer chokepoint — main-thread queue manager.
*
* All hot-path writes to `brain.db` MUST route through `enqueueBrainWrite` so
* that a single Node.js `worker_threads.Worker` owns the only write handle and
* serializes every INSERT/UPDATE through one consumer. This eliminates the
* within-process race documented in the T10301 RCA (page-1 sqlite_schema
* B-tree corruption caused by concurrent setImmediate writers + dialectic-hook
* + propose-tick reconciler + STDP plasticity loop all opening their own
* `getBrainDb` singletons).
*
* ## Architecture
*
*  Main thread                         Worker thread
*  ───────────                         ─────────────
*  enqueueBrainWrite(op)               (owns getBrainDb handle)
*        │                                    ▲
*        ▼                                    │ MessagePort
*  pendingRequests[seq] = {resolve, reject}   │
*        │                                    │
*        └──── postMessage({seq, op}) ────────┘
*                                             │
*                                             ▼
*                                       handleWriteOp(op)
*                                       (SQL INSERT/UPDATE)
*                                             │
*        ┌──── postMessage({seq, ok, ...})────┘
*        ▼
*  resolve(...) → caller's await
*
* Reads continue to use `getBrainDb` / `getBrainNativeDb` directly. SQLite WAL
* permits concurrent readers; only writes must be funneled.
*
* ## Bypass mechanism
*
* Forensic / one-shot scripts that need a direct write handle can set
* `CLEO_BRAIN_BYPASS_WRITER_THREAD=1`. Each call to `enqueueBrainWrite` then
* executes the op inline on the main thread. **A Pino warn is emitted on every
* bypass** (audit trail per AC #5).
*
* @task T10351
* @epic T10286
* @saga T10281
*/
/**
* Check whether the bypass env var is set. Emits a Pino warn on every call
* so the audit trail is preserved (AC #5).
*/
function bypassEnabled() {
	if (process.env["CLEO_BRAIN_BYPASS_WRITER_THREAD"] === "1") {
		getLogger("brain-writer").warn({
			event: "bypass-active",
			envVar: "CLEO_BRAIN_BYPASS_WRITER_THREAD"
		}, "Brain writer-thread chokepoint bypassed via env var — direct main-thread write");
		return true;
	}
	return false;
}
/**
* Resolve the absolute path to the compiled brain-writer-worker script.
*
* Strategy:
*  1. Adjacent to this file (`memory/brain-writer-worker.js`)
*  2. Fallback null when not found (esbuild bundle context, tests, etc.)
*/
function resolveWorkerPath() {
	try {
		const candidate = join(dirname(fileURLToPath(import.meta.url)), "brain-writer-worker.js");
		if (existsSync(candidate)) return candidate;
		return null;
	} catch {
		return null;
	}
}
/**
* Singleton writer-thread manager. One worker per process, owns the only
* brain.db write handle. Reads remain on the main thread via `getBrainDb`.
*/
var BrainWriterManager = class {
	worker = null;
	workerReady = false;
	workerInitError = null;
	initPromise = null;
	pending = /* @__PURE__ */ new Map();
	seqCounter = 0;
	shuttingDown = false;
	/** Lazy-initialize the worker thread on first enqueue. */
	async ensureWorker() {
		if (this.workerReady || this.shuttingDown) return;
		if (this.workerInitError) throw this.workerInitError;
		if (this.initPromise) return this.initPromise;
		this.initPromise = this.doInit();
		try {
			await this.initPromise;
		} finally {
			this.initPromise = null;
		}
	}
	async doInit() {
		const workerPath = resolveWorkerPath();
		if (!workerPath) {
			this.workerInitError = /* @__PURE__ */ new Error("brain-writer-worker.js not found adjacent to brain-writer-thread.js");
			throw this.workerInitError;
		}
		const { Worker } = await import('node:worker_threads');
		const worker = new Worker(workerPath);
		worker.on("message", (msg) => {
			this.handleWorkerMessage(msg);
		});
		worker.on("error", (err) => {
			for (const [, req] of this.pending) req.reject(err instanceof Error ? err : new Error(String(err)));
			this.pending.clear();
			this.worker = null;
			this.workerReady = false;
			this.workerInitError = err instanceof Error ? err : new Error(String(err));
			getLogger("brain-writer").error({ err }, "Brain writer-thread crashed");
		});
		worker.on("exit", (code) => {
			this.worker = null;
			this.workerReady = false;
			if (code !== 0 && !this.shuttingDown) {
				const err = /* @__PURE__ */ new Error(`brain-writer-worker exited with code ${code}`);
				for (const [, req] of this.pending) req.reject(err);
				this.pending.clear();
				this.workerInitError = err;
			}
		});
		this.worker = worker;
		this.workerReady = true;
	}
	handleWorkerMessage(msg) {
		const pending = this.pending.get(msg.seq);
		if (!pending) return;
		this.pending.delete(msg.seq);
		if (msg.ok) pending.resolve(msg.result);
		else pending.reject(new Error(msg.error));
	}
	/**
	* Enqueue a write op. Returns a promise that resolves with the worker's
	* result envelope payload (shape depends on op kind).
	*/
	async enqueue(op) {
		if (this.shuttingDown) throw new Error("brain-writer is shutting down — no new writes accepted");
		await this.ensureWorker();
		const worker = this.worker;
		if (!worker) throw new Error("brain-writer worker is not available");
		const seq = ++this.seqCounter;
		const envelope = {
			seq,
			op
		};
		return new Promise((resolve, reject) => {
			this.pending.set(seq, {
				resolve,
				reject
			});
			try {
				worker.postMessage(envelope);
			} catch (err) {
				this.pending.delete(seq);
				reject(err instanceof Error ? err : new Error(String(err)));
			}
		});
	}
	/** Drain pending work and terminate the worker. Idempotent. */
	async shutdown() {
		if (this.shuttingDown) return;
		this.shuttingDown = true;
		const grace = 250;
		const started = Date.now();
		while (this.pending.size > 0 && Date.now() - started < grace) await new Promise((r) => setTimeout(r, 25));
		if (this.worker) {
			try {
				await this.worker.terminate();
			} catch {}
			this.worker = null;
		}
		this.workerReady = false;
		this.pending.clear();
	}
	/** @internal — test helper */
	_hasInFlight() {
		return this.pending.size;
	}
};
/**
* The cleo.db scope the brain WRITE handle targets. `getBrainDb()` routes through
* `openDualScopeDb('project')` (memory-sqlite.ts T11522) — the `brain_*` tables
* live in the consolidated PROJECT `cleo.db`, co-located with `tasks_*`. So the
* brain lane is leased in the `'project'` scope.
*/
var BRAIN_LEASE_SCOPE = "project";
/** The dedicated brain write lane (AC4 — held independently of the `tasks` chokepoint). */
var BRAIN_LEASE_LANE = "brain";
/**
* The process-local brain-batch grant. The brain worker serializes every op
* through one consumer; the lease is taken ONCE per drain batch (acquire → drain
* N → release) rather than per-op, which is both cheaper and starvation-safe
* because the chokepoint already serializes internally. Concurrent
* `enqueueBrainWrite` callers in this process re-enter the SAME grant (refcount++,
* no second claim txn) for the duration of the in-flight batch; the grant is
* released when the batch quiesces (no callers left in flight).
*/
var _brainBatchHandle = null;
var _brainBatchRefcount = 0;
/**
* In-flight FIRST brain-batch acquire promise (single-flight — companion to the
* writer-lease engine's own single-flight guard). Without it, two concurrent
* `enqueueBrainWrite` callers racing the very first acquire both observe
* `_brainBatchHandle === null` across the `await acquireWriterLease(...)` and each
* runs a full acquire — the second stalls the whole acquire window then writes
* lease-less. The documented hot path (STDP loop + dialectic hook + propose-tick
* reconciler all calling `enqueueBrainWrite` concurrently) is exactly that race.
*/
var _brainBatchInflight = null;
/**
* Acquire (or re-enter) the batch-granularity `brain` lease for the duration of a
* single `enqueueBrainWrite`. The first in-flight call acquires the lease;
* concurrent calls share it (refcount) — including callers that race the FIRST
* acquire, which single-flight onto the same in-flight promise rather than each
* running a full acquire. The lease is released when the last in-flight call
* finishes (refcount → 0). In `off` mode this is a no-op pass-through (no lease,
* `busy_timeout` serializes as before).
*
* @returns A release callback the caller MUST invoke (in a `finally`) when its
*   write completes; the underlying row is freed only when the batch quiesces.
*/
async function enterBrainBatchLease() {
	if (resolveLeaseMode() === "off") return async () => {};
	if (_brainBatchHandle === null) {
		if (_brainBatchInflight === null) _brainBatchInflight = acquireWriterLease(BRAIN_LEASE_SCOPE, BRAIN_LEASE_LANE, { priority: 50 });
		try {
			const handle = await _brainBatchInflight;
			if (_brainBatchHandle === null) _brainBatchHandle = handle;
		} finally {
			_brainBatchInflight = null;
		}
	}
	_brainBatchRefcount += 1;
	let released = false;
	return async () => {
		if (released) return;
		released = true;
		_brainBatchRefcount -= 1;
		if (_brainBatchRefcount <= 0 && _brainBatchHandle !== null) {
			const handle = _brainBatchHandle;
			_brainBatchHandle = null;
			_brainBatchRefcount = 0;
			await handle.release();
		}
	};
}
/**
* Async mutex used by the inline fallback path so that bypass-mode writes
* (and the test-env path where the worker file is unavailable) still
* serialize within the process. Without this guard, the bypass would
* reintroduce the very race condition the chokepoint exists to eliminate.
*/
var inlineQueueTail = Promise.resolve();
function runInline(op) {
	const next = inlineQueueTail.then(() => executeInline(op));
	inlineQueueTail = next.catch(() => void 0);
	return next;
}
/**
* Inline executor — imports the worker's handler module dynamically and runs
* the op on the main thread. Used by:
*  - bypass mode (`CLEO_BRAIN_BYPASS_WRITER_THREAD=1`)
*  - worker-unavailable contexts (tests, esbuild bundle context)
*
* The handler module is shared between the worker thread and this fallback,
* so behaviour is identical except for the thread of execution.
*/
async function executeInline(op) {
	assertWriterLeaseHeld(BRAIN_LEASE_SCOPE, BRAIN_LEASE_LANE);
	const { handleWriteOp } = await import('./brain-writer-handlers-CWbffydU.js');
	return handleWriteOp(op);
}
var _manager = null;
function getManager() {
	if (!_manager) {
		_manager = new BrainWriterManager();
		process.on("exit", () => {
			_manager?.shutdown().catch(() => void 0);
		});
		const gracefulShutdown = () => {
			const deadline = setTimeout(() => process.exit(0), 2e3);
			deadline.unref();
			_manager?.shutdown().catch(() => void 0).finally(() => {
				clearTimeout(deadline);
			});
		};
		process.once("SIGTERM", gracefulShutdown);
		process.once("SIGINT", gracefulShutdown);
	}
	return _manager;
}
/**
* Enqueue a brain.db write op through the single-writer chokepoint.
*
* Resolves with the typed result for the op's `kind`. Rejects when:
*  - the op fails inside the worker (worker forwards the error message)
*  - the worker crashes (the in-flight promise rejects with the worker error)
*  - bypass mode is on AND the inline execution fails
*
* @example
* ```ts
* const result = await enqueueBrainWrite({
*   kind: 'observe',
*   projectRoot: '/path/to/project',
*   params: { text: 'Decided X over Y', sourceType: 'manual' },
* });
* if (result.kind === 'observe') {
*   console.log('Stored observation', result.result.id);
* }
* ```
*
* @param op - The write op (discriminated by `kind`).
* @returns Worker result for the op.
*/
async function enqueueBrainWrite(op) {
	const releaseBatch = await enterBrainBatchLease();
	try {
		if (bypassEnabled()) return await runInline(op);
		try {
			return await getManager().enqueue(op);
		} catch (err) {
			const msg = err instanceof Error ? err.message : String(err);
			getLogger("brain-writer").debug({ err: msg }, "Brain writer-thread unavailable — falling back to inline serialized executor");
			return await runInline(op);
		}
	} finally {
		await releaseBatch();
	}
}

export { enqueueBrainWrite };
//# sourceMappingURL=brain-writer-thread-Ddrc33cA.js.map
