/** * `pr:` evidence-atom validator. * * Resolves a GitHub pull request via the `gh` CLI and decides whether the PR * satisfies the `implemented`, `testsPassed`, and `qaPassed` gates (T9838). * Closes the release-verb dogfood gap (T9764): tasks that ship via the * standard PR + admin-merge flow previously had no zero-friction way to record * evidence retroactively — * `tool:test` re-runs the entire monorepo suite and `note:` is rejected for * hard gates on critical verifications. * * A PR atom counts as evidence when: * 1. `state === 'MERGED'` — the PR was actually shipped to main. * 2. `mergedAt` is non-null — defends against API races. * 3. Every required-workflow check has `conclusion === 'SUCCESS'` (or * `'SKIPPED'`) — there are zero `FAILURE` checks among the * required workflows configured by branch protection. * * Required-workflow list precedence (gh#1192 / T12104): * 1. `CLEO_PR_REQUIRED_WORKFLOWS` env var (operator override). * 2. `.cleo/project-context.json` `release.prRequiredWorkflows` (an explicit * empty array means NO required workflows — gh#1104). * 3. The TARGET repo's branch-protection required status checks, queried * via `gh api repos/{owner}/{repo}/branches/{branch}/protection/ * required_status_checks` and cached for 1h. * 4. {@link PR_REQUIRED_WORKFLOWS} built-in default (cleocode's own gates). * * Results are cached under `/.cleo/cache/evidence/pr-.json`, * keyed on `(prNumber, mergedAt)` so re-verifies skip the network round trip. * The branch-protection tier is cached separately at * `/.cleo/cache/evidence/pr-required-checks.json` (1h TTL). * * @task T9764 * @task T12104 * @epic T9762 * @saga T9758 */ import { type GhPrViewPayload } from '@cleocode/contracts'; /** * Result of resolving a `pr:` atom. * * On success carries the data needed to materialise an `EvidenceAtom` of * `kind: 'pr'`. On failure carries a human-readable reason plus the same * `codeName` vocabulary used by the rest of the evidence pipeline so error * presentation stays uniform. * * @task T9764 */ export type PrAtomResolution = { ok: true; prNumber: number; mergeCommitSha: string; mergedAt: string; successCount: number; totalChecks: number; cacheHit: boolean; } | { ok: false; reason: string; codeName: 'E_EVIDENCE_INVALID' | 'E_EVIDENCE_INSUFFICIENT' | 'E_EVIDENCE_TESTS_FAILED' | 'E_EVIDENCE_TOOL_FAILED'; }; /** * Filesystem location for the cached PR validation result. * * Path: `/.cleo/cache/evidence/pr-.json`. * * @task T9764 */ export declare function prCacheEntryPath(projectRoot: string, prNumber: number): string; /** * Function signature for fetching a GitHub PR's metadata. * * Returning `null` signals a fetch failure (binary missing, auth error, * malformed JSON). Returning a payload that fails {@link ghPrViewSchema} * also yields a `E_EVIDENCE_TOOL_FAILED` outcome. * * @task T9764 */ export type FetchGhPrPayload = (prNumber: number, cwd: string) => Promise<{ ok: true; payload: unknown; } | { ok: false; reason: string; }>; /** * Default `gh pr view` invocation. Calls `gh pr view --json * state,mergedAt,mergeable,headRefOid,statusCheckRollup` via `execFileSync` * for parity with the rest of the release module's `gh` usage. * * @task T9764 */ export declare const defaultFetchGhPrPayload: FetchGhPrPayload; /** * Resolve the list of required-workflow names for the current project. * * SYNC tiers only (most specific wins): * 1. `CLEO_PR_REQUIRED_WORKFLOWS` env var (comma-separated) — CI/operator * override. * 2. `.cleo/project-context.json` `release.prRequiredWorkflows` * ({@link PR_REQUIRED_WORKFLOWS_CONTEXT_KEY}) — committed per-project config. * An explicit empty array means NO required workflows (gh#1104). * 3. {@link PR_REQUIRED_WORKFLOWS} default (the cleocode contract-repo gates). * * The branch-protection tier (gh#1192) requires a `gh api` round trip and * therefore lives in the async {@link resolveRequiredWorkflowsDetailed}; this * pure function is kept for callers that cannot await. * * @task T9764 * @task T12014 (gh#1104) * @task T12104 (gh#1192) */ export declare function resolveRequiredWorkflows(env?: NodeJS.ProcessEnv, projectContext?: Record | null): string[]; /** * Which tier produced the required-workflow list. Surfaced in rejection * messages (gh#1198) so the reader can tell immediately whether the list is * their own configuration or a CLEO-side assumption. * * @task T12104 (gh#1192, gh#1198) */ export type RequiredWorkflowsSource = { readonly tier: 'env'; } | { readonly tier: 'project-context'; } | { readonly tier: 'branch-protection'; readonly repo: string; readonly branch: string; } | { readonly tier: 'default'; }; /** * The resolved required-workflow list plus the tier that produced it. * * @task T12104 (gh#1192, gh#1198) */ export interface RequiredWorkflowsResolution { readonly workflows: string[]; readonly source: RequiredWorkflowsSource; } /** * Human-readable description of a {@link RequiredWorkflowsSource}, embedded * verbatim in rejection messages as `(source: …)`. * * @task T12104 (gh#1198) */ export declare function describeRequiredWorkflowsSource(source: RequiredWorkflowsSource): string; /** * Function signature for querying the TARGET repo's branch-protection * required status checks via the `gh` CLI. Injectable for tests, mirroring * {@link FetchGhPrPayload}. * * Returning `ok: false` NEVER fails the atom — the caller falls back to the * {@link PR_REQUIRED_WORKFLOWS} built-in default. An ok result carries the * repo (`owner/name`) and branch the contexts were read from so the source * can be named in rejection messages. * * @task T12104 (gh#1192) */ export type FetchGhBranchProtection = (cwd: string) => Promise<{ ok: true; contexts: string[]; repo: string; branch: string; } | { ok: false; reason: string; }>; /** * Default {@link FetchGhBranchProtection}: reads the repo identity and * default branch via `gh repo view --json nameWithOwner,defaultBranchRef` * (falling back to `main` when the default branch cannot be determined), * then queries `gh api repos/{owner}/{repo}/branches/{branch}/protection/ * required_status_checks`. Both the classic `.contexts[]` entries and the * newer `.checks[].context` entries are collected and de-duplicated. * * A lookup that yields ZERO contexts (protection exists but requires no * checks) is treated as a lookup failure and falls back to the built-in * default — the explicit "no required workflows" lever remains the empty * `release.prRequiredWorkflows` array from gh#1104. * * @task T12104 (gh#1192) */ export declare const defaultFetchGhBranchProtection: FetchGhBranchProtection; /** * Filesystem location for the cached branch-protection required-checks list. * * Path: `/.cleo/cache/evidence/pr-required-checks.json`. * * @task T12104 (gh#1192) */ export declare function branchProtectionCachePath(projectRoot: string): string; /** * TTL for the branch-protection cache: 1 hour. Branch protection changes * rarely and verify runs cluster in time, so a short TTL keeps repeated * verifies off the API without going stale for long. The entry also records * the repo+branch it was read from, purely for diagnostics. * * @task T12104 (gh#1192) */ export declare const BRANCH_PROTECTION_CACHE_TTL_MS: number; /** * Full async required-workflow resolution (gh#1192). Precedence: * 1. env var → 2. project context → 3. branch protection (cached, 1h TTL) * → 4. built-in default. * * The branch-protection tier is a READ-ONLY hint: any lookup failure (offline, * no `gh`, no branch protection, not a GitHub repo) silently falls through to * the built-in default — the atom never fails because the protection lookup * failed. * * @task T12104 (gh#1192) */ export declare function resolveRequiredWorkflowsDetailed(projectRoot: string, opts?: { /** Env (defaults to `process.env`). */ readonly env?: NodeJS.ProcessEnv; /** Parsed `.cleo/project-context.json` (or `null`). */ readonly projectContext?: Record | null; /** Mock branch-protection fetcher for tests. */ readonly fetchGhBranchProtection?: FetchGhBranchProtection; /** When `true`, skip the branch-protection cache read (writes still happen). */ readonly bypassProtectionCache?: boolean; }): Promise; /** * Inspect the `statusCheckRollup` and decide whether the required checks * are green. * * Required-check names from {@link PR_REQUIRED_WORKFLOWS} match against * EITHER the rollup entry's `workflowName` (top-level workflow like * `"Lockfile Check"`) OR its `name` (individual job, like `"Contracts * Dep Lint"` which lives under `workflowName === "CI"`). GitHub branch * protection lists required contexts by job name OR workflow name, and * the rollup may surface them at either granularity. * * ## Per-required-entry success rule * * For each entry in {@link PR_REQUIRED_WORKFLOWS}: * 1. At least one rollup check must match by name OR workflowName. * 2. At least one matching check must have `conclusion === 'SUCCESS'`. * 3. The match's status must be `COMPLETED` (not pending). * * Workflow-level matches (e.g. `"CI"` matches every job under CI) are * intentionally LENIENT: when ANY job under the workflow is `SUCCESS`, * the workflow counts as satisfied even if other (non-required-by-name) * jobs inside the same workflow are `FAILURE`. This mirrors GitHub's * branch-protection behavior, which gates merge on the * `required_status_checks[contexts]` list — administrators may merge * past non-required job failures, and `pr:` evidence accepts that * reality. To enforce stricter rules, narrow the required list via the * {@link PR_REQUIRED_WORKFLOWS_ENV_VAR} env var to specific job names. * * @internal * @task T9764 * @task T12104 (gh#1198) */ export declare function evaluateRollup(rollup: GhPrViewPayload['statusCheckRollup'], requiredWorkflows: readonly string[], opts?: { /** * Human-readable source of the required list (see * {@link describeRequiredWorkflowsSource}), named in the missing-workflows * rejection so the reader knows which tier produced it (gh#1198). */ readonly requiredSource?: string; /** PR number, embedded in the missing-workflows rejection header. */ readonly prNumber?: number; }): { ok: true; successCount: number; totalChecks: number; } | { ok: false; reason: string; }; /** * Options for {@link resolvePrEvidenceAtom}. Allows test injection of a * mock `gh` wrapper plus an env override so tests do not have to mutate * `process.env`. * * @task T9764 */ export interface ResolvePrEvidenceAtomOptions { /** Mock `gh` wrapper for tests; defaults to {@link defaultFetchGhPrPayload}. */ readonly fetchGhPrPayload?: FetchGhPrPayload; /** Env (defaults to `process.env`). */ readonly env?: NodeJS.ProcessEnv; /** When `true`, bypass cache reads (PR-result cache and branch-protection cache). Cache writes always happen. */ readonly bypassCache?: boolean; /** * Parsed `.cleo/project-context.json` (or `null`). Tier between env and the * built-in default for required-workflow resolution (gh#1104). @task T12014 */ readonly projectContext?: Record | null; /** * Mock branch-protection fetcher for tests; defaults to * {@link defaultFetchGhBranchProtection}. Only consulted when neither the * env var nor the project context declares a required-workflow list * (gh#1192). @task T12104 */ readonly fetchGhBranchProtection?: FetchGhBranchProtection; } /** * Validate a `pr:` atom end-to-end. * * Steps: * 1. Cache lookup — return immediately on a fresh hit. * 2. Fetch `gh pr view --json …`. * 3. Schema-validate the payload. * 4. Reject non-merged PRs (`state !== 'MERGED'` or null `mergedAt`). * 5. Resolve the required-workflow list (env → project context → branch * protection → built-in default; gh#1192). * 6. Evaluate the status-check rollup against required workflows. * 7. Persist the result to cache and return. * * @param prNumber - PR number (positive integer). * @param projectRoot - Absolute path to the project root (for cache + cwd). * @param opts - Optional overrides for testing. * @returns Validation result envelope. * * @task T9764 * @task T12104 */ export declare function resolvePrEvidenceAtom(prNumber: number, projectRoot: string, opts?: ResolvePrEvidenceAtomOptions): Promise; //# sourceMappingURL=pr-evidence.d.ts.map