/**
 * ASI-09 (OWASP Agentic Top 10) — Human-Agent Trust: oversight-effectiveness scoring.
 *
 * Microsoft AGT parity for the human-oversight gap (and Above-the-Floor Module 5).
 * EU AI Act Art. 14: "presence is not practice." An approval GATE proves a human
 * was present; it does NOT prove they exercised judgment. This read-model scores
 * whether a reviewer actually reviewed — time-to-decision, whether they
 * overrode/edited the agent's proposal, whether they recorded a rationale, and
 * their approval rate — and flags RUBBER-STAMPING (instant near-100% approvals
 * with no edits and no rationale).
 *
 * Process-quality metric, not individual punishment. Pure compute; never throws.
 *
 * @connexum/ai-governance
 */
const DEFAULTS = { rubberStampMaxMs: 3000, rubberStampMinApprovalRate: 0.97 };
function median(nums) {
    if (nums.length === 0)
        return 0;
    const s = [...nums].sort((a, b) => a - b);
    const mid = Math.floor(s.length / 2);
    return s.length % 2 === 0 ? (s[mid - 1] + s[mid]) / 2 : s[mid];
}
function clamp(v) {
    return Math.max(0, Math.min(100, v));
}
/**
 * Score human-oversight effectiveness per reviewer from a window of review
 * events. NEVER throws — bad/empty input returns [] or default-filled rows.
 */
export function scoreOversight(events, config) {
    if (!Array.isArray(events) || events.length === 0)
        return [];
    const cfg = { ...DEFAULTS, ...(config ?? {}) };
    const byReviewer = new Map();
    for (const e of events) {
        if (!e || typeof e !== 'object')
            continue;
        const id = typeof e.reviewerId === 'string' && e.reviewerId ? e.reviewerId : 'unknown';
        const list = byReviewer.get(id) ?? [];
        list.push(e);
        byReviewer.set(id, list);
    }
    const out = [];
    for (const [reviewerId, list] of byReviewer) {
        const n = list.length;
        const approvals = list.filter((e) => e.decision === 'approved').length;
        const overrides = list.filter((e) => e.changedAgentProposal === true).length;
        const rationales = list.filter((e) => e.rationaleProvided === true).length;
        const times = list.map((e) => (typeof e.timeToDecisionMs === 'number' && e.timeToDecisionMs >= 0 ? e.timeToDecisionMs : 0));
        const approvalRate = n ? approvals / n : 0;
        const overrideRate = n ? overrides / n : 0;
        const rationaleRate = n ? rationales / n : 0;
        const medianTimeToDecisionMs = median(times);
        const tooFast = medianTimeToDecisionMs <= cfg.rubberStampMaxMs;
        const rubberStamp = n >= 1 &&
            tooFast &&
            approvalRate >= cfg.rubberStampMinApprovalRate &&
            overrideRate < 0.05 &&
            rationaleRate < 0.5;
        const flags = [];
        if (tooFast)
            flags.push(`median decision ${Math.round(medianTimeToDecisionMs)}ms <= ${cfg.rubberStampMaxMs}ms (too fast)`);
        if (approvalRate >= cfg.rubberStampMinApprovalRate)
            flags.push(`approval rate ${(approvalRate * 100).toFixed(0)}% (near-unanimous)`);
        if (overrideRate < 0.05)
            flags.push('almost never overrides the agent proposal');
        if (rationaleRate < 0.5)
            flags.push('rationale missing on most reviews');
        if (rubberStamp)
            flags.unshift('RUBBER-STAMP: presence without practice (EU AI Act Art. 14 risk)');
        // Effectiveness: reward rationale, override (judgment), thoughtful time, and
        // decision diversity (not 100% approval). Rubber-stamp caps the score.
        const medianSec = medianTimeToDecisionMs / 1000;
        const timeScore = medianSec >= 30 ? 25 : medianSec >= 10 ? 18 : medianSec >= 3 ? 8 : 0;
        let score = rationaleRate * 30 +
            Math.min(overrideRate, 0.5) / 0.5 * 25 +
            timeScore +
            (1 - approvalRate) * 20;
        if (rubberStamp)
            score = Math.min(score, 25);
        out.push({
            reviewerId,
            reviewCount: n,
            approvalRate,
            medianTimeToDecisionMs,
            overrideRate,
            rationaleRate,
            effectivenessScore: Math.round(clamp(score)),
            rubberStamp,
            flags,
        });
    }
    return out;
}
//# sourceMappingURL=oversight-effectiveness.js.map