/**
 * @internal
 * G4 verify-approved — blocking gate for `apt:ship`.
 *
 * Requires {task_dir}/qa_signoff.json to exist, parse, and carry
 * verdict === "approved" (BUG-013: normalized via parseQaSignoff — case
 * and underscore/hyphen variants collapse to canonical). BUG-012: also
 * accepts `approved-with-notes` when config.qa.approved_with_notes_threshold
 * is set AND every dimension score meets the threshold (best-effort when
 * dimensions are missing or in an unknown shape).
 *
 * Accepts both `verdict` (newer) and `status` (older) field names — that
 * normalization is delegated to parseQaSignoff.
 */
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { parseQaSignoff } from '../../consistency/parse-qa.mjs';
import { registerGate } from '../registry.mjs';
/**
 * Extract dimension scores from the qa_signoff.json shape. We've seen two
 * shapes in the wild:
 *   - Array: `dimensions: [{name: 'security', score: 0.9}, ...]`
 *   - Object: `dimensions: {security: 0.9, performance: 0.85}` OR
 *     `dimensions: {security: {score: 0.9}, ...}`
 * Returns an array of numeric scores, or null when no recognizable shape
 * is present (best-effort accept per brief).
 */
function extractDimensionScores(doc) {
    const dims = doc.dimensions ?? doc.qa?.dimensions ?? null;
    if (!dims)
        return null;
    if (Array.isArray(dims)) {
        const scores = dims
            .map((d) => (typeof d?.score === 'number' ? d.score : null))
            .filter((s) => s !== null);
        return scores.length > 0 ? scores : null;
    }
    if (typeof dims === 'object') {
        const scores = [];
        for (const v of Object.values(dims)) {
            if (typeof v === 'number')
                scores.push(v);
            else if (typeof v?.score === 'number')
                scores.push(v.score);
        }
        return scores.length > 0 ? scores : null;
    }
    return null;
}
const gate = {
    id: 'verify-approved',
    description: 'qa_signoff.json exists with verdict === "approved" (or "approved-with-notes" when configured)',
    severity: 'blocking',
    skills: ['ship'],
    applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
    async check(ctx) {
        const path = join(ctx.taskDir, 'qa_signoff.json');
        const inputs = [path];
        if (!existsSync(path)) {
            return {
                status: 'fail',
                reason: `qa_signoff.json missing at ${path}`,
                inputs,
            };
        }
        let doc;
        try {
            doc = JSON.parse(readFileSync(path, 'utf-8'));
        }
        catch (err) {
            return {
                status: 'fail',
                reason: `qa_signoff.json does not parse: ${err.message}`,
                inputs,
            };
        }
        // BUG-013: route through parseQaSignoff so the verdict is
        // case-insensitive AND tolerates `_↔-` (so 'APPROVED', 'Approved',
        // ' approved ', 'APPROVED_WITH_NOTES' all behave canonically). The
        // pre-fix gate compared the raw string against `'approved'` which
        // false-rejected every legitimate variant.
        const { verdict } = parseQaSignoff(doc);
        if (verdict === 'approved') {
            return {
                status: 'pass',
                reason: 'qa_signoff.json verdict normalized to "approved"',
                inputs,
            };
        }
        // BUG-012: accept `approved-with-notes` when the project has opted
        // in via config.qa.approved_with_notes_threshold. The threshold is
        // the minimum per-dimension score required. Best-effort: when
        // dimensions are missing / in an unknown shape we still accept and
        // log, rather than hard-failing, so projects that haven't started
        // scoring dimensions yet aren't blocked.
        if (verdict === 'approved-with-notes') {
            const threshold = ctx.config?.qa?.approved_with_notes_threshold;
            if (typeof threshold !== 'number') {
                return {
                    status: 'fail',
                    reason: 'qa_signoff.json verdict is "approved-with-notes" but config.qa.approved_with_notes_threshold is not set',
                    evidence: { verdict, threshold: threshold ?? null },
                    inputs,
                };
            }
            const scores = extractDimensionScores(doc);
            if (scores === null) {
                return {
                    status: 'pass',
                    reason: `qa_signoff.json verdict is "approved-with-notes" and threshold ${threshold} is enabled; dimensions missing or unknown-shape — accepting best-effort`,
                    evidence: { verdict, threshold, dimensions_present: false },
                    inputs,
                };
            }
            const failing = scores.filter((s) => s < threshold);
            if (failing.length > 0) {
                return {
                    status: 'fail',
                    reason: `qa_signoff.json verdict is "approved-with-notes" but ${failing.length}/${scores.length} dimension score(s) below threshold ${threshold}`,
                    evidence: { verdict, threshold, scores, failing },
                    inputs,
                };
            }
            return {
                status: 'pass',
                reason: `qa_signoff.json verdict is "approved-with-notes" and all ${scores.length} dimension score(s) meet threshold ${threshold}`,
                evidence: { verdict, threshold, scores },
                inputs,
            };
        }
        return {
            status: 'fail',
            reason: `qa_signoff.json verdict is "${verdict}" (expected "approved" or "approved-with-notes" with threshold)`,
            evidence: { verdict },
            inputs,
        };
    },
};
registerGate(gate);
export default gate;
//# sourceMappingURL=verify-approved.mjs.map