@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
404 lines (373 loc) • 15.8 kB
text/typescript
import { spawnSync } from "node:child_process";
import { createHash, randomUUID } from "node:crypto";
import {
chmodSync,
closeSync,
existsSync,
fsyncSync,
lstatSync,
mkdirSync,
openSync,
readFileSync,
realpathSync,
renameSync,
rmSync,
writeFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { DatabaseSync } from "./sqlite.ts";
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
import {
KxmConfigError,
KxmSchemaRegistry,
parseRestrictedYaml,
type JsonObject,
type KxmConfigIssue,
} from "./project-config.ts";
const MAX_BINDING_RECORD_BYTES = 256 * 1024;
const BINDING_LABEL = "Runtime-local repository bindings";
export interface KxmLocalBindingRecord {
schema: "kxm.local-repository-bindings.v1";
projectId: string;
projectRoot: string;
repositories: Readonly<Record<string, string>>;
}
export interface LoadedKxmLocalBindings extends KxmLocalBindingRecord {
file: string;
}
export interface KxmLocalBindingPathOptions {
stateRoot?: string;
env?: NodeJS.ProcessEnv;
homeDir?: string;
platform?: NodeJS.Platform;
}
export interface KxmLocalBindingStoreOptions extends KxmLocalBindingPathOptions {
schemasDir?: string;
}
export interface KxmLocalBindingWriteResult {
file: string;
written: boolean;
record: KxmLocalBindingRecord;
}
export interface KxmLocalBindingLock {
readonly projectRoot: string;
readonly file: string;
}
const activeLocks = new WeakSet<KxmLocalBindingLock>();
function bindingIssue(phase: KxmConfigIssue["phase"], code: string, message: string): KxmConfigIssue {
return { phase, code, file: BINDING_LABEL, message };
}
function bindingError(phase: KxmConfigIssue["phase"], code: string, message: string): never {
throw new KxmConfigError([bindingIssue(phase, code, message)]);
}
export function canonicalHostPath(path: string): string {
const absolute = resolve(path);
try {
return realpathSync.native(absolute);
} catch {
return absolute;
}
}
export function sameHostPath(left: string, right: string, platform = process.platform): boolean {
const first = canonicalHostPath(left);
const second = canonicalHostPath(right);
return platform === "win32"
? first.toLocaleLowerCase("en-US") === second.toLocaleLowerCase("en-US")
: first === second;
}
/** Resolve the per-user state root without consulting any legacy KXM_* workspace override. */
export function kxmUserStateRoot(options: KxmLocalBindingPathOptions = {}): string {
const env = options.env ?? process.env;
const platform = options.platform ?? process.platform;
const home = options.homeDir ?? homedir();
const explicit = options.stateRoot ?? env.KXM_STATE_HOME?.trim();
if (explicit) {
if (!isAbsolute(explicit)) bindingError("path", "local_state_root_not_absolute", "KXM_STATE_HOME must be an absolute host-local path");
return resolve(explicit);
}
if (platform === "win32") {
const localAppData = env.LOCALAPPDATA?.trim();
const base = localAppData && isAbsolute(localAppData) ? localAppData : join(home, "AppData", "Local");
return resolve(base, "KXM");
}
if (platform === "darwin") return resolve(home, "Library", "Application Support", "KXM");
const xdgState = env.XDG_STATE_HOME?.trim();
const base = xdgState && isAbsolute(xdgState) ? xdgState : join(home, ".local", "state");
return resolve(base, "kxm");
}
function projectBindingKey(projectRoot: string, platform = process.platform): string {
const canonical = canonicalHostPath(projectRoot);
const keyInput = platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
return createHash("sha256").update(keyInput, "utf8").digest("hex");
}
export function kxmLocalBindingFile(projectRoot: string, options: KxmLocalBindingPathOptions = {}): string {
const stateRoot = kxmUserStateRoot(options);
return join(stateRoot, "projects", projectBindingKey(projectRoot, options.platform), "repository-bindings.json");
}
function checkedDirectory(path: string, description: string): boolean {
if (!existsSync(path)) return false;
const stat = lstatSync(path);
if (stat.isSymbolicLink() || !stat.isDirectory()) {
bindingError("path", "local_binding_directory_invalid", `${description} must be a regular directory, not a link or file`);
}
return true;
}
function existingBindingDirectory(file: string, stateRoot: string): boolean {
const projects = join(stateRoot, "projects");
const project = dirname(file);
if (!checkedDirectory(stateRoot, "local state root")) return false;
if (!checkedDirectory(projects, "local projects directory")) return false;
return checkedDirectory(project, "local project binding directory");
}
function asRecord(value: JsonObject, file: string, projectRoot: string, schemasDir?: string): LoadedKxmLocalBindings {
const registry = new KxmSchemaRegistry(schemasDir);
const issues = registry.validateLocalBindings(value, BINDING_LABEL);
if (issues.length > 0) throw new KxmConfigError(issues);
const recordRoot = String(value.projectRoot);
if (!sameHostPath(recordRoot, projectRoot)) {
bindingError("semantic", "local_binding_project_root_mismatch", "binding record belongs to a different control worktree");
}
const repositories = Object.fromEntries(Object.entries(value.repositories as Record<string, string>)
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
return {
schema: "kxm.local-repository-bindings.v1",
projectId: String(value.projectId),
projectRoot: recordRoot,
repositories,
file,
};
}
/** Read one exact, bounded Runtime-local binding record. Missing state is not an error. */
export function readKxmLocalBindings(
projectRoot: string,
options: KxmLocalBindingStoreOptions = {},
): LoadedKxmLocalBindings | undefined {
const root = resolve(projectRoot);
const stateRoot = kxmUserStateRoot(options);
const file = kxmLocalBindingFile(root, options);
if (!existingBindingDirectory(file, stateRoot) || !existsSync(file)) return undefined;
const stat = lstatSync(file);
if (stat.isSymbolicLink() || !stat.isFile()) {
bindingError("path", "local_binding_file_invalid", "binding record must be a regular file, not a link or directory");
}
if (stat.size > MAX_BINDING_RECORD_BYTES) {
bindingError("parse", "local_binding_file_too_large", `binding record exceeds ${MAX_BINDING_RECORD_BYTES} bytes`);
}
const parsed = parseRestrictedYaml(readFileSync(file), BINDING_LABEL);
return asRecord(parsed, file, root, options.schemasDir);
}
function normalizedRecord(
projectRoot: string,
projectId: string,
repositories: Readonly<Record<string, string>>,
schemasDir?: string,
): KxmLocalBindingRecord {
const normalizedRepositories = Object.fromEntries(Object.entries(repositories)
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
.map(([repositoryId, path]) => {
if (!isAbsolute(path)) bindingError("path", "repository_binding_not_absolute", `host-local binding for ${repositoryId} must be absolute`);
return [repositoryId, canonicalHostPath(path)];
}));
const value: JsonObject = {
schema: "kxm.local-repository-bindings.v1",
projectId,
projectRoot: canonicalHostPath(projectRoot),
repositories: normalizedRepositories,
};
const issues = new KxmSchemaRegistry(schemasDir).validateLocalBindings(value, BINDING_LABEL);
if (issues.length > 0) throw new KxmConfigError(issues);
return value as unknown as KxmLocalBindingRecord;
}
function recordsEqual(left: KxmLocalBindingRecord, right: KxmLocalBindingRecord): boolean {
if (left.projectId !== right.projectId || !sameHostPath(left.projectRoot, right.projectRoot)) return false;
const leftEntries = Object.entries(left.repositories);
const rightEntries = Object.entries(right.repositories);
return leftEntries.length === rightEntries.length && leftEntries.every(([id, path], index) => {
const other = rightEntries[index];
return other?.[0] === id && sameHostPath(path, other[1]);
});
}
function syncDirectory(path: string): void {
let descriptor: number | undefined;
try {
descriptor = openSync(path, "r");
fsyncSync(descriptor);
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (process.platform !== "win32" && code !== "EINVAL" && code !== "ENOTSUP" && code !== "EISDIR") throw error;
} finally {
if (descriptor !== undefined) closeSync(descriptor);
}
}
function createDirectoryIfMissing(path: string): void {
if (existsSync(path)) return;
try {
mkdirSync(path, { mode: 0o700 });
syncDirectory(dirname(path));
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
}
}
function ensureDurableDirectory(path: string, description: string): void {
const missing: string[] = [];
let current = resolve(path);
while (!existsSync(current)) {
missing.push(current);
const parent = dirname(current);
if (parent === current) bindingError("path", "local_binding_directory_invalid", `${description} has no existing filesystem ancestor`);
current = parent;
}
checkedDirectory(current, `${description} ancestor`);
for (const directory of missing.reverse()) {
mkdirSync(directory, { mode: 0o700 });
checkedDirectory(directory, description);
syncDirectory(dirname(directory));
}
}
function ensureBindingDirectory(file: string, stateRoot: string): void {
ensureDurableDirectory(stateRoot, "local state root");
checkedDirectory(stateRoot, "local state root");
syncDirectory(stateRoot);
const projects = join(stateRoot, "projects");
createDirectoryIfMissing(projects);
checkedDirectory(projects, "local projects directory");
const project = dirname(file);
createDirectoryIfMissing(project);
checkedDirectory(project, "local project binding directory");
}
function assertNoLinkedDirectoryComponents(path: string, description: string): void {
const absolute = resolve(path);
const filesystemRoot = parse(absolute).root;
let current = filesystemRoot;
const remainder = relative(filesystemRoot, absolute);
for (const segment of remainder.split(sep).filter(Boolean)) {
current = join(current, segment);
const stat = lstatSync(current);
if (stat.isSymbolicLink() || !stat.isDirectory()) {
bindingError("path", "project_operation_lock_parent_invalid", `${description} contains a linked or non-directory component`);
}
}
}
function projectOperationLockFile(projectRoot: string): string {
const env = { ...process.env };
for (const key of Object.keys(env)) {
if (key.toLocaleUpperCase("en-US").startsWith("GIT_")) delete env[key];
}
const result = spawnSync("git", ["-C", projectRoot, "rev-parse", "--absolute-git-dir"], {
encoding: "utf8",
env,
timeout: 5000,
windowsHide: true,
});
if (result.status !== 0 || result.error || !result.stdout.trim()) {
bindingError("discovery", "project_operation_lock_unavailable", "cannot resolve the authoritative Git metadata path for project locking");
}
const gitDirectory = resolve(result.stdout.trim());
assertNoLinkedDirectoryComponents(gitDirectory, "authoritative Git metadata path");
const gitStat = lstatSync(gitDirectory);
if (gitStat.isSymbolicLink() || !gitStat.isDirectory()) {
bindingError("path", "project_operation_lock_parent_invalid", "authoritative Git metadata must resolve to a regular directory");
}
const lockDirectory = join(gitDirectory, "kxm");
if (existsSync(lockDirectory)) {
const stat = lstatSync(lockDirectory);
if (stat.isSymbolicLink() || !stat.isDirectory()) {
bindingError("path", "project_operation_lock_parent_invalid", "Git-local KXM lock directory must be regular, not a link or file");
}
} else {
mkdirSync(lockDirectory, { mode: 0o700 });
syncDirectory(gitDirectory);
}
return join(lockDirectory, "project-operation-lock.sqlite");
}
/** Serialize one project mutation with a process-death-released SQLite write lock. */
export function withKxmLocalBindingLock<T>(
projectRoot: string,
options: KxmLocalBindingStoreOptions,
callback: (lock: KxmLocalBindingLock) => T,
): T {
const root = resolve(projectRoot);
const file = projectOperationLockFile(root);
if (existsSync(file)) {
const stat = lstatSync(file);
if (stat.isSymbolicLink() || !stat.isFile()) {
bindingError("path", "local_binding_lock_invalid", "project-operation lock must be a regular file, not a link or directory");
}
}
const database = new DatabaseSync(file);
try { chmodSync(file, 0o600); } catch { /* Windows and restrictive filesystems may ignore POSIX modes. */ }
try {
try {
database.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE");
} catch (error) {
if (/busy|locked/i.test(error instanceof Error ? error.message : String(error))) {
bindingError("semantic", "local_binding_lock_busy", "another project initialization or binding update is active");
}
throw error;
}
const lock: KxmLocalBindingLock = Object.freeze({ projectRoot: canonicalHostPath(root), file });
activeLocks.add(lock);
try {
return callback(lock);
} finally {
activeLocks.delete(lock);
database.exec("ROLLBACK");
}
} finally {
database.close();
}
}
export function planKxmLocalBindings(
projectRoot: string,
projectId: string,
repositories: Readonly<Record<string, string>>,
options: KxmLocalBindingStoreOptions = {},
): KxmLocalBindingWriteResult {
const root = resolve(projectRoot);
const file = kxmLocalBindingFile(root, options);
const record = normalizedRecord(root, projectId, repositories, options.schemasDir);
const existing = readKxmLocalBindings(root, options);
if (existing?.projectId !== undefined && existing.projectId !== projectId) {
bindingError("semantic", "local_binding_project_id_mismatch", "binding record belongs to a different project identity");
}
return { file, written: !existing || !recordsEqual(existing, record), record };
}
/** Atomically persist validated host paths outside the Git-tracked project. */
export function writeKxmLocalBindings(
projectRoot: string,
projectId: string,
repositories: Readonly<Record<string, string>>,
options: KxmLocalBindingStoreOptions = {},
lock?: KxmLocalBindingLock,
): KxmLocalBindingWriteResult {
const root = resolve(projectRoot);
if (!lock) {
return withKxmLocalBindingLock(root, options, (acquired) => writeKxmLocalBindings(root, projectId, repositories, options, acquired));
}
if (!activeLocks.has(lock) || !sameHostPath(lock.projectRoot, root)) {
bindingError("semantic", "local_binding_lock_invalid", "binding update does not hold the active project lock");
}
const planned = planKxmLocalBindings(root, projectId, repositories, options);
const { file, record } = planned;
if (!planned.written) return planned;
ensureBindingDirectory(file, kxmUserStateRoot(options));
const temporary = join(dirname(file), `.repository-bindings-${process.pid}-${randomUUID()}.tmp`);
let descriptor: number | undefined;
try {
descriptor = openSync(temporary, "wx", 0o600);
writeFileSync(descriptor, `${JSON.stringify(record, null, 2)}\n`, "utf8");
fsyncSync(descriptor);
closeSync(descriptor);
descriptor = undefined;
renameSync(temporary, file);
syncDirectory(dirname(file));
} finally {
if (descriptor !== undefined) closeSync(descriptor);
rmSync(temporary, { force: true });
}
const verified = readKxmLocalBindings(root, options);
if (!verified || !recordsEqual(verified, record)) {
bindingError("semantic", "local_binding_install_verification_failed", "installed binding record does not match the validated input");
}
return { file, written: true, record };
}