/** * Incident state machine + phase routing (Sprint 24). * * Wraps Sprint 19 setIncidentStatus with a GUARDED transition table: * invalid transitions reject with a typed error before any disk write. * * The state machine is deterministic: given (currentPhase, agentOutput), * the next phase is fully determined. No human-in-loop required for * happy-path autopilot (subject to Tier 2 risky-action gates that fire * inside executeAction). This is the integration capstone — no new * primitives, only orchestration. * * Re-open path (resolved → investigating) is the ONLY transition that * requires an explicit `reason` arg. Every other transition is implicit * from the agent's output. * * Phase transition diagram: * * ┌──────────────────────────────────────────────────────┐ * │ │ * ▼ │ * ┌───────────────┐ │ * │ investigating │ │ * └───────┬───────┘ │ * │ │ * diagnoser produces │ * nextActions with │ * ≥1 risky │ * │ │ * ▼ │ * ┌───────────────┐ │ * │ remediating │ │ * └───────┬───────┘ │ * │ │ * all proposed actions executed │ * + postcondition passed │ * │ │ * ▼ │ * ┌───────────────┐ ──── verifyResolution fails ─────────────────┤ * │ monitoring │ │ * └───────┬───────┘ │ * │ │ * verifyResolution.verified=true │ * for criteria.windowMinutes │ * │ │ * ▼ │ * ┌───────────────┐ ──── user re-opens (reason REQUIRED) ────────┘ * │ resolved │ * └───────────────┘ (auto-postmortem triggered by setIncidentStatus) * * At any phase: user issues `bober incident abort --reason [--confirm-rollback]` * ──────────────────────────────────────► aborted (terminal) * * Sprint 24 — src/incident/orchestrator.ts */ import { type SetStatusOpts } from "./timeline.js"; import { type IncidentId, type IncidentMetadata, type IncidentPhase } from "./types.js"; import { type ExecuteRollbackOpts, type RollbackResult } from "./rollback.js"; import { type ResolutionCriteria, type VerifyResolutionDeps } from "./resolution-verify.js"; import type { RiskyActionConfig } from "../orchestrator/deploy/resolve.js"; import type { BoberConfig } from "../config/schema.js"; export declare class InvalidTransitionError extends Error { from: IncidentPhase; to: IncidentPhase; reasonRequired: boolean; constructor(from: IncidentPhase, to: IncidentPhase, reasonRequired?: boolean); } export interface TransitionOpts { reason?: string; setStatus?: SetStatusOpts; } export interface DiagnosisNextAction { blastRadius: "safe" | "risky"; action?: string; requiresApproval?: boolean; } export interface DiagnosisResult { nextActions: DiagnosisNextAction[]; summary?: string; } export interface ApplyDiagnosisOpts { /** Override clock for tests. */ now?: () => Date; } export interface ApplyDeploymentDeployedEntry { status: "executed" | "failed"; } export interface ApplyDeploymentResult { executed: ApplyDeploymentDeployedEntry[]; } export interface ApplyDeploymentOpts { /** Forwarded to verifyResolution when monitoring transition is attempted. */ verifyDeps?: Omit; resolutionCriteria?: ResolutionCriteria; /** Skip verifyResolution call (test seam). */ skipVerification?: boolean; now?: () => Date; } export interface AbortOpts { reason: string; confirmRollback?: boolean; config?: RiskyActionConfig; rollbackOpts?: ExecuteRollbackOpts; now?: () => Date; /** Sprint 28 — optional BoberConfig for telemetry emit on incident-aborted. */ boberConfig?: BoberConfig; } export interface AbortResult { rollback?: RollbackResult; abortReportPath: string; } export declare function readIncidentMetadata(projectRoot: string, incidentId: IncidentId): Promise; export declare function transitionPhase(projectRoot: string, incidentId: IncidentId, toPhase: IncidentPhase, opts?: TransitionOpts): Promise; /** * Inspect a diagnosis output and transition the incident phase accordingly. * * If any nextAction has blastRadius='risky' → transition to 'remediating'. * If nextActions is empty (diagnoser found no remediation needed) → leave at * 'investigating'; the operator should call transitionPhase or `bober incident end`. * If nextActions has entries but all are safe → leave at 'investigating' (no * state-mutating actions are needed; diagnose further or end the incident). * * Returns { newPhase } — the phase AFTER the call. Callers can use this to * branch their own logic. */ export declare function applyDiagnosisOutcome(projectRoot: string, incidentId: IncidentId, diagnosis: DiagnosisResult, _opts?: ApplyDiagnosisOpts): Promise<{ newPhase: IncidentPhase; }>; /** * After deployment actions complete, transition the incident phase. * * If all actions executed successfully AND verifyResolution is available: * - call verifyResolution * - if verified=true → transition to 'monitoring' * - Returns { newPhase: 'monitoring', verified: true } * * Transitioning from monitoring → resolved is the caller's responsibility * (the CLI / orchestration layer drives the second transition after the * criteria window elapses). This function ONLY moves to monitoring. * * If verifyDeps is not provided or skipVerification=true: * - Transition to monitoring unconditionally (operator is asserting success). * * If any action failed: * - Stay in remediating (return { newPhase: 'remediating', verified: false }). */ export declare function applyDeploymentOutcome(projectRoot: string, incidentId: IncidentId, deployResult: ApplyDeploymentResult, opts?: ApplyDeploymentOpts): Promise<{ newPhase: IncidentPhase; verified?: boolean; }>; /** * Abort an incident at any phase. * * Writes .bober/incidents//aborted.txt with reason. * Writes .bober/incidents//abort-report.md with reason + rollback plan. * Transitions the incident to status='aborted' (terminal). * * IF confirmRollback=true: plans + executes rollback for executed-not-rolled-back * changes (each step goes through the deploy gate individually). * IF confirmRollback=false (default): does NOT execute any rollbacks. * * Silent rollback on abort is a footgun — confirmRollback=true is the explicit * opt-in. Without it, the operator is on the hook for manual cleanup. * * @throws Error if the incident is already aborted (abort is terminal). */ export declare function abort(projectRoot: string, incidentId: IncidentId, opts: AbortOpts): Promise; //# sourceMappingURL=orchestrator.d.ts.map