/** * Stage Chain Runner * * Sits on top of the DAG runner and executes the four-stage chain: * dev → staging → prod * * Contract (from the architectural discussion): * - Each stage is its own DAG. Within a stage, `Fix.requires` orders fixes * and skip-propagation marks dependents as `skipped` on prereq failure. * - Cross-stage gating is sequential-and-critical: if any fix in stage N * ends up `failed` or `manual-critical`, stages N+1..end run with every * fix auto-marked `skipped` and a shared reason "prior stage failed". * - canReach() routing is NOT consulted. Everything executes on the dev * machine; staging/prod scanfixes reach the server through the tunnel. * - The tunnel lifecycle (open/close) is owned by runStageChain, not by * individual scanfixes. When openTunnel throws, the whole stage is * synthesised as `skipped` and the chain breaks. * * scan.ts / fix.ts / deploy.ts don't call this yet — individual scanfixes * are migrating to `requires` first. The stage chain becomes the default * runner once every staging/prod scanfix that exists today has been * audited to work over the tunnel instead of on the server. */ import type { Fix, FactiiiConfig, Stage } from '../types/index.js'; import { type DAGResult, type FixOutcome } from './dag-runner.js'; import { openTunnel as realOpenTunnel, closeTunnel as realCloseTunnel } from './ssh-tunnel.js'; /** Default stage order. Callers can narrow it but not reorder. */ export declare const STAGE_ORDER: Stage[]; export interface StageChainOptions { config: FactiiiConfig; rootDir: string; /** When false (default), scan only. True flips on fix application. */ applyFixes?: boolean; /** Narrow the chain (e.g. ['dev', 'staging']). Must stay in STAGE_ORDER order. */ stages?: Stage[]; /** Called as each fix settles, inside its stage. */ onOutcome?: (outcome: FixOutcome, fix: Fix, stage: Stage) => void; /** Called when a stage finishes; receives the whole DAG result. */ onStageComplete?: (stage: Stage, result: DAGResult) => void; /** * Optional injectable tunnel functions. Production callers omit this and * get the real openTunnel/closeTunnel from utils/ssh-tunnel.ts. Unit tests * pass fakes so they don't need to monkey-patch the SSH module. */ tunnel?: { openTunnel: typeof realOpenTunnel; closeTunnel: typeof realCloseTunnel; }; } export interface StageChainResult { byStage: Map; /** True once any stage produced a failed outcome — downstream stages are auto-skipped after that. */ chainBroken: boolean; /** The stage that first broke the chain, if any. */ firstFailedStage: Stage | null; } /** * Execute the stage chain. Within each stage, fixes run as a DAG. Across * stages, a `failed` or `manual-critical` outcome breaks the chain — every * fix in subsequent stages is synthesized as `skipped`. * * For remote stages (staging, prod), runStageChain owns the tunnel * lifecycle: it calls openTunnel on stage entry (only if the stage has * fixes to run) and closeTunnel on exit, regardless of `applyFixes`. If * openTunnel throws, the entire stage skip-results with the tunnel error * in `reason` and the chain breaks. */ export declare function runStageChain(fixes: Fix[], options: StageChainOptions): Promise; //# sourceMappingURL=stage-chain.d.ts.map