tools/core/runners/gate.runner.ts

tools/core/runners/gate.runner.ts is a file in Coordination Surface. 145 lines of code and 27 definitions.

import { CLEAR_STATES, countIn, movedOf, verdictKey, worstVerdicts } from "../comparators/gate.comparator.ts";
import { judge, unsuppliedReads } from "../validators/gate.validator.ts";
import type { GateFixture } from "../types/fixture.types.ts";
import type { GateOutcome } from "../types/gate.types.ts";
import { NO_FIXTURE_PAIR } from "../strings/gate.strings.ts";
import { discoverFixtures } from "../registries/fixture.registry.ts";
import { discoverRules } from "../registries/rule.registry.ts";
import { judgeBranch } from "../inspectors/gate.inspector.ts";
import { priorKindVerdicts } from "../readers/gate.reader.ts";

interface RunnerReport {
    readonly outcomes: readonly GateOutcome[];
    readonly branches: readonly GateOutcome[];
    readonly proven: number;
    readonly exempt: number;
    readonly untested: number;
    readonly failed: number;
    readonly branchesProven: number;
    readonly branchesOpen: number;
    readonly unfixturedKinds: readonly string[];
    readonly kindVerdicts: Readonly<Record<string, string>>;
    readonly movedVerdicts: { readonly kinds: readonly string[]; readonly bound: string };
    readonly treeLoadedReads: { readonly fixtures: readonly string[]; readonly bound: string };
    readonly kindScope: { readonly declaredKinds: number; readonly rules: number; readonly bound: string };
}

type Registered = Awaited<ReturnType<typeof discoverRules>>["rules"][number];

interface RuleJudgment {
    readonly outcome: GateOutcome;
    readonly exposed: string[];
    readonly verdicts: [string, GateOutcome["state"]][];
}

const UNTESTED: Omit<GateOutcome, "rule"> = { detail: NO_FIXTURE_PAIR, state: "untested" };

const judgeRule = function judgeRule(
    registered: Registered,
    held: readonly GateFixture[],
    repoRoot: string,
): RuleJudgment {
    if (held.length === 0) {
        return { exposed: [], outcome: { ...UNTESTED, rule: registered.id }, verdicts: [] };
    }

    const exposed = held.flatMap((fixture) => {
        const missing = unsuppliedReads(registered.declaration, fixture);
        return missing.length === 0 ? [] : [`${verdictKey(registered.id, fixture.kind)}: ${missing.join(", ")}`];
    });
    const judged = held.map((fixture) => ({
        fixture,
        outcome: judge(registered.id, registered.declaration, repoRoot, fixture),
    }));
    const verdicts = judged.map(({ fixture, outcome }): [string, GateOutcome["state"]] => [
        verdictKey(registered.id, fixture.kind),
        outcome.state,
    ]);
    const broken = judged.find(({ outcome }) => !CLEAR_STATES.has(outcome.state))?.outcome;
    const outcome = broken ?? {
        detail: judged.map((entry) => entry.outcome.detail).join(" · "),
        rule: registered.id,
        state: judged[0]?.outcome.state ?? "proven",
    };
    return { exposed, outcome, verdicts };
};

export const runGates = async function runGates(repoRoot: string): Promise<RunnerReport> {
    const registry = await discoverRules(repoRoot);
    const fixtures = await discoverFixtures(repoRoot);
    const declared = fixtures.gates;

    const judgments = registry.rules.map((registered) =>
        judgeRule(
            registered,
            declared.filter((fixture) => fixture.rule === registered.id),
            repoRoot,
        ),
    );
    const outcomes = judgments.map((judgment) => judgment.outcome);
    const exposed = judgments.flatMap((judgment) => judgment.exposed);
    const verdicts = worstVerdicts(judgments.flatMap((judgment) => judgment.verdicts));
    const movedVerdicts = movedOf(verdicts, priorKindVerdicts(repoRoot), exposed);

    const branches = fixtures.branches.map((fixture) => judgeBranch(fixture));

    const proven = countIn(outcomes, "proven");
    const exempt = countIn(outcomes, "exempt");
    const untested = countIn(outcomes, "untested");
    const failed = outcomes.length - proven - exempt - untested;

    const fixtured = new Set(
        declared.filter((one) => one.kind !== undefined).map((one) => `${one.rule}/${String(one.kind)}`),
    );
    const declaredKinds = registry.rules
        .flatMap((entry) => entry.declaration.kinds.map((kind) => `${entry.id}/${kind}`))
        .toSorted((left, right) => left.localeCompare(right, "en"));
    const unfixturedKinds = declaredKinds.filter((kind) => !fixtured.has(kind));

    const branchesProven = branches.filter((outcome) => outcome.state === "proven").length;

    return {
        branches,
        branchesOpen: branches.length - branchesProven,
        branchesProven,
        exempt,
        failed,
        kindScope: {
            bound:
                "every kind is DECLARED by the check that emits it and read from the registry, so the population " +
                "has no spelling to miss, no quote style to prefer, no delegation depth to bound and no " +
                "attribution to guess. THE PRIOR MECHANISM RECOVERED THIS SET BY MATCHING SOURCE TEXT, and a " +
                "check composing its kind as a template literal matched none of its forms — so the subtraction " +
                "that reports an unfixtured kind ranged over an empty population for those checks and could " +
                "never report a gap for one, publishing a zero whose own greenness was the evidence that what it " +
                "measured was working. A DECLARATION CAN STILL BE WRONG and the direction is the honest one: a " +
                "kind a check emits and does not declare is unproven and unreported, which is the same silence " +
                "as before for that one kind, while the population is now stated per check rather than inferred " +
                "for all of them. What closes that residue is the declaration and the emission sharing one " +
                "operand, which is a further change on the emitting sites rather than a bound on this one",
            declaredKinds: declaredKinds.length,
            rules: registry.rules.length,
        },
        kindVerdicts: verdicts,
        movedVerdicts: {
            bound:
                "each entry names a kind whose verdict DIFFERS from the previous run's. The prior verdict is retained " +
                "BY this comparison and consumed by it, which is what makes it an operand rather than a diary — remove " +
                "the comparison and the value stops being written. IT IS THE DELIVERY HALF OF THE TREE-LOADED-READS " +
                "POPULATION: that list is correct and reaches only a reader who already suspects what it would tell " +
                "them, because the defect it describes is a proof changing verdict when ANOTHER party edits an " +
                "unrelated file — so the reader who needs it has just been handed a result they cannot explain and no " +
                "signal saying this is that case. A moved verdict names the case AT THE MOMENT IT OCCURS, and where the " +
                "moved kind also leaves a declared read to the tree the entry says so, which is the explanation beside " +
                "the signal. Movement is also the discriminator the artifact cannot carry: deliberate reliance is " +
                "stable and a forgotten sibling is exactly what moves when unrelated work lands",
            kinds: movedVerdicts,
        },
        outcomes,
        proven,
        treeLoadedReads: {
            bound:
                "each entry names a fixture whose rule declares a read the fixture does not supply, so the certifier " +
                "loads it FROM THE REAL TREE and that half of the proof ranges over content the fixture never declared. " +
                "THIS IS PUBLISHED RATHER THAN FAILED, and the reason is measured rather than assumed: the same " +
                "comparison as a failing check names most of the fixture population, because a rule that DERIVES its " +
                "schema from a contract surface is meant to be proven against the real contract — so a red here would " +
                "fire on the correct pattern and the incorrect one alike, which is a check that punishes the right " +
                "answer. What it cannot separate is a fixture relying on a contract deliberately from one that supplied " +
                "a sibling and forgot this member, and nothing in either artifact distinguishes them. The measured harm " +
                "is real: a fixture in this shape changes verdict when unrelated work lands, so its failure is triggered " +
                "by another party's edit and presents as a flake with no attributable cause. The list is the population " +
                "a reader checks when a proof moves for no reason it can find",
            fixtures: exposed,
        },
        unfixturedKinds,
        untested,
    };
};