Imported from kunchenguid/no-mistakes (
.agents/skills/pipeline-review-and-agents/SKILL.md). Install upstream withnpx skills add kunchenguid/no-mistakes --skill pipeline-review-and-agents. Copyright stays with the author.
Review-Loop Agent Sessions (internal/pipeline/sessions.go)
- Per run, the review loop keeps ONE durable fixer session across review-fix turns, and EVERY review turn (initial review and every full rereview) runs session-free. A rereview certifies fixes implementing the previous review turn's findings, so resuming any review session seats the prescriber as certifier - the mechanism that let one fix round ship wrong code plus the test blessing it with zero findings. Cross-round review context travels only in the explicit sanitized round history; the fixer session is never lent to review turns, no other step uses sessions, and sessions are keyed strictly by run. The rereview prompt reframes fix-round changes as pipeline-authored code under the author-grade adversarial standard (
fixRoundProvenanceClause); the same clause is emitted on a later run's initial review when a persisted uncertified range is bound. Prior findings, fix summaries, and same-round tests are claims, not evidence. - Fail-safe rules: unsupported adapter runs cold; a failed fixer resume drops the identity and re-runs the same turn in a fresh fixer session, never skipping the turn; a cancelled ctx gets no fallback retry;
session_reuse: falseforces everything cold. Persistence is minimum metadata only, never prompts or transcripts;SessionRoleReviewerremains only so crash recovery accepts legacy persisted rows, which are never resumed. codex exec resumehas a narrower flag surface thancodex exec, so an unsupported override fails the resume and falls back; the e2e fakeagent must keep parsing both codex argv shapes (extractCodexPrompt).- Regressions:
internal/pipeline/sessions_test.go,internal/pipeline/steps/review_session_test.go(incl.TestReviewLoop_RereviewNeverResumesTheSessionThatPrescribedItsFixes),TestReviewStep_RereviewTreatsFixRoundsAsPipelineAuthoredCode,internal/agent/session_test.go.
Recorded Human Decisions on Findings
- Approve, skip, and abort each record
selected_finding_ids = "[]"plusselection_source = user_declinedon a gated round with findings (executor.gorecordDeclinedRound,db.SetStepRoundDeclined); a round with no findings records no decision. The conditional write must never erase an existing selection. User-facing semantics are owned bydocs/src/content/docs/reference/pipeline-steps.md. - A fix response accounts for every finding the gate shows: each must be in
--findingsor--ignoreunless an earlier user round of the same step already decided it, IDs the gate does not show and IDs in both lists are refused, and the gate stays parked (splitFixResponse,internal/pipeline/gate_response.go). The decline set is NOT stored: it stays the complement ofselected_finding_ids(PR #790 decision B - no decline list, no column, no table), derived on read bydeclinedFindingLines, soSetStepRoundUserDecisionis unchanged.--ignorenaming a finding an earlier user round of the same step chose to fix is refused (RespondRefusal.DeclinedEarlierFix,ipc.RespondResult.DeclinedEarlierFix) because reverting an applied fix is out of scope for a gate response: a caller keeps that decision by omitting the finding, and the TUI and the e2e harness retry once with those IDs dropped from their decline set whileaxi respondreports the refusal. Sticky: a finding a user round chose to fix is never rendered underuser_chose_to_ignoreby a later omission or a later approve/skip/abort (renderRoundHistoryEntry/withChosenToFix), and a later response cannot decline it at all. Anauto_fixcomplement is still findings awaiting a decision (rendered underauto_fix_left_unselected), never a human decision. The gate-side help,axi respond --help, and every successful response'srecorded:echo (ipc.RespondResult) are owned by the same contract; user-facing semantics are owned bydocs/src/content/docs/reference/pipeline-steps.mdanddocs/src/content/docs/reference/cli.md. - A fix response is durable BEFORE its dispatch:
persistResponseDecision(executor.go) writes the normalized selection and the merged payload while the gate is still parked, so therecorded:echo never claims a decision the next gate cannot read, and a failed write refuses the response with the gate still parked. That write is the ordering guarantee, not a second source of truth: the park-time record is what a daemon death between the write and the fix round leaves behind, and recovery re-parks that same round, so the response that answers it is the only dispatch the decision will ever get.restorePendingDecisionfolds the recorded selection, its per-finding instructions, and its user-authored findings back into that response; it is called with the round's own recorded payload (UserFindingsJSONfiltered toselected_finding_ids), which is why a nonemptypreviousmeans "recorded but never dispatched". A restored gate finding is echoed underfixed, never also underkept(excludeFindingIDs), and an addition the new response restates by content is restored once under the recorded identity (resolveAddedFindingIDsreserves the recorded IDs, so the restatement gets a fresh one and would otherwise double the item). roundHistoryPromptSection(internal/pipeline/steps/round_history.go) now carries three parts: this step's rounds, this run's OTHER steps' decisions, and earlier runs' decisions on this branch (bound per step bypipeline.BindBranchDecisions, unlike review-onlyBindUncertifiedPipelineRange). Nothing clears branch decisions - a completed review deletes the uncertified range, which is why that channel could not carry a decision forward, but approving a gate IS the decision. The prompt states that a recorded decision SUPERSEDES the user-intent wording. Both the cross-step/branch preamble and the own-step prefix carry the respect rule as plain instruction text: never revert, undo, or work around a recorded human decision, and never re-implement a decline…axi respond --instructionsis an agent driver's fix guidance, not a human decision: the CLI setsipc.RespondParams.InstructionsFromDriver, the executor routes it throughpipeline.FixInstructionsintotypes.Finding.DriverInstructions(driver_instructions, kept apart from the TUI'suser_instructions, including acrossrestorePendingDecision), the own-step history names it viadriverInstructionsNote, andwithoutDriverInstructionsdrops it everywhere else so it is never carried as the user's choice: the cross-step and branch decision lines, and every non-own-step round-history rendering (renderRoundHistoryBlocks(..., ownStep=false): the superseded-run and uncertified previous-run sections).--instructionskeys only on gate finding IDs, soparseAddFindingrefusesdriver_instructionsin--add-findingJSON; no added finding can carry a driver note. The selection itself stays a user round. Regressions:TestParseAddFinding,TestExecutor_DriverInstructionsStayApartFromUserInstructions,TestDriverInstructionsAreNeverPresentedAsTheUsersDecision,TestEarlierRunRoundsNeverCarryDriverInstructions,TestAxiRespond_InstructionsAreMarkedAsTheDriversGuidance.- Deliberately ADVISORY and fail-open: no step is blocked and no commit is gated, so an agent may still re-raise a declined finding when the code genuinely changed. There is no reversion detector;
assertPipelineHeadContinuityandassertReviewApprovedPushHeadremain lineage-only.ci_fix.gocomposes the section like the other fix-capable steps - beforeuserIntentPromptSection, because the run's frozen intent always predates a later gate's decision - so a CI repair sees the decision an earlier gate, an earlier run on this branch, or its own gate recorded (internal/pipeline/steps/ci_decision_history_test.go).rebase.gostill builds its prompt withoutroundHistoryPromptSection. - Regressions:
TestExecutor_GateResolutionsWithoutASelectionRecordTheDecline,TestExecutor_GateResolutionWithNoFindingsRecordsNoDecision,TestExecutor_FixResolutionStillRecordsAUserSelection,internal/db/round_decisions_test.go,TestDeclinedFindingReachesALaterStepInTheSameRun,TestDeclinedFindingReachesALaterRunOnTheSameBranch,TestCompletedReviewDoesNotClearBranchDecisions,TestAutoFixComplementIsNeverPresentedAsAUserDecision,TestDocumentPromptCarriesRecordedHumanDecisions,TestLintAgentPassPromptCarriesRecordedHumanDecisions,TestLintFixPromptCarriesRecordedHumanDecisions,TestTestPromptsCarryRecordedHumanDecisions,TestOwnStepHistoryPromptCarriesNeverRevertClause,TestRoundHistory_OmittedCarriedFindingIsNotRecordedAsIgnored(the incident reproduction),TestRoundHistory_DeclinesRenderAsTheSelectionComplement,TestRoundHistory_ALaterSelectionNeverRediscardsAnEarlierFix,TestRoundHistory_ApproveAfterAnEarlierFixDoesNotDeclineThatFix,TestSplitFixResponse_*(incl.TestSplitFixResponse_RefusesDecliningAFindingChosenToFixEarlier),TestExecutor_FixResponseRefusalLeavesTheGateParked,TestExecutor_FixResponseKeepsEarlierDecisionsAndRefusesTheirReversal,TestExecutor_RecoveredGateValidatesAndRecordsTheDecision,TestAxiRespond_RendersARefusalWithTheUnaccountedIDs,TestAxiRespond_RendersTheRefusalOfAnAlreadyFixedDecline,TestAxiRespond_EchoesRecordedDispositionsAndForwardsIgnores,TestModel_RespondFixKeepsAnEarlierFixOutOfTheDeclineSet,TestExecutor_RecoveredRoundRestoresTheAcceptedAddition,TestExecutor_ReansweredRoundPreservesAcknowledgedFindingContent.
Uncertified Review Provenance (internal/pipeline/uncertified.go)
- When a review-step fixer round commits and its re-review does not complete, persist the per-branch uncertified range (
from_sha,to_sha). Persist on review-step fixer commits only, not lint or document. On the next run's initial review, bind that range and emitfixRoundProvenanceClauseeven whenFixing==false, so the replacement reviewer is not cold. Rerun proceeds; there is no refusal or--ack-uncertified-reviewgate. - Missing git objects warn and continue, never block. Clear the range only after a completed review whose approved head equals or is a descendant of
to_sha; parked, failed, skipped, and aborted reviews must not clear it. Rebase remaps the persisted SHAs onto the rewritten head so the next review can still bind. - Regressions:
internal/pipeline/uncertified_test.go,TestCommitAgentFixes_PersistsUncertifiedRangeForReview,TestCommitAgentFixes_LintDoesNotPersistUncertifiedRange,TestCommitAgentFixes_DocumentDoesNotPersistUncertifiedRange,TestFixRoundProvenanceClause_EmitsForUncertifiedRangeWhenNotFixing,TestUncertifiedRange_PersistsThenFeedsNextInitialReview,TestRebaseStep_RemapsUncertifiedRangeWhenHeadRewritten.
Authorization and Privacy Tracing (internal/pipeline/steps/review.go)
- Authorization/privacy is a conditional obligation inside the existing Review pass, never a separate turn, gate, finding type, status, certification, or default-cost surface. For changed behavior involving potentially protected resources or user data, trace identity, the earliest shared authorization boundary and alternate callers, ownership/role/tenant scope, public serialization, secondary disclosures, and fail-open or stale-context paths.
- Policy remains repository-owned through project instructions and trusted
review.path_instructions. Findings require a concrete source-backed reachable operation or disclosure and name the protected resource/field, missing control, and impact; accept equivalent controls and intentionally public data rather than keyword-matching auth machinery. A material policy ambiguity introduced by changed behavior must beask-userand name the missing policy decision; do not report immaterial or pre-existing ambiguity, and retainauto-fixfor source-proven routine defects. - Docs owner:
docs/src/content/docs/reference/pipeline-steps.md. Prompt regression:TestReviewStep_AuthorizationPrivacyTracingContract. Development-only qualitative cases:internal/pipeline/steps/testdata/authorization_privacy_review/.
Intended-Usage Evidence Threshold (internal/pipeline/steps/review.go)
- A review finding needs a concrete sequence that occurs during the change's intended usage. A rare but real sequence those callers actually perform still qualifies. A finding whose only supporting path is a hypothetical unused execution that intended callers, the public API, or documented usage never take does not. This is an evidence threshold, not a general "be less noisy" rewrite.
- Docs owner:
docs/src/content/docs/reference/pipeline-steps.md. Prompt regression:TestReviewStep_IntendedUsageEvidenceContract. Development-only qualitative cases:internal/pipeline/steps/testdata/intended_usage_review/.
Simplification Pass and Fix-Through-Removal (internal/pipeline/steps/review.go, internal/pipeline/steps/common_fix.go, internal/pipeline/steps/lint.go, internal/pipeline/steps/ci_fix.go)
- The review prompt carries a dedicated
Simplificationsection betweenRulesandRisk assessment. It asks a different question from the defect pass: not "is this component correct" but "does the intent strictly require this component". Every component the change introduced (branch, acceptance/matching path, fallback, alias, mode, flag, option, second definition of a concept already defined once, parallel copy of a rule) is judged against the user intent, or the change's own stated purpose when none is given. An unrequired component is awarningwith actionask-userwhose remedy is removal, never hardening, validating, or documenting; a defect finding inside such a component must name removal as its remedy. Refactor-only "simplification opportunities" keep their existing non-feature-removing meaning and point at the section instead of contradicting it. No schema field, detector, or second reviewer. fixerPromptapplies the removal rule at the shared Review, Test, and configured-Lint repair boundary; Lint's no-command safe-fix pass and the CI repair prompt apply the same rule directly. A finding resolvable by removing a code path the intent does not strictly require is fixed by removing it. The Review-specific anti-revert guard protects only intent-required code; doubt about whether the intent requires a path leaves it alone and reports the finding unresolved.- Reference case: backpass PR #107 (https://github.com/kunchenguid/backpass/pull/107), where a permissive target resolver and a second skill-only budget semantics each cleared the intended-usage evidence threshold every round and were hardened for thirteen rounds without converging; one deleted acceptance branch would have closed the class. The intended-usage threshold (#948) is about whether a defect is real; this pass is about whether the component should exist.
- Docs owners:
docs/src/content/docs/reference/pipeline-steps.md(Review, shared repair behavior, CI fixer) anddocs/src/content/docs/concepts/auto-fix.md(finding actions). Regressions:TestReviewStep_SimplificationSectionContract,TestReviewStep_FixPromptPrefersRemovalOfUnrequiredPaths,TestTestStep_FixMode,TestLintStep_FixMode_CommitsChanges,TestLintStep_NoConfiguredLint_UnresolvedFindingsNeedApprovalWithoutAutoFixLoop,TestCIStep_FixPromptPrefersRemovalOfUnrequiredPaths,TestReviewStep_SimplificationFixturesApply. Development-only qualitative cases:internal/pipeline/steps/testdata/simplification_review/.
Review Fixer Verification Discipline (internal/pipeline/steps/review.go)
- The review-fix prompt requires all fixes before one focused verification limited to the changed area and forbids the whole repository test/lint suite during the fix round. A missing verification tool never licenses fewer edits: the prompt and the shared steering sentence ("stop searching there") say so, the fix result schema (
fixSummarySchema) carries an optionalverification_blocked, andlogBlockedVerificationlogs an unverified commit or a no-edit round that cited a missing tool. A fixer that runsgit commititself leaves the worktree clean, so the log decides committed-vs-no-edit from HEAD movement across the fixer turn rather than from the pipeline's own commit step. The dedicated Test and Lint steps are the authoritative gates, although their coverage may be focused when commands are unconfigured. This is a prompt contract, not an enforced sandbox. Regressions:TestReviewStep_FixMode_FocusedVerificationContract,TestReviewFixPromptKeepsEditsWhenVerificationToolIsMissing,TestReviewFixLogsUnverifiedCommit,TestReviewFixLogsNoEditRoundThatCitesMissingTool,TestReviewFixLogsUnverifiedCommitWhenFixerCommittedItself,TestReviewFixLogsNothingExtraWhenFixerCommittedItselfAndVerificationRan,TestReviewFixLogsNothingExtraWhenVerificationRan,TestBlockedVerificationBoundsAndNormalizes(internal/pipeline/steps/fix_verification_blocked_test.go).
Remedy-Scope Discipline in the Review Prompts (internal/pipeline/steps/review.go)
- Three prompt rules keep review findings and fix rounds from growing machinery nobody scoped, using only the existing action vocabulary and gate. No detector, no schema field, no second scope reviewer: a growth detector or scope verifier is itself the machinery being prevented, and a second judgment owner on the gate contradicts VISION's "never stacked".
- Reviewer classifies by REMEDY, not only topic: a finding whose smallest honest remedy would add durable state, a schema change, background/retry/persistence machinery, a new subsystem, or otherwise EXTEND rather than CORRECT the change must be
ask-user, with the description naming the remedy as what needs authorization. This ridesActionOrDefault's established fail-toward-the-human direction. - The fixer's unit of work is the INVARIANT a finding violates, at every sibling site in the changed area (axis, direction, representation; sibling call path, command, action, state transition; other consumer of the same input, field, or record), closed in the same round with the same small correction or at one shared boundary; it then re-traces each finding's failing sequence and the ordinary path through every changed function and its callers, and removes what the fix made unreachable. Depth is reached by simplifying an architectural reason rather than bolting on machinery for the symptoms: closing sibling sites is the fix, handling/state/fallbacks/retries/a subsystem is not. The preceding local-defect-vs-deeper-flaw diagnosis rule stays. Neither superseded wording may return: "fix the deepest practical cause instead" bred machinery, and "fix the reported instance narrowly" (PR #892) bred sibling follow-ons - on SSHHIP PR #462 and seven local runs, 58-65% of rereviews reported the sibling site the previous fix left behind or a regression the fix made. Every CI repair prompt path, including merge-conflict-only repair, carries the same three rules through
ciFixerClassRules(ci_fix.go). - Reviewer reports a defect CLASS once: the finding anchors to one primary file:line and enumerates every sibling site in its description (an incomplete-validation finding lists every consumed field still unvalidated). Prose only - no schema field - so the carry-forward set and finding identity are unchanged. The rereview additionally labels a follow-on as a follow-on (which round, and whether the fix introduced, left behind, or moved the defect) and lists the remaining siblings; emitted in both
fixRoundProvenanceClausebranches before the revert exit ramp. - Rereview gets one exit ramp from the fix-round ratchet: defects in code a PRIOR fix round introduced that exceeds what the original finding required become a single
ask-userfinding recommending that round be reverted to the minimal fix, instead of further repairs layered on it. Emitted in bothfixRoundProvenanceClausebranches (this run's fix rounds and a previous run's uncertified fixer commits); conditioning on prior-round code keeps it off ordinary multi-round fixes. - Docs owners:
docs/src/content/docs/concepts/auto-fix.md(finding actions) anddocs/src/content/docs/reference/pipeline-steps.md(Review). Regressions:TestReviewStep_PromptClassifiesFindingsByRemedyScope,TestReviewStep_FixPromptPrefersSimplificationOverMachinery,TestReviewStep_RereviewOffersRevertExitFromPriorRoundMachinery,TestReviewStep_FixPromptClosesTheInvariantAcrossSiblingSites,TestReviewStep_ReviewPromptReportsTheClassOnce,TestReviewStep_RereviewNamesFollowOnsOfPriorFixRounds,TestCIStep_FixPromptClosesTheInvariantAcrossSiblingSites. Qualitative fixtures:internal/pipeline/steps/testdata/class_fix_review/(kept applicable byTestReviewStep_ClassFixFixturesApply).
Agent-Invocation Timeouts Report Measured Silence, Never the Budget
- Configured
agent_timeout/review_agent_timeout/test_agent_timeoutare silent budgets, and absolute cutoffs while the matching still-working cap is unset (the default): every invocation stops there, and the bound readssilent budget; no still-working cap is setwith no idle or child claim, because nothing checked either. Once the cap is set they become stall budgets: a silent invocation is cancelled there, while one still producing output, or whose agent subprocess has a live child missing from the helper baseline (agentActivity.waitingOnChild, because a long tool call such as a test suite emits no bytes), continues untilAgentTimeoutIdleGraceof quiet with no live child (the fixedconfig.DefaultStepQuietWarning, not the configuredstep_quiet_warning) or the configured still-working cap (agent_working_timeout,review_agent_working_timeout, ortest_agent_working_timeout); an unset cap does not extend the turn. With the cap set, provider slowness cannot fail a turn whose work already validated.sampleChildrenreadsprocreap.LiveDescendantsperiodically while a budgeted agent runs. Every output, the first included, freezes the latest sample completed at leastchildSampleSettleBEFORE it as the helper baseline; that baseline is never refreshed during a quiet stretch, and until one freezes every live descendant counts. A snapshot taken after an output (including a "permanent" set sampled after the first output) catches a tool spawned right after its announcement and cuts the #1109 case, and whole-secondps etimeordering cannot separate a helper started just before output from a tool started just after, so do not return to either; a helper started within one sample interval before a first output followed by a hang is bounded by the still-working cap instead. Do not raise the shipped 30m default; hung agents must still fail closed at the stall budget. Intent extraction keeps its five-minute absolute cap (existing parent deadline is honored, not extended). - A timeout diagnostic may only state what was observed, never restate the configured budget as measured silence.
pipeline.AgentBudgetBoundowns the bound wording (unset-cap silent budget, stall budget, or still-working cap, plus elapsed time) that Review and Test messages reuse. Only the applied budget's own cause earns a diagnosis; an operator abort, supersede, or shutdown cause is returned verbatim.agentActivityinagent_run.gois the single owner of the measurement and resets per-attempt evidence whenever a retry or fallback starts a replacement attempt, including provider, session-resume, and OpenCode prompt-format fallbacks. A substantive adapter error (a native agent's exit status plus captured stderr) is URL-redacted, length-bounded, and appended asagent reported: .... - Observed output is streamed assistant text plus throttled
agent.LifecyclePhaseActivity, sourced from every non-empty read of a native subprocess's stdout or stderr. Prose alone cannot prove liveness: verified against pi 0.84.3, a tool-using turn emits onlytool_execution_*/toolcall_*and notext_deltauntil the very end, and no adapter forwards those toOnChunk. Subprocess start and exit are deliberately NOT output - start proves launch, not work, and exit is the deadline's own consequence, so counting either would recreate the fabricated evidence. - The executor consumes
LifecyclePhaseActivityinto step activity only, never the step log:axi statusneeds the liveness, and a half-hour turn would otherwise emit hundreds of log lines. - A CI auto-fix agent that exhausts its budget parks at an ask-user gate (
ciFixAgentTimeoutOutcome) instead of being logged as a warning and re-issued on the next poll. That old path spent up toauto_fix.cifull budgets invisibly untilci_timeout. Onlypipeline.ErrAgentTimeoutparks; other fix failures keep warn-and-retry. Review deliberately still fails the run rather than parking - Push commits leftover worktree changes, so an approved park would ship a half-finished, unreviewed fix. - Each Review fixer and each independent session-free reviewer owns a fresh
review_agent_timeoutstall budget. A silent turn is cancelled there; a still-working turn may continue until idle or the configuredreview_agent_working_timeout; an unset cap does not extend the turn. Prompt preparation and post-fixer commits stay on the step parent context, and an expired candidate context stops the fallback chain before another provider is announced or instrumented. - The budget
reviewAgentTimeoutreads is the merged, trusted value: a repository may set trustedreview.agent_timeoutto tightenreview_agent_timeoutfor its own review, never to loosen it (mergeininternal/config/config.gotakes the smaller of the two and marks it configured for crash-retry purposes throughConfig.ReviewAgentTimeoutTightened, whichConfig.AgentTimeoutConfiguredForapplies to Review only, so Document, Lint, and Test keep their default-budget crash retry); an unset, looser, or unparsable repository value leaves the global budget in force, and a pushed branch cannot set or alter it since only the trusted copy is read. Trust selection is owned by therepository-routing-securityskill; user semantics are owned bydocs/src/content/docs/reference/repo-config.md(review.agent_timeout). Regressions:TestReviewAgentTimeout_RepositoryTightensGlobal,TestReviewAgentTimeout_RepositoryCannotLoosenGlobal,TestReviewAgentTimeout_AbsentKeepsGlobal,TestReviewAgentTimeout_PushedBranchCannotAlterIt,TestReviewAgentTimeout_RejectsUnusableValues,TestReviewAgentTimeout_HandBuiltInvalidValueNeverLoosens,TestReviewAgentTimeoutUsesTrustedRepositoryLimit,TestExecutor_AgentCrashRetryRepoReviewTimeoutScopedToReview. - Docs owners:
docs/src/content/docs/reference/global-config.md(agent_timeout,review_agent_timeout) for the diagnostic and ownership vocabulary,docs/src/content/docs/reference/repo-config.md(review.agent_timeout) for the repository tightening key,docs/src/content/docs/reference/pipeline-steps.md(Review and CI) for step behavior. Regressions:TestRunAgent_Timeout*,TestRunAgent_StreamingPastStallBudgetSucceeds,TestRunAgent_SilentAgentStopsAtTheSilentBudget,TestRunAgent_StreamingAgentHitsTheWorkingCap,TestRunAgent_SubprocessStartAloneIsNotObservedOutput,TestRunAgent_OperatorCancellationIsNotDressedUpAsAnAgentFault,TestRunAgent_OperatorAbortWithACauseIsNotDressedUpAsABudgetCut,TestRunAgent_AgentWaitingOnALiveChildOutlastsTheStallBudget,TestRunAgent_HelperStartedBeforeTheLastOutputDoesNotExtendTheBudget,TestRunAgent_HelperStartedJustBeforeTheFirstOutputDoesNotExtendTheBudget,TestRunAgent_ToolAfterAnOnlyOutputBeforeAnySampleStillExtends,TestRunAgent_LaunchedAgentWithoutAChildIsCutAtTheStallBudget,TestPiAgent_ToolOnlyStreamStillReportsSubprocessLiveness,TestPiAgent_SilentSubprocessReportsNoLiveness,TestExecutor_SubprocessLivenessUpdatesActivityWithoutFloodingTheStepLog,TestCIStep_FixAgentBudgetExhaustionParksForADecisionInsteadOfRetrying,TestCIStep_FixAgentTimeoutRecordsCommittedRepair,TestCIStep_FixAfterATimedOutRepairRevalidatesTheRecordedCommit,TestCIStep_FixAgentCutMidRebaseRecordsNoPartialHead,TestCIStep_NonTimeoutFixFailureKeepsRetrying,TestReviewStep_EachAgentInvocationGetsItsOwnBudget,TestReviewStep_StreamingPastStallBudgetCompletes,TestReviewStep_WallClockTimeoutPreservesTheAgentReport, e2eTestSilentAgentTimeoutReportsMeasuredEvidence.
Local Test Is Targeted Validation (internal/pipeline/steps/test.go)
- Local Test (normal evidence agent and Test-repair agent) validates the requested intent with the smallest relevant checks and end-user-aligned evidence; it is never a repository-wide regression-suite walk.
Broad regression belongs to remote CI (
go test -race ./...in.github/workflows/ci.yml) and remains mandatory before a PR is ready.commands.testis the same contract when set: targeted baseline, not CI-parity complete-suite configuration; docs owner isdocs/src/content/docs/reference/repo-config.md(commands.test), step behavior owner isdocs/src/content/docs/reference/pipeline-steps.md(Test). This repository dogfoods an emptycommands.testso the agent-driven targeted path is the default; do not reintroducego test -race ./...as a local Test override. Trustedtest.prepareis the explicit eager opt-in for calling the sharedensurePreparedlifecycle before the first agent-only Test turn whencommands.testis empty. A successful worktree receipt is reused after recovery and by later configured commands; failures and restoration failures stop before the agent, while evidence remains fresh and verdict policy remains unchanged. Trust selection is owned by therepository-routing-securityskill; user semantics are owned bydocs/src/content/docs/reference/repo-config.md(test.prepare) anddocs/src/content/docs/reference/pipeline-steps.md(Test). Regressions:TestAgentOnlyPreparation_*,TestTestPrepareTrustAndMerge. Trustedtest.base_attribution(test_base_attribution.go) re-runs only a FAILING configuredcommands.teston the run's base SHA in agit clone --sharedscratch checkout outside the run worktree and gate (runningcommands.preparethere first, both under the machine-localnice), then splits recognized failure lines of the repository command's own output (checkResult.Output, never machine-localadditionalchecks) into introduced, pre-existing (only lines qualified by identity: go lines keyed by package, pytestpath::testids), and ambiguous (any unqualified line that also fails on base); a go package failing with no per-test line that did not fail that way on base is listed as unattributed; a failing base with no recognized lines reports "could not be separated". The result is prepended to the findings summary and appended to the evidence prompt; it never edits thetest-commandfinding, whose description is the attested override reason, and never changes severity, action, or approval semantics. Any base-side failure degrades to an "unavailable" note with every failure still attributed to the change; only step cancellation is an error. User semantics:docs/src/content/docs/reference/repo-config.md(test.base_attribution). Regressions:TestTestStep_BaseAttribution*,TestTestFailureLinesNormalizesRunnerNoise,TestBaseAttribution*,TestTestBaseAttributionTrustAndMerge. Process-group reaping on clean/error exit (#357) and Unix WaitDelay remain the lifecycle safety net when agents spawn test workers - restoring the agent-driven path must not revive the daemon OOM leak. Those agent turns are bounded bytest_agent_timeout(default 30m, global-only): a silent evidence or repair agent is cancelled at that stall budget and the Test step parks for a decision rather than failing the run as a code defect; a still-working turn may continue until idle or the configuredtest_agent_working_timeout; an unset cap does not extend the turn. Late structured output from the expired turn is still not used as a pass. The park carries the configured command's result from the same execution (a cut repair turn re-runs it first), so an approval over a failing command stays a configured-command waiver; a cut fix round also carries the gate it answered (selected and deferred findings plus the last completed evidence turn's verdict, scenarios, and tested head,answeredTestGate). A commit the timed-out agent already made is recorded locally unless an unfinished rebase or merge leaves a partial HEAD; uncommitted leftovers stay in the live worktree. While the worktree is dirty or HEAD is past the last completed evidence turn's tested head (else the head an earlier cut measured from, carried on the park asunvalidated_since_shaand measured again on every cut, else this execution's start), the park addstest-agent-unvalidated-workand the executor refuses Approve (pipeline.HasUnvalidatedWorkRefusal), because Document and Push would commit and publish it. A fix selection of only budget-cut findings re-runs validation without a repair turn. Raise the setting when targeted tests routinely stay quiet past the stall budget; do not raise the shipped default. Docs owner isdocs/src/content/docs/reference/global-config.md. Every other pipeline agent invocation is bounded byagent_timeout(default 30m, global-only) atpipeline.RunAgent/ the executortimeoutAgentseam, so a new agent-spawning step cannot hang a run by forgetting a deadline. Review gives every fixer and reviewer a freshreview_agent_timeout; an existing sooner parent deadline is honored rather than capped. The invocation context is scoped only toAgent.Run; a late successful return after the deadline is rejected. Docs owner isdocs/src/content/docs/reference/global-config.md. Regressions:TestTestStep_InitialAgent_TargetedValidationContract,TestTestStep_FixMode_TargetedVerificationContract,TestTestStep_FixMode_DriverFullSuiteInstructionDoesNotOverrideContract,TestTestStep_InitialAgent_NoTargetedEvidenceRequiresHonestFinding,TestTestStep_HangingEvidenceAgentParksForADecision,TestTestStep_StreamingEvidenceAfterStallBudgetCompletes,TestTestStep_FixAgentTimeoutParksWithoutCommit,TestTestStep_FixAgentTimeoutRecordsCommittedHead,TestTestStep_EvidenceCutKeepsTheFailingConfiguredCommand,TestTestStep_RepairCutRecordsTheConfiguredCommandResult,TestTestStep_FixOfABudgetCutAloneRerunsOnlyValidation,TestTestStep_CutMidRebaseRecordsNoPartialHead,TestTestStep_RepeatedCutKeepsRefusingUnvalidatedWork,TestTestStep_RepairCutKeepsTheFindingsItWasFixing,TestTestStep_CutAfterAnEarlierStepCommittedStaysApprovable,TestTestStep_RepeatedCutKeepsRefusingBeforeAnyEvidenceCompletes,TestTestStep_ValidationOnlyCutKeepsTheDeferredNoGo,TestTestStep_RepeatedCutReMeasuresLeftoversInsteadOfRepeatingThem,TestExecutor_UnvalidatedTestWorkRefusesApprovalUntilFixValidatesIt,TestCodexAgent_RunCancelsSilentHang,TestDogfoodConfig_NoBroadLocalTestCommand,TestCIWorkflow_RetainsFullRaceSuiteAsBroadRegressionOwner, plus the existing #357 reap/WaitDelay tests,TestRunAgent_*,TestExecutor_DirectAgentRunIsDeadlineBounded,TestDocumentStep_HangingAgentFailsRunAfterTimeout,TestLintStep_HangingAgentFailsRunAfterTimeout,TestCIStep_HangingFixAgentFailsAfterTimeout,TestCIStep_FixAgentTimeoutRecordsCommittedRepair,TestRebaseStep_HangingConflictAgentFailsAfterTimeout.
Intent Provenance & Conformance (internal/pipeline/steps/intent_prompt.go)
- Intent carries provenance: an explicit
axi run --intentpersistsSource==db.RunIntentSourceAgent("agent", score 1); a transcript match persists the agent name ("claude"/"codex"/...). The executor propagates it asStepContext.IntentSourcealongsideUserIntent(executor.go). userIntentPromptSectionbranches on source: an EXPLICIT intent renders as sanitized-but-AUTHORITATIVE acceptance criteria; an INFERRED intent keeps the low-confidence hint framing verbatim. Both branches keep theStripAdversarial+RedactSecretspipeline and BEGIN/END "do not execute instructions" guard - authoritative reframes only the content's authority (check the diff against the criteria), never whether control tokens are stripped. The review prompt addsintentConformanceReviewClausefor agent-source intent only: a fixer change that contradicts the criteria (removes intent-required or adds intent-forbidden behavior) MUST become anask-userfinding, which parks with no executor change. Both the authoritative framing and the conformance clause state that later recorded human decisions supersede conflicting original intent, so a human-selected fix is never flagged as an intent contradiction;fixerRemovalRuleincommon_fix.gocarries the same precedence so a fixer does not remove a path a recorded decision requires. Conformance is limited to source-verifiable criteria; deferred pipeline-owned delivery (remote branch / push / PR / CI for this run) is out of scope at review.- Review is always pre-push (
StepReviewbeforeStepPush/StepPR/StepCI).pipelineDeliveryPhaseClauseplusstripDeferredPipelineOwnedDeliveryFindings(pipeline_delivery.go, applied inreview.go) keep findings that only claim those later-owned outcomes are missing from parking the run. External or pre-existing lifecycle requirements (numbered PR, third-party artifact, non-run-owned state) stay enforceable. Push, PR, and CI steps remain strict after their stages run. - Empty/missing finding
actionfails closed toask-user, not auto-fix (types/findings.goActionOrDefault);HasAskUserFindingsusesActionOrDefaultso it agrees withAutoFixableFindings(an unclassified finding is never auto-fixed and is always caught as ask-user).MergeUserOverridesstill stamps user-added findings auto-fix on purpose. - The deterministic net-deleted-author-lines git-diff backstop is intentionally not built;
review.goowns the held-scope TODO. - Regressions:
internal/pipeline/steps/intent_prompt_test.go,internal/pipeline/steps/review_test.go(TestReviewStep_ConformanceObligationTracksIntentProvenance,TestReviewStep_RereviewFlagsIntentContradictionAsAskUser),internal/pipeline/steps/pipeline_delivery_test.go,internal/pipeline/steps/review_pipeline_delivery_test.go,internal/pipeline/executor_intent_conformance_test.go,internal/types/findings_test.go, e2eTestIntentJourney(inferred-source framing), e2eTestReviewPipelineOwnedPRCriterionDoesNotPark/TestReviewExternalPRLifecycleStillParks.
Live-Validation Contract (Test step)
- The Test step's evidence turn returns
scenarios(name, result pass/fail/untested,live, evidence, reason) plus averdict(go/no-go/inconclusive/no-surface); both are REQUIRED bytestFindingsSchemaand validated inunmarshalRequiredTestFindings.live: truemeans driven against the real running product in that run - a unit test, stub, or fixture is not live. - Invalid analyzer output is not a fatal run error on the first attempt:
runTestAnalyzergives a fresh agent the rejected payload and validation errors for a bounded correction (testAnalyzerMaxAttempts); only exhausting that bound fails the step. The correction prompt must remain correction-only: no tools, scenario reruns, or external operations; preserve supported observations and downgrade unsupported pass/fail claims to untested. Do not weaken the pass/fail-must-be-live or untested-must-have-a-reason invariants to avoid the retry. - Verdict policy (captain's call C2 = a, plus the 2026-09-07 no-surface ask-user decision) lives in
verdictFindings(internal/pipeline/steps/test.go):no-goparks as an auto-fixable error,inconclusiveparks as an ask-user warning (live surface, too little driven),no-surfaceparks as an ask-user warning when the change has nothing no-mistakes can drive live, afailscenario with any other verdict is rejected as a contract violation,no-surfacewith any live or pass/fail scenario is rejected so a skipped live validation cannot masquerade as no-surface, anduntestedNEVER parks - it is listed on the PR so reporting it honestly stays cheaper than guessing a pass. Do not make untested blocking. - Rendering is owned by
internal/pipeline/steps/prsummary_scenarios.go(Testing section, Pipeline fold, and attestationlive_validation: {verdict, live, total}). The attestation omitslive_validationwhenever the current head differs from the head Test validated. Pre-contract runs parse and render exactly as before. - The evidence turn is unconditional: a green
commands.testbaseline is never enough on its own, so the step always drives scenarios afterwards. The step deliberately owns no capability inventory, scratch lab, or environment exports - the agent discovers what the machine can do for itself. test.instructionsis the repository's live-validation runbook. It is trusted-default-branch-only, exactly likedocument.instructionsand for the same reason (it steers the gate that validates the pushed branch), and is resolved from the repository only - never from top-level global config, which has no repository to describe. A matching machine-localrepository_overrides.<remote>.test.instructions(Test.RepositoryInstructions) renders in its own labeled section before it and never replaces it; both reach the repair and the evidence prompt (testRunbookSections).- Regressions:
TestTestStep_PromptDerivesScenariosAndMarksLive,TestTestStep_VerdictPolicy,TestTestStep_NoLiveSurfaceCIWorkflowAsksUser,TestTestStep_MissingScenarioContractFails,TestTestStep_InvalidAnalyzerPayloadTriggersCorrectionRound,TestTestStep_InvalidAnalyzerPayloadExhaustsCorrectionBound,TestTestStep_ValidMixedPayloadDoesNotRetry(internal/pipeline/steps/test_scenario_contract_test.go),internal/pipeline/steps/prsummary_scenarios_test.go,require_no_mistakes_live_validation_test.go,TestEffectiveRepoConfig_TestInstructionsTrustedOnly.
Test Fix-Round Carry (unselectedTestFindings, internal/pipeline/steps/test.go)
- User-facing carry and approval rules are owned by the Test step reference.
unselectedTestFindingsconsumesStepContext.DeferredFindings; its comment owns the deduplication safety rule. It skipsFindingCategoryTestVerdict,FindingCategoryTestCommand, andtestBudgetCutIDs.StepContext.AutoFixRounddistinguishes automatic and human fixes. The executor sets it true for automatic fixes and false for human fixes becausesctxis reused.excludeFindingsJSONsubtracts selected IDs aftertypes.NormalizeFindings(internal/types/findings.go) establishes unique IDs. Regressions:internal/pipeline/steps/test_fix_round_carry_test.go,TestNormalizeFindingsAvoidsIDCollisions,TestExecutor_AutoFixDuplicateIDsKeepUnselectedFindingParked.
Review Schema Retry (internal/pipeline/steps/review.go)
- When the agent adapter marks a review turn's final output as rejected against the schema (
agent.IsStructuredOutputRejected: Pi when its final JSON fails validation or a strict-path run ends without callingno_mistakes_outputalone (internal/agent/pi.gorunOnce), opencode once its own internal StructuredOutput retries run out), orparseReviewAnalyzerOutputrefuses the output, that is a formatting slip, not a verdict:ReviewStep.Executereruns a fresh, session-free review of the same prompt plus a note quoting the validation error, up toreviewAnalyzerMaxAttempts(3) attempts in total. Claude Code's--json-schemare-prompts within its own limit and reports exhaustion aserror_max_structured_output_retries; that result is deliberately not marked as a rejection and fails the step as before, so there is no second retry layer and Test's correction loop is untouched. When Claude's terminalresultevent carries nostructured_output(background completions can end with prose only),parseClaudeEventsininternal/agent/claude.gouses the latestStructuredOutputtool call whosetool_resultwas not an error; a rejected call never replaces an accepted one, and a run with no accepted call still fails as missing structured output. Findings come only from the attempt that validates; nothing else from a rejected attempt carries over, and fix-mode turns are never retried. Exhausting the bound fails the step withvalidate review analyzer findings after 3 attempts: <last error>, so an unreadable review never passes (issue #703); the unconfigured "pass" meaning is unchanged. When the attempts'agent.SchemaViolationfields (typed by the validator ininternal/agent/agent.go, dotted path without array indices, empty for a whole-output type failure) name at least two distinct fields,distinctReviewValidationFailuresattributes the error instead:... output kept failing validation across distinct fields (<fields>): attempt 1: ...; attempt 3: <last error>, each attempt's error printed once (issue #1280). Fields come only from the typed violation, never from parsing the message text. - Every other failure returns at once, and so does a rejection from a turn its deadline or a cancellation cut short (
agentInvocationErrorcarries both the adapter's rejection anderrReviewAgentTimeout). This is a rerun of the review, not a correction turn; Test's correction behaviour is separate and unchanged. - Eval replay (
internal/eval/replay.goobservedAgent) sums token usage across every review attempt and reports none when any attempt lacks reported usage, matching the captured baseline, so a candidate that needed reruns never reads as cheaper. - Regressions:
internal/pipeline/steps/review_schema_retry_test.go,TestReviewStep_UnrunAnalyzerDoesNotApprove,TestReplayTokensCoverEveryReviewAttempt, e2eTestAnalyzerEvidenceFailuresFailPipelineJourney, e2eTestReviewSchemaRetryExhaustionJourney.
Review Fix-Round Carry (internal/pipeline/executor.go, internal/pipeline/findings.go)
- The review gate decides on an append-only outstanding set, never on one round's output:
respond --action fixadditionally hands the selection to the fixer but does NOT subtract it from the set. A selected finding leaves it only on a positive coverage record - the rereview lists the finding's file inreviewed_paths(review schema ininternal/pipeline/steps/common.go,StepOutcome.ReviewedPaths), that path is in the trusted current reviewable changed-file set (StepOutcome.ReviewablePaths), neither re-reports the defect, nor reports anything else at all in that same file - or on an explicit operator approve/skip/abort. Silence, missing or fabricated/out-of-scope coverage (including a mixed coverage list), a round that looked elsewhere, a re-reported defect, and any other finding in the same file (an ambiguous rereport that might be the original defect shifted to another line or reworded) all leave it outstanding; any file-less finding in the current rereview also blocks clearing every selected finding in that round. One upgrade-compat carve-out: a SELECTED finding with no file anchor can never match a coverage record, so it clears on a positive verification round whosereviewed_pathscovers every trusted reviewable path and that no longer reports it - runs parked before the recorded-decision review machinery was removed carry such items, and a fix selection could otherwise never clear them. A crash-resumed fix round fails closed the same way. A validation restart at Review starts a fresh carry set rather than resurrecting the superseded cycle's findings.resolveVerifiedFindingsJSONowns the verify-before-clear rule,mergeOutstandingFindingsJSONowns the append-only merge (an outstanding item keeps its ID when a new round re-mints it; identity is still content-derived and stable IDs are a separate design pass), and the gate, persistedfindings_json, round rows, andinternal/db/stats.goall derive from that same set sono-mistakes statscannot report Fixed 100% while the operator is parked. The loop itself is bounded only byauto_fix.review(the automatic-round budget) and the human/agent gate, matching upstream - no separate round or stall cap. Regressions:internal/pipeline/review_carry_forward_test.go(TestExecutor_ReviewCarryForward_NoOpFixKeepsFindingParked,TestExecutor_ReviewCarryForward_UserAddedFindingStaysOutstanding,TestExecutor_ReviewCarryForward_PositiveCoverageClearsFinding,TestResolveVerifiedFindingsJSON,TestResolveVerifiedFindingsJSON_FilelessPendingFinding,TestMergeOutstandingFindingsJSON_UsesCurrentReviewedPathsWhenRoundIsEmpty) andinternal/pipeline/executor_test.go(TestExecutor_RevalidationClearsReviewCarry),internal/pipeline/executor_autofix_test.go(TestExecutor_AutoFixInfoFindingNoOpParksAfterBudget). - A clean review turn certifies the head only with a
reviewed_pathsrecord covering every trusted reviewable path:ReviewStep.ExecutesetsNeedsApprovalwhen the list is absent (nil, including Codex's null-fill of an omitted field), empty, partial, or names an out-of-scope path, and logs which files went unverified (uncoveredReviewMessage).reviewed_pathsstays optional inreviewFindingsSchemaonly so an older payload still parses - never as a legacy pass; there is no production escape. Eval replay (internal/eval/replay.go) reads onlyFindings, so the park does not change scoring. The fake agent (cmd/fakeagent/scenario.gowithReviewCoverage) is the sole fixture-side stand-in: on a review turn whose canned response has noreviewed_pathsit fills the response from the pipeline's coverage checklist, falling back to the base-commit diff minus the prompt's ignore patterns only for older prompts without a checklist; a scenario that spells the field out - partial, empty, or fabricated - is passed through so coverage gates stay testable. Regressions:TestReviewStep_PartialReviewedPathsDoesNotGrantApproval,cmd/fakeagent/review_coverage_test.go. - Self-reported coverage is lossy on real multi-file diffs: a 15-file branch produced three consecutive zero-finding rounds each omitting a DIFFERENT file, and the only non-waiver path was a nothing-to-fix fix round plus a full re-review that re-rolled the same dice. Two repairs, both in
ReviewStep.Execute:reviewCoverageSectionenumerates the trusted reviewable set in the review prompt (a checklist the reviewer verifies itself, replacing the lossy reconstruct-from-memory round trip), andcompleteCoverageGapsruns ONE bounded focused completion turn over exactly the uncovered files when a clean round would otherwise park solely on coverage. The completion prompt is the full review prompt plusreviewCoverageCompletionSection(so a cold/resumed turn is self-sufficient), merges viamergeReviewedPaths/uncoveredReviewablePathsincommon_fix.go, and is auditable under thereview-coveragepurpose, whichreview_roles.goroutes through the reviewer chain likereview(and whicheval/capture.go'sbaselineForRoundcounts alongsidereviewwhen it freezes a round's cost baseline). Eval replay setsStepContext.EvalReplay, andcompleteCoverageGapsskips the completion turn there: replay scores the review's findings against captured gold and never consumes the coverage certification, so spending an invocation the captured baseline does not charge would double the candidate's recorded cost, while the strict coverage check still parks the incomplete record. The strict coverage check re-runs on the union, so whatever is still uncovered - or fabricated in either turn - parks with the remainder named; a completion-turn agent error or invalid output parks on the incomplete record rather than failing the run, and rounds with blocking findings skip the pass entirely.mergeReviewRiskreconciles risk_level/risk_rationale/risk_scope as whichever turn is worse (by level rank), so a completion turn that finds a real defect in a file the first pass never read overrides a stale "low" label rather than leaving it beside the new finding. The completion turn's prompt still carries the question protocol, soappendOpenReviewQuestionFindings(the shared helper both turns call) re-reads the conversation after it returns - a question the completion turn asks is not dropped just because the first turn already read the conversation once.mergeReviewedPathskeeps a single invalid (empty/whitespace/.-only)reviewed_pathsentry across the union rather than folding it away, andcompleteCoverageGapsskips the completion turn entirely when the first record already carries one (hasInvalidReviewedPath), so the pass can never launder an already-invalid coverage record into one that reads as complete. Both coverage prompt sections (reviewCoverageSection,reviewCoverageCompletionSection) render each branch-controlled path throughcoveragePathLine, which escapes newlines, carriage returns, tabs, and other control runes so a crafted filename cannot read as a second prompt instruction. The completion turn is held to the same pre-push ownership boundary as the first pass:stripDeferredPipelineOwnedDeliveryFindingsruns on its output too before the merge.mergeReviewRiskmoves risk_level/risk_rationale/risk_scope only when the completion turn has a remaining finding of its own: a tied or higher level with a real defect still overrides the first pass's stale "clean" label, but a turn with no remaining finding - including one whose only finding was stripped as deferred pipeline-owned delivery after the merge - never raises the published risk. Regressions:internal/pipeline/steps/review_coverage_retry_test.go(TestReviewStep_PartialCoverageCompletesInOneFocusedPass,TestReviewStep_MultiRoundCoverageOmissionsConvergeWithoutAWaiver,TestReviewStep_CoverageCompletionFailureStillParksExplicitly,TestReviewStep_CoverageCompletionFabricationStillParks,TestReviewStep_BlockingFindingsSkipTheCoverageCompletion,TestReviewStep_ReviewPromptEnumeratesTheCoverageContract,TestReviewStep_CoverageCompletionOverridesStaleRiskLabel,TestReviewStep_CoverageCompletionQuestionParksTheRound,TestReviewCoverageSectionsEscapeBranchControlledPaths,TestReviewStep_InvalidCoverageEntryNeverCertifiesTheHead,TestReviewStep_CoverageCompletionDropsDeferredPipelineOwnedDeliveryFindings,TestReviewStep_CoverageCompletionReconcilesEqualRiskRationale,TestReviewStep_CoverageCompletionDoesNotRaiseRiskWithoutARemainingFinding,TestReviewStep_EvalReplaySkipsTheCoverageCompletion). - Recovering a parked review gate (daemon restart or a mid-run revalidation reset) must not let a reused, positional finding ID alias an unrelated later finding as "selected":
mergeOutstandingFindingsJSONhands a freed ID to the next new item vianextFreeReviewFindingIDonce the original finding drops out of the outstanding set, so blindly retaining any historicalSelectedFindingIDsentry present in the latest findings (by ID alone) can silently treat a never-selected finding as carried.selectedFindingIdentitiesrecords each selected ID's content identity (findingKey) as of the round that reported it;retainFindingIDsByIdentity(both ininternal/pipeline/findings.go, used byExecutor.durableExecutionStateandExecutor.recoveredGate) only keeps an ID whose CURRENT finding still matches that recorded identity. Scoped to recovery only - the live in-process loop's ownretainFindingIDscalls are unchanged. Regressions:TestRetainFindingIDsByIdentity_RejectsPositionalIDReuse,TestRetainFindingIDsByIdentity_KeepsGenuineSameFindingAcrossRounds.
Parked / Awaiting-Agent Signal
runs.awaiting_agent_sinceis non-nil iff a step is actually parked at anawaiting_approval/fix_reviewgate: the executor sets it on gate entry, clears it whenwaitForApprovalreturns, andRecoverStaleRunsclears it on crash recovery. It is observability only (rendered asawaiting_agent: parked <duration>inaxi status) and never changes gate resolution, auto-resume, or the--yesdefault.- Tests:
internal/db/run_test.go,internal/pipeline/executor_approval_test.go,internal/cli/axi_test.go, e2eTestAxiParkedAwaitingAgentSignal.
Approval Override Is Never a Silent Clean Pass (pipeline.ApprovalOverrideVerifier)
- Both
ActionApprovesites ininternal/pipeline/executor.go(live wait andResume) route throughExecutor.applyApprovalOverridebefore completing the step.ApprovalOverrideVerifiersupplies the unresolved condition: CI re-checks live checks; Test reads its persisted configured-command failure.pipeline.ApprovalOverrideVerifierowns verification-error handling and approval refusal; ordinary errors record an unresolved condition, whileErrApprovalBlockedrefuses the approve response atRespondWithOverridestime and leaves the gate parked. Test approval's operator explanation is separate local evidence instep_results.approval_reason(NULL = no recorded approval, empty = approval without a reason);db.StepResult.TestOverrideReasonowns its snapshot/event qualification: only a failing configured command, ano-go/inconclusiveverdict, or a Test-agent invocation-budget cut qualifies, so ano-surfaceacknowledgement completes normally. CI overrides and explicit Test exceptions producepassed-with-overridethroughoutcomeForRun. Do not reuse the configured-commandoverride_reasonfor all Test approvals: that would expand PR enforcement to evidence-only exceptions and change approval policy. The supported--reasoninput and reset semantics are owned bydocs/src/content/docs/reference/cli.md. A configured-command Test override is copied onto the PR attestation assteps[].override_reason;verify.pyrefuses it unless trustedtest.allow_approve_over_failuresupplies a recorded reason (allow_test_command_overrideon the attestation). A CI-repair restamp keeps the prior Test override_reason but applies the current trusted opt-in; it does not copy a previous attestation'sallow_test_command_override. - Every other step (review, document, lint, push, pr) does not implement the interface and is completely unaffected.
- The override reasons also ride the
run_completedIPC delta (ipc.Event.CIOverrideReasonandTestOverrideReason, derived inemitRunEventfrom step rows and gated onrun.Status == RunCompleted), so an attached TUI'srenderOutcomeBannershows⚠ Pipeline passed with overrideon the live event path without waiting for a snapshot - otherwise the banner disagrees with axi'spassed-with-override. - Test-exception journeys:
internal/e2e/test_approval_exception_test.go; recovery:internal/pipeline/test_approval_exception_test.go; migration and reset semantics:internal/db/test_approval_reason_test.go. - Tests:
internal/pipeline/executor_approval_override_test.go(incl.TestExecutor_ApprovalOverride_RunCompletedEventCarriesReason),internal/pipeline/steps/ci_approval_override_test.go,internal/pipeline/steps/test_approval_override_test.go,internal/pipeline/steps/prsummary_test_override_test.go,require_no_mistakes_test_override_test.go,internal/cli/axi_test.go(TestOutcomeForRun),internal/tui/action_bar_test.go(TestOutcomeBanner_CIOverrideShowsReason),internal/tui/events_test.go(TestModel_ApplyEvent_RunCompletedCarriesCIOverride).
