/**
 * The joins between one project's declarations and the world outside it.
 *
 * Three of them, and each is a place two sources of truth are compared for the
 * first time: the ruleset against the declaration (#2854's finding — nothing
 * compares these today, so they are only ever equal by hand), every required
 * context against who owns it, and one gate's implied context against the
 * moment a ruleset can actually guard.
 *
 * Kept out of the report assembly because the assembly is a list of steps and
 * these are the reasoning. Every one of them carries an unknown through rather
 * than resolving it: a run that could not read branch protection has nothing to
 * compare, and an empty comparison would report agreement.
 * @module cli/gate-report-joins
 */
import { classifyDeclarationDrift, contextOwners, } from "../core/gate-declaration-drift.js";
import { classifyRequiredContexts, lisaContextUniverse, } from "./gate-report-contexts.js";
import { compareContexts } from "./gate-report-ruleset.js";
/**
 * The contexts a gates block implies at the merge moment.
 * @param context - The join inputs
 * @returns The contexts, or null when the block cannot be resolved
 */
function declaredContexts(context) {
    try {
        return context.registry.contextsFor(context.gates, {
            moment: context.mergeMoment,
            workflowName: context.workflowName,
        });
    }
    catch {
        return null;
    }
}
/**
 * The Tier 2 answer for one cell, never defaulted to a verdict.
 * @param context - The join inputs
 * @returns A resolver for one cell's expected context
 */
export function mergeBlockResolver(context) {
    return (moment, expectedContext) => {
        if (moment !== context.mergeMoment) {
            return {
                state: "not-applicable",
                reason: "moment-produces-no-merge-context",
                message: `A ruleset guards a merge, and only the ${context.mergeMoment} gate set produces the status contexts it names. A declaration at ${moment} is enforced by a hook, not by branch protection.`,
            };
        }
        if (expectedContext === null) {
            return {
                state: "not-applicable",
                reason: "no-required-declaration",
                message: "Only a `required` declaration produces a status context, so there is nothing here for a ruleset to require.",
            };
        }
        if (context.contexts.state !== "verified")
            return context.contexts;
        const required = context.contexts.value.includes(expectedContext);
        return {
            state: "verified",
            value: { required, context: required ? expectedContext : null },
        };
    };
}
/**
 * The ruleset comparison, or the same unknown the contexts came back with.
 * @param context - The join inputs
 * @returns The comparison
 */
export function buildRulesetFinding(context) {
    if (context.contexts.state !== "verified")
        return context.contexts;
    const declared = declaredContexts(context);
    if (declared === null) {
        return {
            state: "unknown",
            reason: "declarations-unresolvable",
            message: "The gates block could not be resolved, so the contexts it implies cannot be compared with the ruleset.",
        };
    }
    return {
        state: "verified",
        value: compareContexts(declared, context.contexts.value),
    };
}
/**
 * Every context a merge is blocked on, with who owns it.
 * @param context - The join inputs
 * @returns One row per required context, or the same unknown
 */
export function buildRequiredContexts(context) {
    return classifyRequiredContexts(context.contexts, {
        declared: declaredContexts(context) ?? [],
        lisaUniverse: lisaContextUniverse({
            gateIds: Object.entries(context.registry.REGISTRY)
                .filter(([, gate]) => gate.moments.includes(context.mergeMoment))
                .map(([id]) => id),
            moment: context.mergeMoment,
            workflowName: context.workflowName,
            contextsFor: context.registry.contextsFor,
        }),
        projectContexts: context.facade.projectContexts,
    });
}
/**
 * Hold the declaration against one enforcing surface.
 *
 * The fourth join, and the one #2854 filed: `buildRulesetFinding` above says
 * WHICH contexts differ, and `buildRequiredContexts` says WHO owns each live
 * one, but neither says what the difference MEANS for the declaration that was
 * supposed to govern it. A gate declared `off` and a gate never declared at all
 * both show up as "required, not declared" in a set comparison, and they are
 * opposite problems: one is a contradiction, the other is silence.
 *
 * The surface's own `unknown` is returned unchanged rather than replaced with a
 * comparison of what little was read. A run that could not reach a surface has
 * not compared anything, and saying so is the whole contract.
 * @param context - The join inputs
 * @param surface - Which surface `enforced` came from
 * @param enforced - What the surface requires, or why it is unknown
 * @returns The comparison, or the surface's unknown
 */
export function buildDeclarationDrift(context, surface, enforced) {
    if (enforced.state !== "verified")
        return enforced;
    return {
        state: "verified",
        value: classifyDeclarationDrift({
            surface,
            owners: contextOwners({
                registry: context.registry,
                gates: context.gates,
                workflowName: context.workflowName,
            }),
            enforced: enforced.value,
        }),
    };
}
/**
 * The live required contexts, restated as attributed enforcement.
 *
 * The live reader answers with bare strings because that is all branch
 * protection gives it; the comparison wants to say WHERE a requirement was
 * read, so the attribution is added here rather than invented downstream.
 * @param contexts - Contexts read from the live ruleset, or an unknown
 * @returns The same finding, in the comparison's shape
 */
export function liveEnforcement(contexts) {
    if (contexts.state !== "verified")
        return contexts;
    return {
        state: "verified",
        value: contexts.value.map(context => ({
            context,
            ruleset: "the branch's live rules",
            source: "the repository's live branch protection",
        })),
    };
}
//# sourceMappingURL=gate-report-joins.js.map