/**
 * route/drift-detect.mjs — FRAMEWORK-BUG-017 (rung 1 of 3).
 *
 * In-band detection of the "installed kernel drifts behind source
 * workspace" failure mode. When an Aperant contributor self-hosts the
 * framework out of a monorepo checkout, the kernel at
 * `.aperant/deps/node_modules/@aperant/framework/` lags behind the
 * source at `packages/framework/` after every same-PR framework fix.
 * The SessionStart worker only knows about npm — it cannot tell that
 * the workspace is ahead of the install.
 *
 * This module walks up from `targetDir`, looking for a sibling
 * `packages/framework/package.json`. When found AND its `version`
 * differs from the installed kernel's `version`, it returns a synthetic
 * `update_check`-shaped object so `route.mjs` can surface the chip
 * immediately — without waiting for the SessionStart hook to fire.
 *
 * Safe for the route hot path: synchronous, file-existence checks
 * only, bounded walk (6 segments max), no network, no spawn.
 *
 * Security (Codex review edit 2): the workspace `package.json:version`
 * is validated against the same semver-shape regex `update-chips.mjs`
 * uses on the cache. A poisoned workspace `package.json` cannot
 * propagate a non-semver `latest_version` into the synthesized
 * `update_check` block.
 */
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { compareVersions, SEMVER_RE } from '../util/semver.mjs';
/** Max directory frames to walk before giving up. */
const MAX_WALK_DEPTH = 6;
/**
 * Walk up from `start`, returning every directory frame including
 * `start` itself. Stops at the filesystem root or at a depth of
 * `MAX_WALK_DEPTH` (whichever comes first). The hard cap keeps this
 * cheap on the route hot path and contains accidental scans on deeply
 * nested input paths.
 *
 * @param {string} start
 * @returns {string[]}
 */
function walkUp(start) {
    const frames = [];
    let cur = start;
    for (let i = 0; i < MAX_WALK_DEPTH; i++) {
        frames.push(cur);
        const parent = dirname(cur);
        if (parent === cur)
            break;
        cur = parent;
    }
    return frames;
}
/**
 * Detect whether the workspace source for `@aperant/framework` is ahead
 * of the installed kernel.
 *
 * @param {string} targetDir  Absolute path the route was invoked against.
 * @param {string|null} installedVersion  The installed kernel's version (semver string), or null when unknown.
 * @returns {null | {
 *   source_path: string,
 *   source_version: string,
 *   installed_version: string,
 *   drift: true,
 * }}
 */
export function detectSourceWorkspaceDrift(targetDir, installedVersion) {
    if (!targetDir || typeof targetDir !== 'string')
        return null;
    if (!installedVersion || typeof installedVersion !== 'string')
        return null;
    if (!SEMVER_RE.test(installedVersion))
        return null;
    for (const dir of walkUp(targetDir)) {
        const candidate = join(dir, 'packages', 'framework', 'package.json');
        if (!existsSync(candidate))
            continue;
        let pkg;
        try {
            pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
        }
        catch {
            return null;
        }
        // Validate the workspace package is actually @aperant/framework —
        // guard against false matches on unrelated monorepos that happen
        // to have a `packages/framework/` directory.
        if (pkg?.name !== '@aperant/framework')
            continue;
        const sourceVersion = pkg?.version;
        if (typeof sourceVersion !== 'string' || !SEMVER_RE.test(sourceVersion))
            return null;
        // Only report drift when workspace source is strictly ahead of the installed
        // kernel. Equal (already up-to-date) and behind (installed is newer) both
        // return null — only source-ahead is a drift worth surfacing.
        if (compareVersions(sourceVersion, installedVersion) <= 0)
            return null;
        return {
            source_path: candidate,
            source_version: sourceVersion,
            installed_version: installedVersion,
            drift: true,
        };
    }
    return null;
}
//# sourceMappingURL=drift-detect.mjs.map