/**
 * Deterministic structural post-checks (mmnto-ai/totem#2103, strategy#474 slice 4).
 *
 * Zero-LLM, caller-side checks over a finished run artifact. Each check is a
 * {@link PostCheckRule} that returns a verdict at a STATIC enforcement tier:
 *   - `decidable` — a structural fact that can be mechanically resolved (a cited
 *     file exists, a line is in range, output parses against a schema). These
 *     MAY gate: a decidable `fail` rejects the run.
 *   - `sensor` — a softer signal (claim support, summary faithfulness). These
 *     NEVER gate; they are telemetry only.
 *
 * The load-bearing invariant (ADR-109 — blurring decidable gates with soft
 * sensors trains users to bypass the gate): `isRejected` is true IFF some
 * `decidable` rule returned `fail`. A `sensor` finding can never set it — and
 * critically (mmnto-ai/totem#2103 codex review), a rule that THROWS is a fail at
 * the rule's OWN declared tier, never silently upgraded to decidable. A sensor
 * rule that throws must not reject the run.
 *
 * Verdict aggregation is a pure script, never an LLM (Tenet 9). This module is
 * in-memory only (no persisted shape) → plain TS interfaces, not Zod.
 */
import { getErrorMessage } from '../errors.js';
/**
 * Caller-identity aliases (mmnto-ai/totem#2103 codex review). Old review artifacts
 * carry `backend.taskProfile: 'Shield'` (the routing `TAG`), not `'Review'` (the
 * `DISPLAY_TAG`) — see `packages/cli/src/commands/shield-templates.ts`. A fallback
 * that accepted only `Spec`/`Review` would silently skip review rules on existing
 * review fixtures.
 */
const TASK_PROFILE_ALIASES = {
    Spec: 'spec',
    Review: 'review',
    Shield: 'review',
};
/**
 * The run's caller identity (`spec` / `review` / …) for caller-scoped rule
 * targeting. Prefers the explicit `runMetadata.caller` (mmnto-ai/totem#2102), falling
 * back to a `taskProfile` alias for slice-1/2 artifacts that predate it. An
 * UNKNOWN profile returns `undefined` — caller-scoped rules then abstain loudly,
 * never guess.
 */
export function resolveCaller(artifact) {
    const caller = artifact.admission?.runMetadata?.caller;
    // Lower-case the explicit caller too (CR review): an artifact carrying
    // `caller: 'Spec'` would otherwise be returned verbatim and silently skip every
    // caller-scoped gate (which compare against lower-case `'spec'`/`'review'`).
    if (caller !== undefined)
        return caller.toLowerCase();
    return TASK_PROFILE_ALIASES[artifact.backend.taskProfile];
}
/**
 * Run every applicable rule and aggregate. Pure, deterministic, zero-LLM.
 *
 * A rule that THROWS yields a `fail` at the rule's OWN declared tier (a sensor
 * throw is a sensor fail — it must not reject; mmnto-ai/totem#2103 codex review).
 * Only a genuine engine bug (not a rule fault) escapes this loop as an
 * exception — engine-integrity faults are never laundered into a finding.
 */
export async function evaluatePostChecks(artifact, rules, ctx) {
    const findings = [];
    for (const rule of rules) {
        if (!rule.appliesTo(artifact))
            continue;
        let result;
        try {
            result = await rule.evaluate(artifact, ctx);
            // totem-context: fail-loud — a rule throw becomes a tier-preserving 'fail' finding (OQ4).
        }
        catch (err) {
            result = {
                verdict: 'fail',
                message: `rule "${rule.name}" threw: ${getErrorMessage(err)}`,
            };
        }
        findings.push({ ruleName: rule.name, tier: rule.tier, ...result });
    }
    const isRejected = findings.some((f) => f.tier === 'decidable' && f.verdict === 'fail');
    return { findings, isRejected };
}
//# sourceMappingURL=post-checks.js.map