/** * Extracted helpers for the next-step command. * * Splitting these out of nextStepCommand.ts reduces that file to just the * top-level cmdNextStep dispatcher, keeping each module focused on a single * concern. */ import { type ObligationDef } from "audit-tools/shared"; import type { AnalyzerSetting, GraphEdge } from "audit-tools/shared"; import { type ArtifactBundle } from "../io/artifacts.js"; import type { AuditState } from "../types/auditState.js"; import type { RejectedDesignReviewSubmission } from "../types/designAssessment.js"; import { type AdvanceAuditResult } from "../orchestrator/advance.js"; import { decideNextStep } from "../orchestrator/nextStep.js"; import { graphEnrichmentUnresolvedAnalyzers } from "../orchestrator/hostInputPause.js"; import type { AnalyzerPlanEntry } from "../extractors/analyzers/types.js"; import type { ExternalAnalyzerCandidate } from "audit-tools/shared"; import type { ActiveReviewRun } from "../supervisor/operatorHandoff.js"; import { runAuditStep } from "./auditStep.js"; import type { ExternalAcquisitionAdvanceOptions } from "../orchestrator/acquisitionExecutor.js"; import { type AuditHostValidationWarning } from "./dispatch/hostHandoff.js"; import type { AuditHostIngestIssue } from "../validation/ingestIssueCodes.js"; /** * One poll attempt over a lane's bound submission path. Every gate that * consumes host/worker submissions narrows on `status`, so a malformed lane can * never hard-fail the whole next-step call (the 2026-08-06 design-review loss: * a SyntaxError thrown out of one lane destroyed the sibling lane's consumed, * not-yet-persisted results). */ export type SubmissionConsumeAttempt = { status: "ok"; value: T; path: string; } | { status: "absent"; } | { status: "malformed"; path: string; reason: string; }; /** * Read a lane's submission from the TOOL-COMPUTED path its emission bound — * never a name a host could type. `ok` when the file exists and parses; * `absent` on ENOENT-family errors; `malformed` when the file exists but is not * JSON — submitted content is the CALLER's to quarantine, never an * infrastructure failure. All other IO errors re-throw unchanged. */ export declare function tryConsumeSubmission(artifactsDir: string, lane: string): Promise>; export type NextStepParams = { root: string; artifactsDir: string; selfCliPath: string; timeoutMs: number; narrativeEnabled?: boolean; analyzers?: Record; graphLlmEdgeReasoning?: boolean; /** * External-analyzer acquisition gate (Slice D). Set by the real CLI next-step * path (`enabled:true` + global-`fetch` adapter); left unset by tests so the * acquisition executor stays a hermetic empty-marker no-op. */ externalAcquisition?: ExternalAcquisitionAdvanceOptions; since?: string; }; export type TerminalStepResult = { kind: "complete"; state: AuditState; bundle: ArtifactBundle; finalReportPath: string; triage?: import("audit-tools/shared").FrictionTriageDecision; } | { kind: "blocked"; state: AuditState; bundle: ArtifactBundle; reason: string; }; /** * The host-actionable outcome of one `next-step` deterministic fold — the * discriminated union `runDeterministicForNextStep` returns and `cmdNextStep` * renders (one branch per kind). Each audit `ObligationDef.execute` returns this * inside an `emit` outcome (or a `transition` carrying the reloaded bundle when * the fold continues). */ export type NextStepResult = { kind: "semantic_review"; state: AuditState; bundle: ArtifactBundle; activeReviewRun: ActiveReviewRun; selectedExecutor?: string | null; inProcessMadeProgress?: boolean; /** * Failures the just-completed ingest classified — a bound result that * never arrived, would not parse, or failed the contract. Carried to the * emitted step so the host is TOLD which items to repair instead of * receiving an identical workload with no statement of what went wrong. */ ingestIssues?: readonly AuditHostIngestIssue[]; /** * Advisory validation findings on results that WERE accepted. Sibling of * {@link ingestIssues}: informational only — an accepted-with-warning * result needs no repair and must not read as one that could not be * accepted. */ validationWarnings?: readonly AuditHostValidationWarning[]; } | { kind: "design_review_parallel"; state: AuditState; bundle: ArtifactBundle; } | { kind: "design_review_contract"; state: AuditState; bundle: ArtifactBundle; } | { kind: "design_review_conceptual"; state: AuditState; bundle: ArtifactBundle; } | { kind: "charter_extraction"; state: AuditState; bundle: ArtifactBundle; } | { kind: "charter_delta"; state: AuditState; bundle: ArtifactBundle; } | { kind: "charter_clarification"; state: AuditState; bundle: ArtifactBundle; } | { kind: "systemic_challenge"; state: AuditState; bundle: ArtifactBundle; } | { kind: "confirm_intent"; state: AuditState; bundle: ArtifactBundle; } | { kind: "intent_equivalence"; state: AuditState; bundle: ArtifactBundle; } | { kind: "analyzer_install"; state: AuditState; bundle: ArtifactBundle; unresolved: AnalyzerPlanEntry[]; } | { kind: "analyzer_consent"; state: AuditState; bundle: ArtifactBundle; pending: ExternalAnalyzerCandidate[]; } | { kind: "edge_reasoning"; state: AuditState; bundle: ArtifactBundle; candidates: GraphEdge[]; } | { kind: "critical_flow_fallback"; state: AuditState; bundle: ArtifactBundle; } | { kind: "synthesis_narrative"; state: AuditState; bundle: ArtifactBundle; } | { kind: "complete"; state: AuditState; bundle: ArtifactBundle; finalReportPath: string; triage?: import("audit-tools/shared").FrictionTriageDecision; } | { kind: "blocked"; state: AuditState; bundle: ArtifactBundle; reason: string; }; /** The kinds `runDeterministicForNextStep` can return, derived from the table's own keys. */ export declare const NEXT_STEP_RETURN_KINDS: readonly NextStepResult["kind"][]; /** * Finalization thrashing tolerance (ARC-b8fed771 / the finalization-cycle guard). * The deterministic fold may legitimately revisit a prior artifact state a bounded * number of times (e.g. a runtime_validation <-> synthesis ping-pong, or * filesystem-retry revision churn) before the canonical report is rendered; only * outrunning distinct states by THIS many revisits is a non-converging cycle. Kept * a single named constant — never inline the literal (HANDOFF approach-B mandate: * no magic numbers). */ export declare const FINALIZATION_CYCLE_TOLERANCE = 16; /** * Build the terminal step for a deterministic fold that has stopped advancing * (no actionable obligation, or a cycle guard fired). A rendered report is the * deliverable: if synthesis already produced one — or the state is formally * complete — present it instead of reporting the stopped fold as a bare * "blocked" failure. A completed audit must never surface as blocked just * because finalization kept churning (e.g. a runtime_validation <-> synthesis * ping-pong, or revision churn from filesystem retries) after the report was * written. With no report yet, the stop is a genuine block. */ export declare function buildTerminalStep(params: Pick, bundle: ArtifactBundle, state: AuditState, blockedReason: string): Promise; type AnalyzerConsentBranchResult = { action: "continue"; } | { action: "return"; result: { kind: "analyzer_consent"; state: AuditState; bundle: ArtifactBundle; pending: ExternalAnalyzerCandidate[]; }; } | { action: "fallthrough"; }; /** * Item B (consent surfacing) — the acquisition obligation's fold branch, * mirroring the analyzer-install consent fold exactly: * - nothing pending (acquisition off / this run's scoped grant covers every * applicable candidate / all decided) → run the deterministic acquisition * executor (`fallthrough`); * - a decisions submission arrived on the `analyzer_consent` lane * (`{ "": "granted" | "declined" }`) → persist the decisions into * session config (decisions durable, tokens never), fold them into the * in-flight acquisition options, and re-scan (`continue`); * - otherwise → emit the ONE batched operator-interactive offer step * (`return`), so applicable consent-gated candidates are never silently * skipped (the silent-fail-closed defect this program exists to fix). */ export declare function handleAnalyzerConsentBranch(params: Pick, bundle: ArtifactBundle, state: AuditState, analyzersRef: { value: Record | undefined; }): Promise; type GraphEnrichmentBranchResult = { action: "continue"; } | { action: "return"; result: { kind: "analyzer_install"; state: AuditState; bundle: ArtifactBundle; unresolved: AnalyzerPlanEntry[]; }; } | { action: "return"; result: { kind: "edge_reasoning"; state: AuditState; bundle: ArtifactBundle; candidates: GraphEdge[]; }; } | { action: "fallthrough"; }; /** * Handle the `graph_enrichment_executor` submission-polling block. * Checks for pending analyzer install decisions and edge-reasoning results. * Returns an action object: * - `continue` → caller should keep folding (already consumed a submission). * - `return` → caller should emit the embedded result to cmdNextStep. * - `fallthrough` → nothing submitted; run the deterministic executor. */ export declare function handleGraphEnrichmentBranch(params: Pick, bundle: ArtifactBundle, state: AuditState, analyzersRef: { value: Record | undefined; }, deps?: { runStep?: typeof runAuditStep; /** * Injectable so the analyzer-decisions branch is testable at all. The real * resolution asks the MACHINE which analyzers are installed, so a fixture * that needs `unresolved.length > 0` would pass or fail depending on the box * it runs on — a suite verdict must not depend on that. Same shape as the * final gate's injected runner: absent on every production call, where the * behavior is byte-identical to calling the real resolver directly. */ unresolvedAnalyzers?: typeof graphEnrichmentUnresolvedAnalyzers; }): Promise; type BranchActionResult = { action: "continue"; } | { action: "return"; result: { kind: "design_review_parallel"; state: AuditState; bundle: ArtifactBundle; }; } | { action: "return"; result: { kind: "design_review_contract"; state: AuditState; bundle: ArtifactBundle; }; } | { action: "return"; result: { kind: "design_review_conceptual"; state: AuditState; bundle: ArtifactBundle; }; }; type ConsumeArraySubmissionResult = { status: "absent"; } | { status: "ok"; value: T[]; path: string; } | { status: "quarantined"; quarantinePath: string; lane: string; reason: string; }; /** * Read a lane submission expected to be an array (or a top-level object * wrapping exactly one array-valued property, the tolerant unwrap). Accepts * either shape; any other shape is quarantined (never unlinked-and-discarded) * and reported with a reason. * * An accepted submission is NOT deleted here (P25-f) — the caller unlinks after * it has applied the value, so a submission is never destroyed before its * content has landed somewhere else. */ export declare function consumeArraySubmission(artifactsDir: string, lane: string): Promise>; type ConsumeObjectSubmissionResult = { status: "absent"; } | { status: "ok"; value: Record; path: string; } | { status: "quarantined"; quarantinePath: string; reason: string; }; /** * Read a lane submission expected to be a plain top-level object (a * key → value map, e.g. the analyzer decisions). A non-object value — null, * an array, a bare primitive — is quarantined with a stderr diagnostic rather * than left lingering at the bound path (where it used to make the emitting * step re-ask silently forever). An accepted file is NOT deleted here — the * caller unlinks after applying, so a crash mid-apply retains the submission * for the retry. */ export declare function consumeObjectSubmission(artifactsDir: string, lane: string): Promise; /** * Render the host-facing notice for a pending quarantined edge-reasoning * submission, naming the quarantined file and the shape error — so the * re-emitted edge_reasoning step tells the host its prior submission was * rejected and why, rather than silently asking again. Returns `undefined` * when there is nothing to report. */ export declare function renderEdgeReasoningRejectionNotice(artifactsDir: string): Promise; /** * Render a host-facing notice for any pending quarantined design-review * submissions matching the given passes, naming the quarantined file and the * shape error — so a re-emitted design-review step tells the host its prior * submission was rejected and why, rather than silently asking again. * Returns `undefined` when there is nothing to report. */ export declare function renderDesignReviewRejectionNotice(bundle: ArtifactBundle, passes: readonly RejectedDesignReviewSubmission["pass"][]): string | undefined; export declare function handleDesignReviewBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** The common action shape all four `runOmittableGate`-driven branches return. */ type OmittableGateAction = { action: "continue"; } | { action: "run_omit"; } | { action: "return"; result: { kind: TStepKind; state: AuditState; bundle: ArtifactBundle; }; }; type CriticalFlowFallbackBranchResult = OmittableGateAction<"critical_flow_fallback">; type IntentEquivalenceBranchResult = OmittableGateAction<"intent_equivalence">; type SynthesisNarrativeBranchResult = OmittableGateAction<"synthesis_narrative">; type CharterExtractionBranchResult = OmittableGateAction<"charter_extraction">; type CharterDeltaBranchResult = OmittableGateAction<"charter_delta">; type CharterClarificationBranchResult = OmittableGateAction<"charter_clarification">; type SystemicChallengeBranchResult = OmittableGateAction<"systemic_challenge">; /** * Handle the `synthesis_narrative_executor` submission-polling block. * Returns: * - `continue` → a narrative submission was consumed + applied (progress * made); re-scan on the reloaded bundle. * - `return` → a host turn is still needed (narrative enabled, none supplied * yet); emit the synthesis_narrative step. * - `run_omit` → narrative disabled; run the deterministic omit executor (it * writes the `status:omitted` marker, satisfying synthesis_narrative_current). * This MUST make progress, never a no-op reload — otherwise the obligation * stays actionable and the fold spins (the guards do not cover this branch). */ export declare function handleSynthesisNarrativeBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Handle the `intent_equivalence_executor` polling block (DD-9). Deviates from * `runOmittableGate` in ONE way: the consumed verdict is SCHEMA-validated here * and a mis-shaped submission is QUARANTINED with a stderr diagnostic (the * quarantine-loudly property) instead of being handed to the executor to crash * on. Returns: * - `continue` → a valid verdict was consumed + committed; re-scan. * - `run_omit` → a deterministic arm owns the resolution (baseline stamp / * gate-version-stale / structured delta) — run the executor, stay drainable. * - `return` → a prose-only delta awaits the host judge; emit the step. */ export declare function handleIntentEquivalenceBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Handle the `critical_flow_fallback_executor` submission-polling block. * The obligation is only ever selected when the deterministic flow inference * marked itself below the confidence bar (`critical_flows.fallback_required`), * so — unlike the synthesis-narrative / charter gates — there is NO autonomous * omit: the host (always the LLM, conversation-first) is expected to author the * enrichment. Returns: * - `continue` → a submission file was consumed + persisted; re-scan (structure * then re-stales + rebuilds critical_flows off the merged flows). * - `return` → no submission yet; emit the critical_flow_fallback host step. * `run_omit` is never returned (shouldOmit is constant-false). */ export declare function handleCriticalFlowFallbackBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Handle the `charter_extraction_executor` submission-polling block * (Phase C). Mirrors the synthesis-narrative branch: * - every per-kind lane present and valid → assemble+gate the merge via the * preferred executor (ingest), then `continue`; * - otherwise a `shallow` ceiling → `run_omit` (the deterministic executor * writes an empty `status:omitted` register — the conversation-first default, * no host turn); * - a `deep`/`deepest` ceiling with no submission yet → `return` the host step * that renders the charter-extraction prompt. */ export declare function handleCharterExtractionBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Handle the `charter_delta_executor` submission-polling block (Phase C.2 — * the INDEPENDENT delta-miner). Mirrors the charter-extraction branch: * - a pending `charter_delta` lane submission → route+gate it via the preferred * executor (ingest), then `continue`; * - otherwise, when the register is NOT `deltas_pending` (extraction omitted, or * found no subsystems to mine) → `run_omit` (the deterministic executor settles * the register — no host turn); * - a `deltas_pending` register with no submission yet → `return` the host step * that renders the charter-delta prompt for the independent miner. */ export declare function handleCharterDeltaBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Handle the `charter_clarification_executor` obligation (Phase D triangulation * loop). Mirrors the charter-extraction branch, but the loop is DETERMINISTIC — the * executor assembles asked/banked from the Phase-C `charter_register` deltas, so the * host turn only surfaces the VOI-ranked interactive queue for relay: * - a pending `charter_clarification` lane submission (host answers) → assemble via * the deterministic runner, then `continue`; * - a `shallow` ceiling OR zero attention → `run_omit` (the runner writes the * register autonomously — every question banks as a finding, no host turn); * - a `deep`/`deepest` ceiling WITH attention > 0 that has NOT yet produced a * register → `run_omit` first to COMPUTE the loop (partition/rank/gate/split); * - once the register exists with ≥1 interactive `asked` question and no answers * yet → `return` the host step that relays the VOI queue. */ export declare function handleCharterClarificationBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Handle the `systemic_challenge_executor` obligation (Phase E — the second-order * adversary loop-until-dry pass). Mirrors the charter-clarification branch: * - a pending `systemic_challenge` lane submission (an adversary round's findings) → * fold it via the deterministic runner, then `continue`; * - a `shallow` ceiling → `run_omit` (the runner writes an omitted register * autonomously, no host turn); * - a `deep`/`deepest` ceiling that has NOT yet produced a register → `run_omit` * first to compute the metrics digest + open the loop; * - once the register exists and has NOT converged → `return` the host step that * dispatches the next adversary round. * A converged register satisfies the obligation, so this branch is never reached for * it (the priority scan skips a satisfied obligation). */ export declare function handleSystemicChallengeBranch(params: Pick, bundle: ArtifactBundle, state: AuditState): Promise; /** * Execute one deterministic audit step and record its progress. Throws (with * cause) if the executor fails, preserving the existing throw-with-cause pattern. * `index` is the 0-based fold position (the transition counter), surfaced as the * 1-based `iteration` in the `deterministic-progress.json` marker a * filesystem-watching host reads. */ export declare function executeAndRecord(params: Pick, analyzers: Record | undefined, decision: ReturnType, index: number, lastSummary: string): Promise; /** * Pre-dispatch no-progress guard (ARC-b8fed771). * * Runs BEFORE a deterministic executor is dispatched. If the fold is about to * re-dispatch the SAME executor for the SAME obligation from an artifact-state * signature it has ALREADY dispatched that exact (executor, obligation) pair * from this run, the prior dispatch left the content-state unchanged (same * signature) — so dispatching it again cannot make progress and would spin. * Stop the fold with a terminal step instead of re-dispatching. * * The dispatch IDENTITY (signature + executor + obligation), not the signature * alone, is the recurrence key. A recurring signature across DIFFERENT executors * is legitimate: no-op-but-satisfying steps (auto-fix with nothing to fix, * syntax-resolution with no errors) leave the artifact content unchanged while * still advancing the obligation chain — those must NOT trip the guard. Only a * literal re-entry of the same executor on the same unchanged state is the * infinite loop this catches. * * This is the immediate-recurrence complement to `checkFinalizationCycle` (the * post-dispatch tolerance-based thrash detector across many executors): this * guard refuses to re-enter the SAME executor on a state it already failed to * advance, rather than waiting for the tolerance window to fill. Returns a * terminal-step result when the guard fires, or undefined to proceed. * * `dispatchedSignatures` is mutated: the current dispatch identity is recorded * so a later iteration that returns to this exact (state, executor, obligation) * trips the guard. */ export declare function checkNoProgressBeforeDispatch(ctx: { index: number; dispatchedSignatures: Set; params: Pick; bundle: ArtifactBundle; state: AuditState; selectedObligation: string | null | undefined; selectedExecutor: string | null | undefined; }): Promise; /** * Check for a finalization cycle: when fold transitions outrun distinct artifact * states by FINALIZATION_CYCLE_TOLERANCE, the deterministic executors are * revisiting states rather than progressing. Returns a terminal-step result * when a cycle is detected, or undefined when the run is still progressing. */ export declare function checkFinalizationCycle(ctx: { index: number; obligationTrail: string[]; seenStateSignatures: Set; tolerance: number; params: Pick; bundle: ArtifactBundle; state: AuditState; result: AdvanceAuditResult; selectedObligation: string | null | undefined; }): Promise; /** * Per-call execution dependencies threaded to every audit obligation executor. * Mirrors remediate-code's `RemediateCtx`: the shared engine stays agnostic; * audit-code picks its own `Ctx`. The refs carry the fold-local mutable state the * hand-rolled `for` loop kept in closures — the analyzer settings a decisions * file can update mid-fold, the last progress summary surfaced in the terminal * block, and the cycle-guard bookkeeping (transition counter + the no-progress / * finalization-cycle sets the two guards mutate). */ /** * The advisory payload one fold iteration classified but could not render: an * ingest that ends in a `transition` (accepted results for still-pending * tasks) returns before any emission, so its `validation_warnings` and * classified ingest `issues` would otherwise die with the outcome. The carry is * fold-local (a `{ value }` ref on {@link AuditNextStepCtx}), never persisted — * the ledger (`recordHostResultOutcomes`) remains the only durable record, and * this only defers the PROMPT statement of what it already recorded to the next * emission within the same call. */ interface FoldAdvisories { ingestIssues: AuditHostIngestIssue[]; validationWarnings: AuditHostValidationWarning[]; } interface AuditNextStepCtx { params: NextStepParams; analyzersRef: { value: Record | undefined; }; lastSummaryRef: { value: string; }; /** * Advisories an ingest classified on a fold iteration that ended in a * `transition` (see {@link runHostDelegationObligation}): the transition * returns before any emission, and the next ingest skips already-accepted * bindings, so without this carry the warnings and that fold's classified * issues never reach the emitted prompt. The next semantic-review emit * merges + consumes them (once — see {@link mergeFoldAdvisories}). */ foldAdvisoriesRef: { value: FoldAdvisories; }; /** * 0-based fold position == the hand loop's `index`. Incremented AFTER each * `transition` outcome (see `countTransitions`), so during any `execute` it * holds the index of the current iteration. The two guards read it as `index`. */ iterationRef: { value: number; }; /** Pre-dispatch no-progress guard state (ARC-b8fed771): dispatched identities. */ dispatchedSignatures: Set; /** Finalization-cycle guard state: distinct post-execute artifact signatures. */ seenStateSignatures: Set; /** Finalization-cycle guard state: obligation order, for the cycle report. */ obligationTrail: string[]; } /** The engine state audit folds on: the in-memory bundle (reloaded per transition). */ type AuditEngineState = ArtifactBundle; type AuditObligationDef = ObligationDef; /** * Build the audit obligation definitions in `PRIORITY` order. Each `execute` * relocates the corresponding arm of the hand-rolled `for` loop: * deterministic executors `transition` (fold), host-delegation / dispatch / * terminal points `emit` the host-actionable step. Selection stays single-sourced * (`deriveObligationState` reads `deriveAuditState`, and `decideNextStep` resolves * the executor for the selected id), so the obligation list cannot drift from the * priority scan it mirrors. */ export declare function buildAuditObligations(): AuditObligationDef[]; /** * Drive the deterministic fold for one `next-step` call. * * Structure mirrors remediate-code's `decideNextStepLoop` (the proven engine * consumer): a PREAMBLE (the `index===0` file-integrity re-intake, the analog of * remediate's `forceReplan`) then the shared `advance` running audit's `PRIORITY` * obligations. Each deterministic executor `transition`s (folding the whole chain * into one host round-trip); host-delegation / dispatch / terminal obligations * `emit` the host-actionable step. * * Cycle detection stays in audit's `Ctx` (the pre-dispatch no-progress guard + * the FINALIZATION_CYCLE_TOLERANCE finalization-cycle guard, both invoked from * inside `runDeterministicExecutor`), NOT in `advance.opts.stateSignature` — the * shared engine is inherently 0-tolerance and cannot express the tolerance window * or the no-metadata-skip (HANDOFF approach B). `advance`'s `maxTransitions` is * left as its pure runaway backstop. A `step === null` result (no actionable * obligation, e.g. synthesis flipped the state to complete) resolves to the * terminal step (present_report when a report is rendered, else blocked). */ export declare function runDeterministicForNextStep(params: NextStepParams): Promise; export {}; //# sourceMappingURL=nextStepHelpers.d.ts.map