/**
 * HTTP admin routes for the compliance scorecard (Tier 3 of the agentic
 * eval framework).
 *
 * - `GET /admin/compliance/scorecard` — JSON contract identical to the
 *   CLI subcommand. AAuth-gated to hardware/software so anonymous public
 *   requests cannot scrape per-model data.
 * - `GET /admin/compliance/metrics` — Prometheus text format (gauge
 *   `neotoma_compliance_backfill_rate`). Gated additionally by the
 *   `NEOTOMA_COMPLIANCE_METRICS_ENABLED` env var because Prometheus
 *   scraping typically traverses internal networks rather than the
 *   AAuth-signed surface.
 *
 * The same module exports a tier guard so other compliance surfaces
 * (e.g. inspector hand-off endpoints) can reuse it.
 */
import { getAgentIdentityFromRequest } from "../../crypto/agent_identity.js";
import { logger } from "../../utils/logger.js";
import { getComplianceScorecard, resolveWindowBoundary, } from "./scorecard.js";
const METRICS_ENABLED_ENV = "NEOTOMA_COMPLIANCE_METRICS_ENABLED";
function enforceTier(req, res) {
    const identity = getAgentIdentityFromRequest(req);
    const tier = identity?.tier ?? "anonymous";
    if (tier === "hardware" || tier === "software")
        return true;
    res.status(403).json({
        error: "compliance_admin_forbidden",
        message: "Compliance admin endpoints require a hardware/software AAuth tier. Resolved tier: " +
            tier,
        tier,
    });
    return false;
}
function parseQuery(req) {
    try {
        const sinceRaw = typeof req.query.since === "string" ? req.query.since : "";
        const untilRaw = typeof req.query.until === "string" ? req.query.until : "";
        const since = sinceRaw ? resolveWindowBoundary(sinceRaw) : null;
        const until = untilRaw ? resolveWindowBoundary(untilRaw) : null;
        const groupByRaw = typeof req.query.group_by === "string"
            ? req.query.group_by
            : "model+harness";
        const minTurnsRaw = typeof req.query.min_turns === "string"
            ? parseInt(req.query.min_turns, 10) || 0
            : 0;
        const minRateRaw = typeof req.query.min_backfill_rate === "string"
            ? parseFloat(req.query.min_backfill_rate) || 0
            : 0;
        const topMissedRaw = typeof req.query.top_missed_steps === "string"
            ? parseInt(req.query.top_missed_steps, 10) || 10
            : 10;
        const includeSynthetic = typeof req.query.include_synthetic === "string" &&
            ["1", "true", "yes"].includes(req.query.include_synthetic.toLowerCase());
        const userId = typeof req.query.user_id === "string" ? req.query.user_id : null;
        return {
            since,
            until,
            group_by: groupByRaw,
            min_turns: minTurnsRaw,
            min_backfill_rate: minRateRaw,
            top_missed_steps: topMissedRaw,
            include_synthetic: includeSynthetic,
            user_id: userId,
        };
    }
    catch (err) {
        return { error: err.message };
    }
}
/**
 * Render Prometheus text format from a scorecard. Exposed so tests and
 * other surfaces (e.g. push gateways) can reuse the formatter.
 */
export function renderPrometheus(card) {
    const lines = [];
    lines.push("# HELP neotoma_compliance_backfill_rate Fraction of turns that hooks had to backfill (lower is better).");
    lines.push("# TYPE neotoma_compliance_backfill_rate gauge");
    for (const cell of card.cells) {
        const labels = `model="${escapeLabel(cell.model)}",harness="${escapeLabel(cell.harness)}",profile="${escapeLabel(cell.profile)}"`;
        lines.push(`neotoma_compliance_backfill_rate{${labels}} ${cell.backfill_rate.toFixed(6)}`);
    }
    lines.push("# HELP neotoma_compliance_total_turns Total turns observed in the window.");
    lines.push("# TYPE neotoma_compliance_total_turns gauge");
    for (const cell of card.cells) {
        const labels = `model="${escapeLabel(cell.model)}",harness="${escapeLabel(cell.harness)}",profile="${escapeLabel(cell.profile)}"`;
        lines.push(`neotoma_compliance_total_turns{${labels}} ${cell.total_turns}`);
    }
    lines.push("# HELP neotoma_compliance_backfilled_turns Turns where a hook had to backfill missed Neotoma writes.");
    lines.push("# TYPE neotoma_compliance_backfilled_turns gauge");
    for (const cell of card.cells) {
        const labels = `model="${escapeLabel(cell.model)}",harness="${escapeLabel(cell.harness)}",profile="${escapeLabel(cell.profile)}"`;
        lines.push(`neotoma_compliance_backfilled_turns{${labels}} ${cell.backfilled_turns}`);
    }
    return lines.join("\n") + "\n";
}
function escapeLabel(value) {
    return value
        .replace(/\\/g, "\\\\")
        .replace(/\n/g, "\\n")
        .replace(/"/g, '\\"');
}
export function isComplianceMetricsEnabled(env = process.env) {
    const raw = (env[METRICS_ENABLED_ENV] ?? "").toLowerCase().trim();
    return raw === "1" || raw === "true" || raw === "yes";
}
/**
 * Mount compliance admin routes onto the Neotoma HTTP server. Safe to
 * call once at server boot.
 *
 * `resolveUserId` is provided by the caller (typically the helper in
 * `src/actions.ts`) so this module does not pull in the auth subsystem
 * and can be unit-tested in isolation.
 */
export function registerComplianceRoutes(app, resolveUserId) {
    app.get("/admin/compliance/scorecard", async (req, res) => {
        if (!enforceTier(req, res))
            return;
        const parsed = parseQuery(req);
        if ("error" in parsed) {
            res
                .status(400)
                .json({ error: "compliance_admin_invalid_query", message: parsed.error });
            return;
        }
        let userId;
        try {
            userId = await resolveUserId(req, parsed.user_id ?? undefined);
        }
        catch (err) {
            res.status(401).json({
                error: "compliance_admin_unauthorized",
                message: err.message,
            });
            return;
        }
        try {
            const card = getComplianceScorecard({
                since: parsed.since,
                until: parsed.until,
                group_by: parsed.group_by,
                min_turns: parsed.min_turns,
                min_backfill_rate: parsed.min_backfill_rate,
                top_missed_steps: parsed.top_missed_steps,
                include_synthetic: parsed.include_synthetic,
                user_id: userId,
            });
            res.json(card);
        }
        catch (err) {
            logger.warn(`[compliance/admin] scorecard failed: ${err.message}`);
            res
                .status(500)
                .json({ error: "compliance_scorecard_failed", message: err.message });
        }
    });
    app.get("/admin/compliance/metrics", async (req, res) => {
        if (!isComplianceMetricsEnabled()) {
            res
                .status(404)
                .type("text/plain")
                .send(`# Prometheus metrics disabled. Set ${METRICS_ENABLED_ENV}=1 to enable.\n`);
            return;
        }
        if (!enforceTier(req, res))
            return;
        const parsed = parseQuery(req);
        if ("error" in parsed) {
            res
                .status(400)
                .json({ error: "compliance_admin_invalid_query", message: parsed.error });
            return;
        }
        let userId;
        try {
            userId = await resolveUserId(req, parsed.user_id ?? undefined);
        }
        catch (err) {
            res.status(401).json({
                error: "compliance_admin_unauthorized",
                message: err.message,
            });
            return;
        }
        try {
            const card = getComplianceScorecard({
                since: parsed.since,
                until: parsed.until,
                group_by: parsed.group_by,
                min_turns: parsed.min_turns,
                min_backfill_rate: parsed.min_backfill_rate,
                top_missed_steps: parsed.top_missed_steps,
                include_synthetic: parsed.include_synthetic,
                user_id: userId,
            });
            res.type("text/plain; version=0.0.4").send(renderPrometheus(card));
        }
        catch (err) {
            logger.warn(`[compliance/admin] metrics failed: ${err.message}`);
            res
                .status(500)
                .type("text/plain")
                .send(`# scorecard error: ${err.message}\n`);
        }
    });
}
export const COMPLIANCE_ROUTES_INTERNALS = {
    enforceTier,
    parseQuery,
    isComplianceMetricsEnabled,
    renderPrometheus,
};
//# sourceMappingURL=routes.js.map