"use strict";
/**
 * ProcessSandbox — Story 15.1 (v1.5, research-track).
 *
 * Thin wrapper around `child_process.spawn` that injects Node's
 * experimental `--permission` flag plus the per-capability `--allow-*`
 * flags so a child node process runs inside a kernel-style sandbox.
 *
 * **Not wired anywhere yet** — see `docs/sandboxing-research.md` for the
 * decision matrix. v1.5 ships this class so v1.6 can opt-in selectively
 * (doctor script first, then TaskExecutor.runShellCommand).
 *
 * Graceful version downgrade:
 *   • Node 20+ → injects `--permission` + the requested allow-list.
 *   • Node 18  → spawns plain (no enforcement) and stamps
 *     `result.enforced=false` so the caller can detect the downgrade.
 *
 * The wrapper does NOT execute anything by default. Callers must opt in
 * by calling `.spawn(cmd, args, opts)`. There is no global side effect
 * just by importing this module — keeping with OpenLife's lazy-import
 * contract in src/index.ts.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessSandbox = void 0;
const child_process = __importStar(require("child_process"));
const MIN_PERMISSION_MAJOR = 20;
class ProcessSandbox {
    opts;
    nodeMajor;
    spawnImpl;
    constructor(opts = {}) {
        this.opts = opts;
        this.nodeMajor = opts.nodeMajor ?? this.detectNodeMajor();
        this.spawnImpl = opts.spawnImpl ?? child_process.spawn;
    }
    /** True when the running Node version supports the `--permission` flag. */
    supportsEnforcement() {
        return this.nodeMajor >= MIN_PERMISSION_MAJOR;
    }
    /**
     * Build the flag list that `spawn` would prepend. Public for testability —
     * the unit tests inspect this without spawning anything.
     */
    buildPermissionFlags() {
        if (!this.supportsEnforcement())
            return [];
        const flags = ['--permission'];
        for (const p of this.opts.allowFsRead || [])
            flags.push(`--allow-fs-read=${p}`);
        for (const p of this.opts.allowFsWrite || [])
            flags.push(`--allow-fs-write=${p}`);
        if (this.opts.allowChildProcess)
            flags.push('--allow-child-process');
        if (this.opts.allowWorker)
            flags.push('--allow-worker');
        if (this.opts.allowWasi)
            flags.push('--allow-wasi');
        if (this.opts.allowAddons)
            flags.push('--allow-addons');
        return flags;
    }
    /**
     * Spawn `command` with `args`. When `command` is `node` (or absolute
     * path ending in `/node`), the permission flags are inserted before
     * the user's argv. Other commands are spawned plain — the wrapper is
     * a no-op for non-node binaries, since Node's permission model only
     * applies to spawning Node itself.
     *
     * The promise resolves with a result envelope; it never rejects.
     * stdout/stderr are captured up to a generous default buffer.
     */
    async spawn(command, args = [], spawnOpts = {}) {
        const isNode = /(?:^|\/)node(?:\.exe)?$/i.test(command) || command === 'node';
        const permissionFlags = isNode ? this.buildPermissionFlags() : [];
        const finalArgs = permissionFlags.length > 0 ? [...permissionFlags, ...args] : args;
        const enforced = isNode && permissionFlags.length > 0;
        return await new Promise((resolve) => {
            const child = this.spawnImpl(command, finalArgs, { ...spawnOpts, stdio: ['ignore', 'pipe', 'pipe'] });
            let stdout = '';
            let stderr = '';
            child.stdout?.on('data', (chunk) => { stdout += String(chunk); });
            child.stderr?.on('data', (chunk) => { stderr += String(chunk); });
            child.on('close', (code, signal) => {
                resolve({ code, signal, enforced, appliedFlags: permissionFlags, stdout, stderr });
            });
            child.on('error', () => {
                resolve({ code: 1, signal: null, enforced, appliedFlags: permissionFlags, stdout, stderr });
            });
        });
    }
    detectNodeMajor() {
        const m = process.versions.node.match(/^(\d+)/);
        return m ? Number(m[1]) : 0;
    }
}
exports.ProcessSandbox = ProcessSandbox;
