/** * `ci:` evidence — required CI green on a merged PR's MERGE COMMIT. * * Owner decision D11149: when every required check completed with `success` * on the commit a PR actually merged as, that satisfies `testsPassed` and * `qaPassed` for the task the PR closes. It replaces the post-merge local * full-suite rerun, which was the single largest override driver. Opt-in via * `evidence.ciSatisfies: true` in `.cleo/project-context.json`. * * Verified the way `pr:` provenance is: * * - the PR itself goes through {@link resolvePrEvidenceAtom} (merged, a real * merge-commit identity), so a `ci:` atom can never outrun `pr:`; * - the required-check list comes from the SAME resolver `pr:` uses * (env → `release.prRequiredWorkflows` → branch protection), and an * undetermined list refuses rather than accepting vacuously; * - each required name is judged on the merge commit's own check runs and * workflow runs (GitHub REST, by SHA). A run whose `head_sha` is anything * else — the PR head included — never counts, and a required check found * only there is named as such. * * Pending, failed, cancelled, skipped and missing required checks are each * refused with the check's name and state. * * One substitution (T12742): when a required check's merge-commit push run * was only CANCELLED or SKIPPED (a concurrency group superseded it) and no * run on the merge commit failed, the first decisive green `push` run on a * later default-branch commit stands in — only when the PR head's own * `pull_request` run was green and nothing in between touched the PR's files * or a CI definition. See {@link findGreenDescendant}. * * A second (T13453): a merge commit whose own run FAILED may also be stood in * for, but only when every failing job had already failed on its nearest * decided first-parent ancestor, i.e. the PR merged into a red main and * introduced no failure of its own. See {@link inheritedOnMerge}. * * A third (T13809): a merge commit whose run FAILED only on the flaky-quarantine * budget, with every test passing, failed no test of the PR's. See * {@link nonTestFailuresOn}. The stand-in search also looks past later runs red * for that reason alone, up to {@link CI_DESCENDANT_MAX_CANDIDATES} commits. * * A fourth (T14032, owner decision D11192), for a WORKSPACE-WIDE change only * (one a targeted test-run cannot speak for: a lockfile, a root config): when * the merge commit is not green and no T12742 descendant stands in, a green * push run of every required check on the default branch's CURRENT head (or * on a commit differing from it only in documentation) that descends from the * merge stands in, whatever the merge's own run concluded and whatever later * commits touched. See {@link findGreenCurrentHead}. * * @task T12634 */ import type { EvidenceAtom, EvidenceValidationContext } from '@cleocode/contracts'; import type { ViewComponentPr } from '../tasks/component-pr.js'; import { type EvidenceRoots } from '../tasks/evidence.js'; import { type FetchGhBranchProtection, type PrAtomResolution, type RequiredCheckPinSpec } from './pr-evidence.js'; /** One check on a commit: a job (check run) or a whole workflow run. */ export interface CommitCheck { /** Check-run (job) name or workflow name. */ name: string; /** Which GitHub object reported it. */ source: 'check-run' | 'workflow-run'; /** `queued` / `in_progress` / `completed` / … */ status: string; /** `success` / `failure` / `skipped` / … — null until completed. */ conclusion: string | null; /** The commit the check ran on. */ headSha: string; /** GitHub id; a higher id is a later attempt within the same event. */ id: number; /** Posting GitHub App slug (`github-actions`, …), when known. */ appSlug?: string; /** Posting GitHub App id, when known. */ appId?: number; /** Workflow file that produced the run, when known. */ workflowPath?: string; /** Triggering event (`push`, `pull_request`, …), when known. */ event?: string; /** Workflow attempt (1 = first run, above 1 = a re-run), when known (T14026). */ runAttempt?: number; /** ISO 8601 time the run (or this attempt of it) started, when known (T14026). */ startedAt?: string; } /** A judged check: which run, on which commit, attests a required name. */ export interface JudgedCheck { name: string; conclusion: string; sha: string; app?: string; workflow?: string; event?: string; } /** Result of {@link evaluateMergeCommitChecks}. */ export type MergeCommitCheckResult = { ok: true; checks: JudgedCheck[]; } | { ok: false; reasons: string[]; }; /** Options for {@link evaluateMergeCommitChecks}. */ export interface EvaluateMergeCommitChecksOptions { /** * PR head whose `pull_request` runs tested exactly the merged tree (tree * equal AND the merge's first parent is an ancestor of it). Its runs stand * in for a merge-commit run that was cancelled or never started. */ treeEquivalentSha?: string; /** Required-name → app/workflow pin; a run from anything else never counts. */ pins?: Record; /** * Later default-branch commit (T12742) whose `push` runs stand in for a * required check whose merge-commit verdict is only `cancelled`/`skipped` — * never for a failed, pending or missing one. The caller proves ancestry and * file integrity ({@link findGreenDescendant}). */ descendantSha?: string; /** * The merge commit's failures were not the PR's: all inherited from its * base (T13453), or only the flaky-quarantine budget with every test passing * (T13809). The descendant may stand in for a FAILED required check too. */ inheritedFailures?: boolean; /** * The descendant is the default branch's current head standing in for a * workspace-wide change (T14032): it stands in for any merge-commit verdict. */ currentHead?: boolean; } /** One failing merge-commit job and the base run it was inherited from (T13453). */ export interface InheritedFailure { /** Job or workflow name. */ name: string; /** The failing run on the merge commit. */ mergeRunId: number; /** The first-parent ancestor whose own run of the job had already failed. */ baseSha: string; /** That ancestor's failing run. */ baseRunId: number; } /** First-parent ancestors searched for a decided base verdict of a failing job (T13453). */ export declare const CI_INHERITED_MAX_ANCESTORS = 10; /** One first-parent commit of the default branch after a merge commit (T12742). */ export interface MainCommit { /** Commit SHA. */ sha: string; /** Number of parents; 2+ is a merge commit. */ parents: number; } /** Most later default-branch commits examined for a green stand-in run (T12742). */ export declare const CI_DESCENDANT_MAX_CANDIDATES = 20; /** Latest committer time after the merge commit a stand-in may have (T12742): 7 days. */ export declare const CI_DESCENDANT_MAX_AGE_SECONDS: number; /** A `ci:` atom resolution: the validated atom, or the refusal. */ export type CiAtomResolution = { ok: true; atom: Extract; } | { ok: false; reason: string; codeName: 'E_EVIDENCE_INSUFFICIENT' | 'E_EVIDENCE_TESTS_FAILED' | 'E_EVIDENCE_TOOL_FAILED' | 'E_EVIDENCE_INVALID' | 'E_EVIDENCE_GIT_ROOT' | 'E_EVIDENCE_CONTENT_MISMATCH' | 'E_EVIDENCE_CI_VACUOUS'; }; /** Injectable I/O for {@link resolveCiEvidenceAtom}. */ export interface ResolveCiEvidenceOptions { /** Task, gates and criteria the atom is validated for; required (like `pr:`). */ context?: EvidenceValidationContext; /** PR provenance; defaults to {@link resolvePrEvidenceAtom}. */ resolvePr?: (prNumber: number, roots: EvidenceRoots) => Promise; /** Checks reported for one commit SHA; defaults to the GitHub REST API via `gh`. */ fetchChecks?: (sha: string, cwd: string) => Promise<{ ok: true; checks: CommitCheck[]; } | { ok: false; reason: string; }>; /** Parsed `.cleo/project-context.json`, for the required-check list and pins. */ projectContext?: Record | null; /** Tree SHA of a commit, or null when the object is not local; defaults to `git rev-parse`. */ treeOf?: (sha: string, cwd: string) => string | null; /** Whether `ancestor` is an ancestor of `descendant`; defaults to `git merge-base --is-ancestor`. */ isAncestor?: (ancestor: string, descendant: string, cwd: string) => boolean; /** First parent of a commit, or null; defaults to `git rev-parse ^1`. */ firstParentOf?: (sha: string, cwd: string) => string | null; /** * Whether a commit has landed on origin's default branch; defaults to * `origin/HEAD` (else origin/main, origin/master) and `merge-base --is-ancestor`. */ onDefaultBranch?: (sha: string, cwd: string) => { ref: string | null; landed: boolean; }; /** * Component PR the task is linked through (`ci:@`, * T12671): the integration PR's CI is judged; the component supplies the link. */ componentPrNumber?: number; /** Component PR reader; defaults to `gh pr view`. */ viewComponentPr?: ViewComponentPr; /** Persist nothing: no PR-result or branch-protection cache writes (the `--plan` preview). */ readOnly?: boolean; /** * First-parent commits of `ref` after `mergeSha`, oldest first, already * bounded to {@link CI_DESCENDANT_MAX_CANDIDATES} within * {@link CI_DESCENDANT_MAX_AGE_SECONDS}; null when git cannot tell (T12742). * Defaults to `git log --first-parent`. */ listDescendants?: (mergeSha: string, ref: string, cwd: string) => string[] | null; /** * ISO 8601 commit time of the first first-parent commit of `ref` after * `mergeSha`, or null when there is none or git cannot tell (T14026). * Defaults to `git log --first-parent --ancestry-path`. */ nextMainCommitAt?: (mergeSha: string, ref: string, cwd: string) => string | null; /** * First-parent commits in `from..to` whose diff against their first parent * touches any of `paths`; null when git cannot tell (T12742). Any hit — * merge or not — refuses the stand-in. Defaults to * `git log --first-parent -- `. */ touchingCommits?: (from: string, to: string, paths: readonly string[], cwd: string) => MainCommit[] | null; /** * Plain-text log of one Actions job (a check-run id), or null when it cannot * be read (T13809). Defaults to `gh api repos/{owner}/{repo}/actions/jobs//logs`. */ fetchJobLog?: (jobId: number, cwd: string) => Promise; /** * The current head of the default-branch ref (T14032); null when unknown. * Defaults to GitHub's branch head via `gh`, else `git rev-parse `. */ defaultBranchHead?: (ref: string, cwd: string) => string | null; /** * Paths that differ between two commits, or null when git cannot tell * (T14032). Defaults to `git diff --name-only --no-renames`. */ changedBetween?: (from: string, to: string, cwd: string) => string[] | null; /** * Why a change is workspace-wide (only a full run speaks for it), or null * when it is scoped (T14032). Defaults to the `test-run:` requirement rule * (`testRunRequirement` kind `full`). */ workspaceWideReason?: (root: string, paths: readonly string[]) => Promise; } /** * Whether the project opted in to merge-commit CI as test/QA evidence * (`evidence.ciSatisfies === true`; anything else, including a missing file, * is `false`). * * @param projectRoot - CLEO store root. * @returns True only for an explicit boolean `true`. * @task T12634 */ export declare function readCiSatisfies(projectRoot: string): boolean; /** * The `evidence.ciSatisfies` remedy for a project that has GitHub workflows * but has not opted in (T13096), or null when it has, or has no workflows. * * Without the setting, `cleo complete` cannot stand a merged PR's CI in for * `testsPassed`/`qaPassed` (`ci:`), so a scoped run recorded before the * merge leaves only a full local suite. The remedy is a ready-to-paste * `evidence` block whose `ciChecks` are the default branch's required checks * ({@link resolveRequiredWorkflowsDetailed}, read-only: nothing is cached). * * @param root - The project checkout (its `.cleo/project-context.json` and `.github/workflows`). * @param opts - Branch-protection fetcher override (tests). * @returns The remedy text, or null. * @task T13096 */ export declare function ciSatisfiesRemedy(root: string, opts?: { fetchGhBranchProtection?: FetchGhBranchProtection; }): Promise; /** * The checks that attest each gate (`evidence.ciChecks`, T12634). * * @param projectRoot - CLEO store root. * @returns `tests` → testsPassed, `qa` → qaPassed; a missing list is absent. * @task T12634 */ export declare function readCiChecks(projectRoot: string): CiChecksConfig; /** * One `evidence.ciChecks.covering` rule (T13175): changed paths matching * `paths` are covered by the jobs matching `jobs`. */ export interface CoveringJobRule { /** Repo-relative path globs (`**` any depth, `*` within one segment). */ paths: string[]; /** Job-name globs (`*` wildcard) that must each have run and succeeded. */ jobs: string[]; } /** `evidence.ciChecks` as read from project context (T12634). */ export interface CiChecksConfig { /** Required checks attesting testsPassed. */ tests?: string[]; /** Required checks attesting qaPassed. */ qa?: string[]; /** * Job-name globs (`*` wildcard) that must each match at least one job run by * the pinned workflows of that gate's checks, all `success`, before a `code` * task's gate is attested — an aggregate that counts skipped jobs as a pass * is not enough. */ jobs?: { tests?: string[]; qa?: string[]; }; /** * T13175: jobs that cover changed paths a change-detection filter keeps out * of the `jobs` globs (e.g. `scripts/**` → `Scripts Tests`). When every * required job of a gate was SKIPPED (by its `if:` filter; a failure upstream * already turns the required check red) and every changed path matches a * rule whose jobs ran and succeeded, those jobs attest the gate instead. */ covering?: { tests?: CoveringJobRule[]; qa?: CoveringJobRule[]; }; } /** * Stricter than `pr:`'s {@link isDocumentArtifact}: a documentation artifact * that lives outside every code root. Only a diff made entirely of these may * leave its test and typecheck jobs honestly skipped. * * @param path - Repo-relative changed path. * @returns True when the path cannot affect code, tests or CI. * @task T12634 */ export declare function isCiDocumentPath(path: string): boolean; /** * Path glob to an anchored regular expression (T13175): `**` followed by `/` is * any number of whole directories (including none), a trailing `/` followed by * `**` is everything below, `*` any run of characters within one segment, `?` * one character within a segment. Everything else is literal. * * @param glob - Repo-relative path glob. * @returns The anchored expression. */ export declare function pathGlobToRegExp(glob: string): RegExp; /** * Judge a gate's required jobs with the covering rules (T13175): accepted only * when each required glob either succeeded or had every latest run completed * `skipped` (never cancelled, failed, pending or missing), at least one was * skipped, every changed path matches a rule, and every job glob of each rule * used has run and succeeded on `sha`. * * @param globs - The gate's required job globs (`evidence.ciChecks.jobs`). * @param rules - The gate's covering rules. * @param changedPaths - The PR's changed paths. * @param checks - Every check reported for the commit. * @param sha - Commit being judged. * @param scope - The app and workflow files the jobs must come from. * @returns The covering job names, or why coverage does not apply. * @task T13175 */ export declare function evaluateCoveringJobs(globs: readonly string[], rules: readonly CoveringJobRule[], changedPaths: readonly string[], checks: readonly CommitCheck[], sha: string, scope: { app?: string | number; workflows: readonly string[]; }): { ok: true; jobs: string[]; } | { ok: false; reason: string; }; /** * Judge job globs on one SHA: every glob must match at least one job (check * run) from the allowed workflows/app, and the latest run of each matched * (job, event) must be `success` — a `skipped` job is refused by name. * * @param globs - Job-name globs for the gate. * @param checks - Every check reported for the commit. * @param sha - Commit being judged. * @param scope - The app and workflow files the jobs must come from. * @param onlyEvent - Restrict to runs of this event (the PR-head substitution). * @returns The judged job names, or one reason per unmet glob. * @task T12634 */ export declare function evaluateJobs(globs: readonly string[], checks: readonly CommitCheck[], sha: string, scope: { app?: string | number; workflows: readonly string[]; }, onlyEvent?: string): { ok: true; jobs: string[]; } | { ok: false; reasons: string[]; }; /** * Whether a fetch left a required check's run without the identity its pin * names (T13017), so the fetch is retried before anything is judged. * * @param checks - Checks fetched for one commit. * @param required - Required check names. * @param pins - Required-name to app/workflow pin. * @returns True when a required run's identity is incomplete. * @task T13017 */ export declare function hasIncompleteIdentity(checks: readonly CommitCheck[], required: readonly string[], pins: Record): boolean; /** * Judge required checks on one merge commit. Pure. * * For each required name, runs are grouped by source (job / workflow) and * triggering event, and within a group only the LATEST attempt counts; every * group on the merge commit must be `completed`/`success`. A pinned name only * considers runs from its pinned app/workflow. When `treeEquivalentSha` is * given, that commit's `pull_request` runs (and only those) stand in for a * merge-commit verdict that failed or is missing. * * @param required - Required check names. * @param checks - Every check reported for the commits involved. * @param mergeCommitSha - The merge commit being judged. * @param opts - Tree-equivalent PR head and pins. * @returns The judged checks, or one reason per unmet name. * @task T12634 */ export declare function evaluateMergeCommitChecks(required: readonly string[], checks: readonly CommitCheck[], mergeCommitSha: string, opts?: EvaluateMergeCommitChecksOptions): MergeCommitCheckResult; /** Result of {@link supersededOnMerge}. */ export type SupersededOnMergeResult = { ok: true; names: string[]; } | { ok: false; reason: string; }; /** * Whether every required check left unproven on the merge commit was only * SUPERSEDED there (T12742): its latest merge-commit verdict is `cancelled` * or `skipped`, and no run of a required check or of a job in a pinned * workflow on the merge commit — any attempt — concluded with a failure. A * failed, timed-out, pending or missing check is never superseded. Pure. * * @param required - Required check names. * @param checks - Every check fetched for the merge commit (and PR head). * @param mergeCommitSha - The merge commit. * @param opts - Tree-equivalent PR head and pins, as for {@link evaluateMergeCommitChecks}. * @returns The superseded names, or why a later main run may not stand in. * @task T12742 */ export declare function supersededOnMerge(required: readonly string[], checks: readonly CommitCheck[], mergeCommitSha: string, opts?: EvaluateMergeCommitChecksOptions): SupersededOnMergeResult; /** Result of {@link inheritedOnMerge}. */ export type InheritedOnMergeResult = { ok: true; names: string[]; inherited: InheritedFailure[]; } | { ok: false; reason: string; }; /** * Whether the merge commit's failures were all inherited from main (T13453). * * Every in-scope job whose latest run on the merge commit failed must have * failed on its nearest DECIDED first-parent ancestor too: the closest of up to * {@link CI_INHERITED_MAX_ANCESTORS} ancestors whose latest run of that job * completed as success or failure (cancelled, skipped and missing runs are * looked past). A job that passed there, or was never decided, is a failure * the PR may have introduced and refuses. A pending merge-commit run refuses. * Pure: the ancestors' checks are passed in, nearest first. * * The refusal blames the PR only when the failing run could have seen nothing * but the PR's change (T14026): a re-run, or a run that started after a later * default-branch commit landed, ran in a world that moved on since the merge * (rotated secrets, external services), so it still refuses but says the * failure may not be the PR's. * * @param required - Required check names. * @param checks - Checks fetched for the merge commit. * @param mergeCommitSha - The merge commit. * @param ancestors - First-parent ancestors of the merge, nearest first, with their checks. * @param pins - Required-name to app/workflow pin. * @param nextMainCommitAt - When the next default-branch commit after the merge landed, if known. * @param untested - Failing jobs with no failed test, a runner cancellation or a worker crash (T14049, T14074): never the PR's, so never blamed. * @returns The required names to stand in for and the inherited jobs, or why not. * @task T13453 * @task T14026 * @task T14049 */ export declare function inheritedOnMerge(required: readonly string[], checks: readonly CommitCheck[], mergeCommitSha: string, ancestors: ReadonlyArray<{ sha: string; checks: readonly CommitCheck[]; }>, pins: Record, nextMainCommitAt?: string | null, untested?: ReadonlySet): InheritedOnMergeResult; /** * Whether a unit-test job log shows a failure caused by the flaky-quarantine * budget alone (T13809). `scripts/ci-flaky-quarantine.mjs` fails a main run * whose quarantine is over budget right after the first test run, before it * applies its own rule that a quarantined test's failure never blocks. So * the log qualifies when the budget heading is present, no error escaped a * test, and every failing test of that run is one the quarantine lists (no * failure at all is the common case). Only the run's own summary block counts * ({@link runSummary}), never a summary a test echoed. Any doubt is `false`: * no summary block, a kill or crash marker, a failing test outside the * quarantine, a file that failed to load, or counts that do not add up * (per-file `(N tests | M failed)` lines included). * * With `unretriedTimeouts` (T14086, later runs only), a failing test whose * error is vitest's own "Test timed out in N ms" also qualifies: the budget * stopped the run before its rerun-once flake rule (T13145) could judge it, * so the timeout decided nothing. An assertion failure never qualifies. * * @param log - The job's plain-text log (ANSI colour codes allowed). * @param opts - `unretriedTimeouts`: accept unretried timeouts too. * @returns Whether the job failed only on the quarantine budget. * @task T13809 * @task T14086 */ export declare function isQuarantineBudgetOnlyLog(log: string, opts?: { unretriedTimeouts?: boolean; }): boolean; /** Result of {@link nonTestFailuresOn}. */ export type NonTestFailuresResult = { ok: true; jobs: Array<{ name: string; mergeRunId: number; }>; } | { ok: false; reason: string; }; /** * Whether every failure on a commit's CI is a non-test failure (T13809): each * failing job of a pinned workflow failed on the flaky-quarantine budget alone * ({@link isQuarantineBudgetOnlyLog}) with every test passing. The required * checks themselves (the aggregate job and its workflow run) only report the * jobs they need, so their failure follows; any cancelled or timed-out job, a * pending run, or an unreadable log refuses. * * @param checks - Checks fetched for `sha`. * @param sha - The commit judged. * @param required - Required check names. * @param pins - Required-name to app/workflow pin. * @param fetchJobLog - Plain-text log of one job. * @param cwd - Repository work tree for `gh`. * @param opts - `unretriedTimeouts` for a later run (T14086, {@link isQuarantineBudgetOnlyLog}). * @returns The quarantine-only jobs, or why the commit failed for real. * @task T13809 */ export declare function nonTestFailuresOn(checks: readonly CommitCheck[], sha: string, required: readonly string[], pins: Record, fetchJobLog: NonNullable, cwd: string, opts?: { unretriedTimeouts?: boolean; }): Promise; /** * Whether a later commit's CI was cancelled rather than failed (T13809): its * pinned workflow run concluded `cancelled` (a concurrency group stopped it) * and every failing job's log is readable and names no failed test; a job a * cancel interrupted often reports `failure` with nothing failed in its log. Such a run decides nothing, as a * cancelled run never did (T12742). * * @param checks - Checks fetched for `sha`. * @param sha - The later commit. * @param required - Required check names. * @param pins - Required-name to app/workflow pin. * @param fetchJobLog - Plain-text log of one job. * @param cwd - Repository work tree for `gh`. * @returns Whether the run was cancelled with no test failing. * @task T13809 */ export declare function cancelledWithoutTestFailure(checks: readonly CommitCheck[], sha: string, required: readonly string[], pins: Record, fetchJobLog: NonNullable, cwd: string): Promise; /** * Whether a job log shows only a runner cancellation (T14049): every * `##[error]` line is GitHub's own "The operation was canceled." (or the * runner shutdown notice before it), and no test failed. GitHub then reports * the job, and the aggregate check that needs it, as `failure`, though * nothing the PR changed failed. * * @param log - The job's plain-text log (ANSI colour codes allowed). * @returns Whether the job failed only because its runner was cancelled. * @task T14049 */ export declare function isRunnerCancelOnlyLog(log: string): boolean; /** * Whether a job log shows only an unattributable vitest worker crash * (T14074): no test or file failed, every `Unhandled Error` vitest reports is * its own pool's "Worker forks emitted error" (a fork that exited * unexpectedly, e.g. a native addon abort), and the only `##[error]` is the * step's non-zero exit. Such a run never showed a test of the change failing. * * @param log - The job's plain-text log (ANSI colour codes allowed). * @returns Whether the job failed only on a worker crash. * @task T14074 */ export declare function isWorkerCrashOnlyLog(log: string): boolean; /** Why a failing job tested nothing of the change (T14049, T14074). */ export type UntestedFailureCause = 'runner-cancel' | 'worker-crash'; /** * Why a failing job's log shows no failed test, or `null` when it may have * failed on the change: a runner cancellation ({@link isRunnerCancelOnlyLog}) * or a worker crash ({@link isWorkerCrashOnlyLog}). * * @param log - The job's plain-text log. * @returns The cause, or `null`. * @task T14074 */ export declare function untestedFailureCause(log: string): UntestedFailureCause | null; /** One failing job that tested nothing of the change, and why (T14074). */ export interface UntestedJobFailure { name: string; mergeRunId: number; cause: UntestedFailureCause; } /** A later default-branch run the stand-in search looked past, and why (T14086). */ export interface PassedOverRun { /** The later commit. */ sha: string; /** Its failing jobs. */ jobs: string[]; /** Why they decided nothing. */ reason: string; } /** Result of {@link untestedJobFailures}. */ export interface UntestedJobFailures { /** The failing jobs whose log shows no failed test, with the cause. */ jobs: UntestedJobFailure[]; /** Their run ids. */ ids: ReadonlySet; /** Every failing job of a pinned workflow is one (and at least one failed). */ all: boolean; } /** * The failing jobs of a pinned workflow on `sha` (the required aggregate and * workflow runs excluded) that failed without a failed test: a runner * cancellation (T14049) or a worker crash (T14074), by * {@link untestedFailureCause}. A pending job or an unreadable log is never * one, so `all` is then false. * * @param checks - Checks fetched for `sha`. * @param sha - The commit judged. * @param required - Required check names. * @param pins - Required-name to app/workflow pin. * @param fetchJobLog - Plain-text log of one job. * @param cwd - Repository work tree for `gh`. * @returns Those jobs, and whether they are every failure. * @task T14049 * @task T14074 */ export declare function untestedJobFailures(checks: readonly CommitCheck[], sha: string, required: readonly string[], pins: Record, fetchJobLog: NonNullable, cwd: string): Promise; /** * Whether a job log shows a failure in a setup step before any test ran * (T13809): its first `##[error]` sits in a remote action step * (`Run owner/action@ref`, e.g. `actions/download-artifact@v4` finding no * artifact), never in a `run:` script, and the log has no vitest output at * all. A gate script or test failure runs in a `run:` step and never matches. * * @param log - The job's plain-text log (ANSI colour codes allowed). * @returns Whether the job failed in an action step before any test ran. * @task T13809 */ export declare function isSetupStepFailureLog(log: string): boolean; /** * Whether a later commit's CI failed before any test ran (T13809): every * failing job's log is readable and failed in a setup action step * ({@link isSetupStepFailureLog}), as when each unit-test shard finds no build * artifact. Such a run tested nothing, so it decides nothing. * * @param checks - Checks fetched for `sha`. * @param sha - The later commit. * @param required - Required check names. * @param pins - Required-name to app/workflow pin. * @param fetchJobLog - Plain-text log of one job. * @param cwd - Repository work tree for `gh`. * @returns Whether every failing job failed before running a test. * @task T13809 */ export declare function failedBeforeAnyTest(checks: readonly CommitCheck[], sha: string, required: readonly string[], pins: Record, fetchJobLog: NonNullable, cwd: string): Promise; /** Inputs of {@link findGreenDescendant}. */ export interface FindGreenDescendantInput { /** Required check names. */ required: readonly string[]; /** Checks already fetched for the merge commit (and a tree-equivalent PR head). */ checks: readonly CommitCheck[]; /** The merge commit. */ mergeCommitSha: string; /** Tree-equivalent PR head, when one was proven. */ treeEquivalentSha?: string; /** Required-name → app/workflow pin. */ pins: Record; /** The PR's changed paths; no commit in the descendant range may touch them. */ changedPaths: readonly string[]; /** * The PR's final head (`headRefOid`). Its latest `pull_request` runs must be * green for every superseded check: a later main run never vouches for a * PR that was not itself green. */ prHeadSha?: string; /** * The PR edited a pinned workflow, so its own `pull_request` runs prove * nothing (they ran the PR's edited workflow) and are not consulted: only * default-branch `push` runs attest it (T13174). */ mainOnly?: boolean; /** Default-branch ref (`origin/main`). */ ref: string; /** Repository work tree. */ cwd: string; /** Checks for one SHA. */ fetchChecks: NonNullable; /** Later first-parent commits of `ref`, oldest first, bounded. */ listDescendants: NonNullable; /** First-parent commits in a range touching paths. */ touchingCommits: NonNullable; /** Ancestry test. */ isAncestor: NonNullable; /** First parent of a commit (T13453: the inherited-failure walk); absent = never inherit. */ firstParentOf?: NonNullable; /** Log of one job (T13809: the quarantine-budget check); absent = never classify. */ fetchJobLog?: NonNullable; /** When the next default-branch commit after the merge landed (T14026); absent = unknown. */ nextMainCommitAt?: NonNullable; /** * Job globs a code task's gates need (T13846), with the workflows they must * come from. A candidate whose required checks are green but whose push run * never ran these jobs (a change filter skipped them, or the run was * cancelled before them) decides nothing and the walk moves on; a job that * failed there refuses, and a pending one waits. */ jobs?: ReadonlyArray<{ globs: readonly string[]; scope: JobScope; }>; } /** The app and workflow files a gate's jobs must come from. */ export interface JobScope { app?: string | number; workflows: readonly string[]; } /** Result of {@link findGreenDescendant}. */ export type FindGreenDescendantResult = { ok: true; /** The stand-in commit. */ sha: string; /** Checks fetched for the stand-in and for the PR head. */ checks: CommitCheck[]; /** `..`, proven to leave the PR's files and CI definitions untouched. */ range: string; /** * The PR head whose `pull_request` runs were green for every superseded * check; absent in `mainOnly` mode, where PR runs are not consulted. */ prHeadSha?: string; /** The merge commit's inherited failures, when it FAILED rather than being superseded (T13453). */ inherited?: InheritedFailure[]; /** The merge commit's jobs that failed on the flaky-quarantine budget alone (T13809). */ nonTest?: Array<{ name: string; mergeRunId: number; }>; /** The merge commit's failures tested nothing: runner cancellations or worker crashes (T14049, T14074). */ untested?: UntestedJobFailure[]; /** Later runs the search looked past, and why (T14086). */ passedOver?: PassedOverRun[]; } | { ok: false; reason: string; }; /** * Find a later default-branch commit whose green `push` CI stands in for a * merge-commit run that a concurrency group cancelled (T12742). * * The stand-in's CI tested the DESCENDANT's tree, not the merge tree. It is * accepted only when that difference cannot matter to the PR: * - the merge commit must be superseded, not failed ({@link supersededOnMerge}), * unless its failures were inherited from main ({@link inheritedOnMerge}) or * were only the flaky-quarantine budget ({@link nonTestFailuresOn}, T13809); * - the PR's own final head (`prHeadSha`) must have a green latest * `pull_request` run for every superseded check — a later fix on main * never rescues a broken PR; * - candidates are the first {@link CI_DESCENDANT_MAX_CANDIDATES} first-parent * commits of `ref` within {@link CI_DESCENDANT_MAX_AGE_SECONDS} of the * merge, each a proven descendant (`git merge-base --is-ancestor`); * - NO commit in `merge..candidate` — merge commits included — may touch the * PR's changed paths or a CI definition ({@link ciDefinitionPaths}): a * later merge could otherwise fix the PR's code or weaken the workflow * that judged it (drop steps or filters, add `continue-on-error`); * - the FIRST candidate with a decisive verdict decides: every superseded * check green on its own `push` runs accepts it, a failure on it (or any * failed job of a pinned workflow) refuses, and a PENDING run refuses with * "wait for " — a later green run is never shopped for past one that * is red or may still go red. Only cancelled, skipped and missing * (never-started) runs, and a run red only for the flaky-quarantine budget * (T13809), move on to the next candidate. * * @param input - Merge-commit checks, pins, changed paths, PR head and injected I/O. * @returns The stand-in SHA, its checks and proven range, or the refusal reason. * @task T12742 */ export declare function findGreenDescendant(input: FindGreenDescendantInput): Promise; /** Inputs of {@link findGreenCurrentHead}. */ export interface FindGreenCurrentHeadInput { /** Required check names; every one must be green on the stand-in's push runs. */ required: readonly string[]; /** The merge commit. */ mergeCommitSha: string; /** Required-name → app/workflow pin. */ pins: Record; /** Job globs the gates need, as for {@link findGreenDescendant} (T13846). */ jobs?: FindGreenDescendantInput['jobs']; /** Default-branch ref (`origin/main`). */ ref: string; /** Repository work tree. */ cwd: string; /** Checks for one SHA. */ fetchChecks: NonNullable; /** The default branch's current head. */ defaultBranchHead: NonNullable; /** Paths that differ between two commits. */ changedBetween: NonNullable; /** First parent of a commit. */ firstParentOf: NonNullable; /** Ancestry test. */ isAncestor: NonNullable; } /** Result of {@link findGreenCurrentHead}. */ export type FindGreenCurrentHeadResult = { ok: true; /** The commit whose push CI stands in: HEAD, or one differing from it only in documentation. */ sha: string; /** Checks fetched for it. */ checks: CommitCheck[]; /** `..`. */ range: string; /** The default branch's head at verify time. */ head: string; } | { ok: false; reason: string; }; /** * Find the green required-CI push run on the default branch's CURRENT head * that stands in for a workspace-wide change's merge-commit CI (T14032, owner * decision D11192). A workspace-wide change (a lockfile, a root config, a CI * definition) only a full run speaks for; when its merge-commit run was red * for a reason that is not the change (a secrets job failing on a re-run) and * later commits touched its files, {@link findGreenDescendant} refuses, yet a * full green CI on today's HEAD tests the merged change as it is now — the * same reasoning as the targeted test-run stand-in (T13673), which also * allows later edits. * * Candidates are HEAD, then its first parents while they differ from HEAD only * in documentation ({@link isCiDocumentPath}, the T13784 rule: nothing a test * reads moved), up to {@link CI_DESCENDANT_MAX_CANDIDATES}. Each must descend * from the merge commit. The first decisive verdict decides, as in * {@link findGreenDescendant}: every required check green on its own `push` * runs (and the gates' jobs run, T13846) accepts; a failed or pending one * refuses; cancelled, skipped or missing runs move to the parent. * * @param input - Required checks, pins, merge commit and injected I/O. * @returns The stand-in commit, its checks and the head, or the refusal reason. * @task T14032 */ export declare function findGreenCurrentHead(input: FindGreenCurrentHeadInput): Promise; /** * Default {@link ResolveCiEvidenceOptions.listDescendants} (T12742): the * first-parent commits of `ref` that descend from `mergeSha`, oldest first, * bounded to {@link CI_DESCENDANT_MAX_CANDIDATES} committed within * {@link CI_DESCENDANT_MAX_AGE_SECONDS} of the merge commit. * * @param mergeSha - The merge commit. * @param ref - Default-branch ref (`origin/main`). * @param cwd - Repository work tree. * @returns Candidate SHAs, or null when git cannot read them. * @task T12742 */ export declare function listMainDescendants(mergeSha: string, ref: string, cwd: string): string[] | null; /** * Default {@link ResolveCiEvidenceOptions.touchingCommits} (T12742): the * first-parent commits in `from..to` that changed any of `paths`. With * `--first-parent`, git judges each commit's path-limited change against its * first parent only, so a merge that brought another PR's edit is listed as * a merge, and a direct (non-merge) commit as a non-merge. * * @param from - Exclusive start (the merge commit). * @param to - Inclusive end (the candidate). * @param paths - The PR's changed paths. * @param cwd - Repository work tree. * @returns The touching commits with their parent counts, or null on a git error. * @task T12742 */ export declare function listPathTouchingMainCommits(from: string, to: string, paths: readonly string[], cwd: string): MainCommit[] | null; /** * Default branch name from `gh repo view --json defaultBranchRef`. * * @param cwd - Repository `gh` runs in. * @returns The branch name, or null when `gh` is unavailable, failed, or * answered something that is not a branch name. * @task T12959 */ export declare function ghDefaultBranch(cwd: string): string | null; /** * Fetch once more when a required check's run came back without the identity * its pin names (T13017): GitHub lists a job before its workflow run, and a * run judged from that response would read as another app's. A run still * incomplete after the retry is judged pending, never as a mismatch. * * @param fetchChecks - The underlying check fetch. * @param required - Required check names. * @param pins - Required-name to app/workflow pin. * @returns The same fetch, retried once on incomplete identity. * @task T13017 */ export declare function refetchIncompleteIdentity(fetchChecks: NonNullable, required: readonly string[], pins: Record): NonNullable; /** * Drop runs a schedule started from every fetch (T13623). A nightly run that * happens to land on a merge commit tests main at that hour, not the merge: * only the merge commit's push run, a tree-equal PR head's pull_request run or * a later main commit's push run (T12742) decides the verdict. * * @param fetchChecks - The underlying check fetch. * @returns The same fetch, minus `schedule` runs and their check runs. * @task T13623 */ export declare function withoutScheduledRuns(fetchChecks: NonNullable): NonNullable; /** * Re-check, at `cleo complete`, a `ci:` atom that leaned on a later main * commit (T12742). A completed conclusion is NOT immutable: a GitHub re-run * adds a newer attempt that can go red. Every check judged on the * descendant is re-fetched and its latest `push` attempt (same app and * workflow) must still be `success`; each such check's latest * `pull_request` attempt on the recorded PR head must be too. Atoms without * `descendantSha` pass untouched. * * @param atom - The recorded `ci:` atom. * @param cwd - Repository work tree for `gh`. * @param fetchChecks - Checks for one SHA; defaults to the GitHub REST API. * @returns Ok, or why the stand-in no longer holds. * @task T12742 */ export declare function recheckCiDescendantAtom(atom: Extract, cwd: string, fetchChecks?: NonNullable): Promise<{ ok: true; } | { ok: false; reason: string; }>; /** * Validate a `ci:` atom end to end. * * @param prNumber - The merged PR. * @param roots - Store root (config, caches) and execution root (`gh` cwd). * @param opts - Injectable PR resolution and check fetching. * @returns The validated atom, or a refusal naming every unmet check. * @example * ```ts * const r = await resolveCiEvidenceAtom(357, { storeRoot, executionRoot }); * ``` * @task T12634 */ export declare function resolveCiEvidenceAtom(prNumber: number, roots: EvidenceRoots, opts?: ResolveCiEvidenceOptions): Promise; //# sourceMappingURL=ci-evidence.d.ts.map