Repository navigation
refactor(review): retire FINALIZE through last-event atomic closure #3587
Description
Activity
- addedbugSomething isn't workingSomething isn't workingstatus:approvedApproved for implementation — PRs can now be openedApproved for implementation — PRs can now be openedtype:breaking-changeBreaking change (requires major version bump)Breaking change (requires major version bump)priority:highHigh priorityHigh priority
on Aug 22, 2026 Alan-TheGentleman commented
on Aug 22, 2026 ContributorAuthorMore actionsImplementation plan is locked. This will ship as one atomic PR with a maintainer-authorized
size:exception; splitting the public contract acrossmainwould either break every adapter between slices or require the compatibility layer this change explicitly forbids.Work units inside the PR:
- Reducer semantics: a complete admitted review with no surviving candidate-caused severe finding becomes transiently approved from the admitted result set itself. Clean review no longer enters general final verification.
- Event-owned closure: the last required admitted event closes the lifecycle. This is the last lens capture for a clean review, refuter capture when one is required, and targeted-validator capture after a bounded correction. One closer wins under native authority locking; approval immediately enters the existing atomic burn.
- Public clean break: delete
review finalizefrom command routing, negotiated transitions, capabilities, contracts, adapters, prompts, sanctioned exits, fixtures, and runtime journeys. No alias, compatibility parser, dual reader, or migration route remains. - Parity and proof: add deterministic concurrent-last-capture coverage plus driven clean-4R and corrected-candidate journeys. STATUS must remain read-only and must never offer FINALIZE.
Broad unreachable-provider deletion is deliberately deferred to #2423 after this functional cut. That keeps the architecture change reviewable as behavior rather than mixing it with a repository-wide dead-code sweep.
Open mechanism patches will be dispositioned against this root line, not stacked onto it: #3576's one-writer burn invariant is absorbed by last-event closure, #2033's FINALIZE parser surface is deleted, and #3483's correction-forecast FINALIZE surface is deleted. Those PRs should not merge independently once named #3587 tests prove their reported states unreachable.
- added a commit that references this issue
on Aug 23, 2026 Stable-channel occurrence on
gentle-ai 2.4.0(Darwin arm64, Pi viagentle-pi 2.2.0):- Correction repository evidence was submitted with outcome
passed. - The next STATUS offered a bound
targeted_validation_requiredtransition and rendered--captured-evidence=true. - Submitting that exact bound validation failed in preflight with
invalid_request:compact correction acceptance requires captured repository verification evidence. - Native reported
mutation_outcome: not_startedandretry_safe: true; the lineage and corrected candidate remained preserved. No blind retry was attempted.
Expected: after passed repository evidence, targeted validation consumes that admitted evidence, or STATUS remains on evidence collection. Actual: STATUS advances to validation while validation denies the evidence record.
I found the fix merged in #3627 and published in
v2.5.0-rc.1; the latest stable release remainsv2.4.0. This records the stable-channel occurrence only and does not request reopening.- Correction repository evidence was submitted with outcome
New rc.1 recurrence: the correction-plan transition still names the retired
review finalizeverb.Environment
- Gentle AI:
2.5.0-rc.1 - Client: Pi controller wrapper
- OS/architecture: macOS arm64
- Contract:
gentle-ai.review-integration/v2
Observed
- A high-risk staged review captured all four reviewer results and entered
correction_requiredwith a frozen 170-line budget. - Fresh STATUS returned
collect/correction_plan_requiredwithcorrection_lines; its submission descriptor namedoperation_token: finalizeand included a bound request hash and repository-context handle. - Submitting an in-budget 160-line forecast through the controller failed before mutation as
review.finalizeinvalid_request. - Executing the exact provider-issued correction descriptor directly against rc.1 failed with
unknown review command "finalize". - Fresh STATUS repeated the same correction-plan collect. No correction bytes, commit, push, PR, reset, or recovery operation occurred.
Expected
Afterreview finalizewas retired through last-event closure, STATUS must not emit a correction-plan descriptor that names that retired verb. A correction-required lineage needs a supported, revision-bound path to submit its forecast and continue.Actual
STATUS requirescorrection_linesbut routes it through the retiredfinalizeoperation, leaving the lineage unable to begin its one permitted correction.Minimal reproduction
- Start a high-risk staged review.
- Capture the required reviewer results, producing
correction_required. - Read fresh STATUS and submit the positive bounded
correction_linesvalue from its descriptor. - Observe controller
invalid_request; run the descriptor directly and observeunknown review command "finalize".
Preserved state
- Lineage:
review-4352dce3d06fa986 - Authority revision:
sha256:ca653cb650235a6496d3ccbc2f4dae355935f916f9754c3d09dbd61404d1c634 - Candidate remains staged and uncommitted; the review remains
correction_required.
- Gentle AI:
- added a commit that references this issue
on Sep 24, 2026
Pre-flight Checklist
status:approved.📝 Bug Description
Ordinary atomic review still exposes
review finalizeas a separate mutation after all reviewer or validator inputs have already been admitted. That extra lifecycle round reconstructs candidate views, rebinds workspace state, collects general final evidence, and can enter recovery even though review is informational and delivery is unmanaged.In the current atomic architecture, FINALIZE owns no independent product decision.
CaptureAdmittedReviewerResultalready admits each exact candidate-bound lens result, whileReviewCore.finalizeprimarily verifies that all selected lenses are present and reduces the captured set to approve or escalate. Keeping those operations separated creates a failure surface after the substantive work is complete.This is the root class behind multiple FINALIZE, correction-evidence, targeted-validation, intended-untracked, and recovery dead ends. The fix must shrink the lifecycle: the last admitted event becomes the atomic reducer and burn trigger.
🔄 Steps to Reproduce
Concrete sanitized failures observed on current
main:The failed FINALIZE also changed intended-untracked paths to staged entries while reporting no mutation. Candidate bytes and the current candidate tree remained unchanged.
✅ Expected Behavior
The lifecycle closes on the last admitted event:
review.finalize.❌ Actual Behavior
The lifecycle has two closing authorities:
The second authority can fail or mutate client workspace/index state after the candidate has already passed SDD verification, repository tests, and independent review. Recovery then diagnoses review transport state instead of candidate correctness.
🖥️ Environment
gentle-ai dev,main@51a0d60bAcceptance Criteria
Clean path
review capture-resultadmits one exact lens result and, when it is the final outstanding selected lens, elects exactly one closer under the existing authority lock.Correction path
Public contract removal
gentle-ai review finalizeas a public lifecycle command and capability.consent → freeze → four captures → terminal response + burnandcorrection → validator → terminal response + burn/escalation.Delivery and cleanup boundaries
Root-Cause Cluster
This issue is the canonical architectural line. During implementation, audit and disposition the open symptom reports involving separate FINALIZE/correction closure, including #2478, #2503, #3014, #3134, #3194, #3205, #3427, #3450, #3467, and #3476. Close or supersede only when named driven tests prove each reported symptom is unreachable.
Additional Context
The implementation must preserve the atomic foundation:
“Finalize” may remain as an internal pure reducer name temporarily during implementation, but it must not survive as a public phase, command, transition, or caller-supplied decision point. The final architecture has one closing mutation: the last admitted event.