"use strict";
/**
 * preflight.ts
 *
 * Boot each detected agent in dry-run mode against the running governance-sidecar.
 * For each agent: fires a PreToolUse-equivalent check-action call, asserts that
 * an audit chain entry lands, and returns per-agent status.
 *
 * Pure async function -- no process.exit, no stdout writes.
 *
 * F-NEW-PRODUCT-AGENT-DIR-WRAPPER slice 4
 * @connexum/ai-governance
 */
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.preflightAgents = preflightAgents;
const http = __importStar(require("node:http"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
// ---------------------------------------------------------------------------
// Providers with a governed wrapper in this SDK version
// ---------------------------------------------------------------------------
const SUPPORTED_PROVIDERS = new Set([
    'openai',
    'anthropic',
    'google',
    'langchain',
    'crewai',
    'autogen',
    'langgraph',
    'pydantic_ai',
    'haystack',
    'semantic_kernel',
    'llama_index',
    'replicate',
    'bedrock',
    'azure_openai',
]);
// ---------------------------------------------------------------------------
// HTTP helper (no external dependencies)
// ---------------------------------------------------------------------------
function httpRequest(url, method, body, headers, timeoutMs = 8_000) {
    return new Promise((resolve, reject) => {
        const parsed = new URL(url);
        const bodyStr = body !== undefined ? JSON.stringify(body) : undefined;
        const reqHeaders = {
            'Content-Type': 'application/json',
            ...headers,
        };
        if (bodyStr !== undefined) {
            reqHeaders['Content-Length'] = String(Buffer.byteLength(bodyStr));
        }
        const opts = {
            hostname: parsed.hostname,
            port: parsed.port ? parseInt(parsed.port, 10) : 80,
            path: parsed.pathname + (parsed.search ?? ''),
            method,
            headers: reqHeaders,
        };
        const req = http.request(opts, (res) => {
            const chunks = [];
            res.on('data', (c) => chunks.push(c));
            res.on('end', () => {
                const raw = Buffer.concat(chunks).toString('utf-8');
                let parsed2 = {};
                try {
                    parsed2 = JSON.parse(raw);
                }
                catch { /* non-JSON body */ }
                resolve({ status: res.statusCode ?? 0, body: parsed2, raw });
            });
        });
        req.setTimeout(timeoutMs, () => {
            req.destroy(new Error(`Request timed out after ${timeoutMs}ms`));
        });
        req.on('error', reject);
        if (bodyStr !== undefined)
            req.write(bodyStr);
        req.end();
    });
}
// ---------------------------------------------------------------------------
// Sidecar reachability check
// ---------------------------------------------------------------------------
async function isSidecarReachable(sidecarUrl) {
    // Accept both 4200 (public) and 4201 (internal) -- try /api/v1/health on
    // whichever port/path is provided.
    try {
        const healthUrl = sidecarUrl.replace(/\/$/, '') + '/api/v1/health';
        const res = await httpRequest(healthUrl, 'GET', undefined, undefined, 3_000);
        return res.status >= 200 && res.status < 500;
    }
    catch {
        return false;
    }
}
// ---------------------------------------------------------------------------
// Mint a preflight auth token via the sidecar's /auth/register endpoint.
// Used when no external authToken is provided.
// Returns the accessToken string, or null on failure.
// ---------------------------------------------------------------------------
async function mintPreflightToken(sidecarUrl, orgId) {
    const base = sidecarUrl.replace(/\/$/, '');
    try {
        const res = await httpRequest(`${base}/api/v1/auth/register`, 'POST', {
            email: `preflight-${orgId}@connexum-preflight.internal`,
            password: 'PreflightPass1!',
            orgId,
        }, {}, 5_000);
        if (res.status === 200 || res.status === 201) {
            return res.body.accessToken ?? null;
        }
        // 409 = already registered — try login
        if (res.status === 409) {
            const login = await httpRequest(`${base}/api/v1/auth/login`, 'POST', {
                email: `preflight-${orgId}@connexum-preflight.internal`,
                password: 'PreflightPass1!',
            }, {}, 5_000);
            return login.body.accessToken ?? null;
        }
    }
    catch {
        // swallow -- caller handles null
    }
    return null;
}
// ---------------------------------------------------------------------------
// Resolve the shim path for an agent (if it exists)
// ---------------------------------------------------------------------------
function shimExists(agent, agentDir) {
    const ext = path.extname(agent.filePath);
    const base = agent.filePath.replace(new RegExp(`\\${ext}$`), '');
    const shimPy = path.join(agentDir, `${base}.governed.py`);
    const shimTs = path.join(agentDir, `${base}.governed.ts`);
    const shimJs = path.join(agentDir, `${base}.governed.js`);
    return fs.existsSync(shimPy) || fs.existsSync(shimTs) || fs.existsSync(shimJs);
}
async function firePreflight(sidecarUrl, agent, orgId, authToken) {
    const base = sidecarUrl.replace(/\/$/, '');
    // Fire a dry-run PreToolUse equivalent against /governance/check-action
    const checkRes = await httpRequest(`${base}/api/v1/governance/check-action`, 'POST', {
        toolName: 'preflight-dry-run',
        input: {
            preflight: true,
            agentId: agent.agentId,
            provider: agent.provider,
            framework: agent.framework,
            filePath: agent.filePath,
            dryRun: true,
        },
        agent: {
            name: agent.agentId,
            orgId,
        },
        userId: 'preflight-system',
        sessionId: `preflight-${agent.agentId}-${Date.now()}`,
    }, { Authorization: `Bearer ${authToken}` }, 8_000);
    const decision = checkRes.body.decision ?? checkRes.body.action ?? 'unknown';
    const auditEntryId = checkRes.body.auditId ?? null;
    return { decision, auditEntryId };
}
// ---------------------------------------------------------------------------
// Verify audit chain entry landed
//
// The governance-server appends to the audit chain in a non-blocking async
// IIFE (fire-and-forget). The HTTP response for check-action returns before
// the chain write completes. We poll with short backoff to give it time.
// ---------------------------------------------------------------------------
const CHAIN_POLL_ATTEMPTS = 6;
const CHAIN_POLL_DELAY_MS = 120; // 6 x 120ms = up to ~720ms wait
function delay(ms) {
    return new Promise((r) => setTimeout(r, ms));
}
async function verifyAuditEntry(sidecarUrl, orgId, authToken, expectedAuditId, agentId) {
    const base = sidecarUrl.replace(/\/$/, '');
    for (let attempt = 0; attempt < CHAIN_POLL_ATTEMPTS; attempt++) {
        if (attempt > 0)
            await delay(CHAIN_POLL_DELAY_MS);
        // The audit-chain response format: each entry is AuditChainEntry { id, orgId, entryData, ... }
        // The server's check-action response returns `auditId` which equals `entryData.auditId`,
        // NOT `AuditChainEntry.id` (which is a separate crypto.randomUUID()).
        // So we always scan `entryData` fields for matching.
        try {
            const chainRes = await httpRequest(`${base}/api/v1/governance/audit-chain?limit=100`, 'GET', undefined, { Authorization: `Bearer ${authToken}` }, 5_000);
            if (chainRes.status !== 200)
                continue;
            const chain = chainRes.body.chain ?? [];
            const found = chain.some((e) => {
                const ed = e.entryData ?? {};
                // Match by auditId in entryData (primary)
                if (expectedAuditId && ed.auditId === expectedAuditId) {
                    return true;
                }
                // Match by toolName = preflight-dry-run OR agentName contains our agentId
                if (ed.toolName === 'preflight-dry-run')
                    return true;
                if (ed.agentName?.includes(agentId))
                    return true;
                return false;
            });
            if (found)
                return true;
        }
        catch {
            // retry on next attempt
        }
    }
    return false;
}
// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------
/**
 * Run a preflight check for every detected agent against the governance-sidecar.
 *
 * For each agent:
 *   1. Checks the sidecar is reachable.
 *   2. Skips unsupported providers (status: 'skipped').
 *   3. Verifies the .governed shim exists (status: 'error' if missing).
 *   4. Fires a dry-run PreToolUse check-action.
 *   5. Asserts an audit chain entry lands.
 *   6. Returns status 'ok', 'denied', or 'error'.
 *
 * Never throws -- all errors are captured in the result.
 *
 * @param agents    Detected agents from scanAgentDirectory()
 * @param agentDir  Absolute path to the agent directory (for shim resolution)
 * @param opts      Preflight options
 */
async function preflightAgents(agents, agentDir, opts) {
    const orgId = opts.orgId ?? 'preflight-org';
    const skipUnsupported = opts.skipUnsupported !== false;
    // Early-exit if no agents
    if (agents.length === 0)
        return [];
    // 1. Reachability check (one call for the whole batch)
    const reachable = await isSidecarReachable(opts.sidecarUrl);
    if (!reachable) {
        return agents.map((a) => ({
            agentId: a.agentId,
            filePath: a.filePath,
            provider: a.provider,
            framework: a.framework,
            status: 'pending',
            detail: `Sidecar not reachable at ${opts.sidecarUrl} — preflight skipped. Start the sidecar or use --skip-preflight.`,
            checkedAt: new Date().toISOString(),
            auditEntryId: null,
        }));
    }
    // 2. Obtain auth token
    const authToken = opts.authToken ?? (await mintPreflightToken(opts.sidecarUrl, orgId));
    if (!authToken) {
        return agents.map((a) => ({
            agentId: a.agentId,
            filePath: a.filePath,
            provider: a.provider,
            framework: a.framework,
            status: 'error',
            detail: 'Could not obtain auth token from sidecar. Check sidecar logs.',
            checkedAt: new Date().toISOString(),
            auditEntryId: null,
        }));
    }
    // 3. Run checks in parallel (bounded by Promise.all -- no artificial cap
    //    because sidecar is local and 30 concurrent HTTP calls are fine)
    const results = await Promise.all(agents.map(async (agent) => {
        const now = new Date().toISOString();
        // Unsupported provider
        const effectiveProvider = agent.framework ?? agent.provider;
        if (skipUnsupported &&
            effectiveProvider !== null &&
            !SUPPORTED_PROVIDERS.has(effectiveProvider)) {
            return {
                agentId: agent.agentId,
                filePath: agent.filePath,
                provider: agent.provider,
                framework: agent.framework,
                status: 'skipped',
                detail: `Provider '${effectiveProvider}' has no governed wrapper yet. Skipping preflight.`,
                checkedAt: now,
                auditEntryId: null,
            };
        }
        // Shim existence check
        if (!shimExists(agent, agentDir)) {
            return {
                agentId: agent.agentId,
                filePath: agent.filePath,
                provider: agent.provider,
                framework: agent.framework,
                status: 'error',
                detail: `Governed shim not found next to ${agent.filePath}. Run 'ai-governance init --agent-dir <path>' to generate it.`,
                checkedAt: now,
                auditEntryId: null,
            };
        }
        // Fire the check-action and verify audit chain
        try {
            const check = await firePreflight(opts.sidecarUrl, agent, orgId, authToken);
            const auditLanded = await verifyAuditEntry(opts.sidecarUrl, orgId, authToken, check.auditEntryId, agent.agentId);
            const decisionUpper = (check.decision ?? '').toUpperCase();
            const isDenied = decisionUpper === 'DENY' || decisionUpper === 'DENIED' || decisionUpper === 'BLOCK';
            if (!auditLanded) {
                return {
                    agentId: agent.agentId,
                    filePath: agent.filePath,
                    provider: agent.provider,
                    framework: agent.framework,
                    status: 'error',
                    detail: `Governance hook fired but audit chain entry was NOT confirmed. Check sidecar storage adapter.`,
                    checkedAt: now,
                    auditEntryId: check.auditEntryId,
                };
            }
            return {
                agentId: agent.agentId,
                filePath: agent.filePath,
                provider: agent.provider,
                framework: agent.framework,
                status: isDenied ? 'denied' : 'ok',
                detail: isDenied
                    ? `Pack policy DENIED tool 'preflight-dry-run' for this agent. Agent IS governed — rule blocked the action.`
                    : `Governance hooks fired. Audit chain entry confirmed. Pack policy applied.`,
                checkedAt: now,
                auditEntryId: check.auditEntryId,
            };
        }
        catch (err) {
            return {
                agentId: agent.agentId,
                filePath: agent.filePath,
                provider: agent.provider,
                framework: agent.framework,
                status: 'error',
                detail: `Preflight threw: ${err.message}`,
                checkedAt: now,
                auditEntryId: null,
            };
        }
    }));
    return results;
}
//# sourceMappingURL=preflight.js.map