/**
 * select-baseline.ts
 *
 * Pure baseline-selection logic for pipeline auto-comparison.
 *
 * The scheduled pipeline auto-compares each new report against a prior
 * "comparable" baseline. Historically "comparable" meant only *same mode +
 * same source name, most recent*, which let a 9-area `baseline`-variant daily
 * run grab a 1-area `full`-variant ad-hoc GROQ run as its reference. Two runs
 * are only comparable when:
 *
 *   1. They share the same run `classification`. `classification` partitions
 *      runs into trend series (D0037): `official` is the canonical, trend-tracked
 *      series and `test`/`adhoc`/`experimental`/`external` are not. An `official`
 *      daily run that diffed against a `test` smoke run (e.g. report-019f0d09 on
 *      2026-06-29: a `testing-ground`-owned `test` run published with
 *      `source: production`) would corrupt the very series the comparison feeds.
 *      A classification
 *      mismatch is never comparable. The team-validity of an `official`
 *      candidate is already guaranteed by the D0073 publish gate, so this axis
 *      checks classification alone, not `owner.team`.
 *   2. They share the same literacy `variant`. The scoring methodology differs
 *      per variant (`baseline` vs `agentic` vs `full`), so a cross-variant
 *      delta is apples-to-oranges. A variant mismatch is never comparable.
 *   3. The candidate covers every area the current run scored
 *      (`current.areas ⊆ candidate.areas`). Otherwise the overall delta mixes a
 *      9-area average against, say, a 1-area average — and the missing areas
 *      have no honest baseline to diff against.
 *
 * Tie-break + fallback order (most-recent-first within each tier):
 *
 *   - Among same-variant candidates, the most recent whose area set is a
 *     superset of (or equal to) the current run's areas wins.
 *   - When no same-variant candidate fully covers the current areas, NOTHING
 *     is selected (`selected: null`). The caller skips auto-comparison rather
 *     than diffing a partial- or non-overlapping run. Per-area partial-baseline
 *     resolution is the scope of the sibling work item
 *     `W-resolve-per-area-baselines`; here we only refuse to fabricate a
 *     misleading overall delta. The best partial near-miss is still reported so
 *     the caller can log *which* candidate it declined.
 *
 * Pure: no I/O, no clock, no Sanity client. The caller (`ReportStore`) fetches
 * a slim candidate projection via GROQ (matching on the indexed scalar
 * dimensions) and feeds it here; area-coverage ranking can't ride GROQ's
 * indexed `Aggregate` path (array-superset predicates force a full `Fetch`), so
 * it lives in TypeScript where it is also directly unit-testable.
 */
import type { ISOTimestamp, LiteracyVariant, ReportId, RunClassification } from "./types.js";
/**
 * A baseline candidate, projected to just the fields selection needs. Mirrors
 * the GROQ projection in `ReportStore.findComparableBaseline`.
 */
export interface BaselineCandidate {
    /** Feature areas this candidate scored (`provenance.areas`). */
    areas: string[];
    /** The candidate's run classification, if recorded (`provenance.classification`). */
    classification?: RunClassification;
    /** When the candidate run completed (used for the most-recent tie-break). */
    completedAt: ISOTimestamp;
    /** The candidate report's id (`reportId`). */
    id: ReportId;
    /** The candidate's literacy variant, if any (`provenance.variant`). */
    variant?: LiteracyVariant;
}
/** The current run's comparability fingerprint. */
export interface BaselineQuery {
    /** Feature areas the current run scored. */
    areas: string[];
    /** The current run's run classification, if recorded. */
    classification?: RunClassification;
    /** The current run's literacy variant, if any. */
    variant?: LiteracyVariant;
}
/**
 * How completely a candidate's scored-area set covers the current run's areas.
 * Only `exact` and `superset` are comparable; `partial` and `disjoint` are not.
 */
export type AreaCoverage = "disjoint" | "exact" | "partial" | "superset";
/** Why baseline selection resolved the way it did (diagnostic + log signal). */
export type BaselineSelectionReason = "no-area-coverage" | "no-candidates" | "no-classification-match" | "no-variant-match" | "selected";
export interface BaselineSelection {
    /**
     * The most-recent same-variant candidate that shared at least one area but
     * did not fully cover the current run's areas. Present only when the reason
     * is `no-area-coverage`, so the caller can log the declined near-miss.
     */
    bestPartial?: {
        candidateAreas: number;
        id: ReportId;
        sharedAreas: number;
    };
    /**
     * Why nothing was selected, or `selected` when a baseline was chosen. Drives
     * the caller's skip-reason log line.
     */
    reason: BaselineSelectionReason;
    /**
     * The chosen baseline, or `null` when none is fully comparable. Only
     * `exact`/`superset` coverage is ever selected.
     */
    selected: {
        completedAt: ISOTimestamp;
        coverage: "exact" | "superset";
        id: ReportId;
    } | null;
}
/**
 * Most-recent-first comparator for baseline candidates. ISO 8601 UTC strings
 * sort lexicographically by time, so a plain string compare orders by
 * `completedAt` descending.
 */
export declare function byCompletedAtDesc(a: BaselineCandidate, b: BaselineCandidate): number;
/**
 * The non-`selected` reasons the shared comparability gate can return — the
 * subset of {@link BaselineSelectionReason} that means "the candidate pool was
 * emptied before area coverage was even considered".
 */
export type ComparabilityGateReason = "no-candidates" | "no-classification-match" | "no-variant-match";
/**
 * Apply the comparability gate shared by whole-report selection
 * ({@link selectComparableBaseline}) and per-area resolution
 * (`resolvePerAreaBaselines`): a candidate is comparable only when it shares
 * the current run's `classification` (trend-series partition, D0037) and
 * `variant` (scoring methodology). `undefined === undefined` keeps a
 * pre-taxonomy candidate comparable to an unclassified query and non-literacy
 * runs (no variant) comparable to each other.
 *
 * On success returns the surviving candidates ordered most-recent-first; on
 * failure returns the reason naming which axis emptied the pool. Area coverage
 * is deliberately NOT applied here — whole-report selection requires full
 * coverage, per-area resolution requires only per-area coverage, so each layers
 * its own area check on top of this shared gate.
 */
export declare function filterComparableCandidates(current: BaselineQuery, candidates: readonly BaselineCandidate[]): {
    ok: false;
    reason: ComparabilityGateReason;
} | {
    ok: true;
    ordered: BaselineCandidate[];
};
/**
 * Classify how a candidate's area set covers the current run's areas.
 *
 * An empty current area set carries no area constraint, so it is a subset of
 * everything: an empty candidate is `exact`, a non-empty candidate is
 * `superset`. This preserves the pre-existing "fall back to most recent" intent
 * for degenerate runs that scored no areas.
 */
export declare function classifyAreaCoverage(currentAreas: ReadonlySet<string>, candidateAreas: readonly string[]): AreaCoverage;
/**
 * Select the most recent comparable baseline from a candidate list.
 *
 * The candidate list need not be pre-sorted — selection sorts defensively by
 * `completedAt` descending. See the module header for the full comparability
 * contract and tie-break order.
 */
export declare function selectComparableBaseline(current: BaselineQuery, candidates: readonly BaselineCandidate[]): BaselineSelection;
