From 419325fd79ae4b259e2463438086431b7f0c6d41 Mon Sep 17 00:00:00 2001 From: garrytan Date: Thu, 15 Oct 2026 01:34:12 +0000 Subject: [PATCH 1/2] fix(autoplan): settle after or with a revise, title pairs need the same remedy, legacy gates refuse rec (integration items 1, 7, 8) 1. A gate with a scope change (uc1) and a packaging change (uc2) answered `rec` deadlocked: after `revise --plan v2 --incorporates uc1` reopened the phases, `revise --settle uc2` compared against the first phase snapshot (now undefined) and was refused forever. Settle evidence is now the plan hash recorded when the owner accepted the change (Amendment.plan_sha256) or a later plan_revised hash, plus "the plan file has no unrevised edits". `--settle` may ride the same revise as `--plan/--incorporates` and binds to the revision's hash. Settling the last amendment after a reopen leaves the run `running` (the reopened phases rerun) instead of approving it. NEXT and PLAN_AMEND_PENDING list the settle step before the edit step. 7. A title association alone no longer confirms: the same remedy confirms, opposite remedies (one keeps, drops or negates what the other changes) disagree, different or missing remedies stay `unverified`. The pairing fixture gains same-title negative controls (opposite and different remedies) and a same-remedy positive, for ordinary findings and user challenges. 8. A gate.json without rec_rev (written by 1.91.101.0, whose render said `all` leaves b items pending) refuses rec/all/approve all/`all except` with GATE_REC_STALE until a re-render binds --rec-hash; explicit tokens still answer. New gates carry rec_rev: 0. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- autoplan/DRIVER.md | 2 +- bin/gstack-autoplan | 8 +- docs/troubleshooting.md | 24 +++-- lib/autoplan-gate.ts | 22 +++-- lib/autoplan-reconcile.ts | 34 ++++--- lib/autoplan-revise.ts | 29 +++--- lib/autoplan-run.ts | 4 +- lib/autoplan-state.ts | 2 + lib/result-codes.ts | 6 +- test/autoplan-answer-invalidation.test.ts | 2 +- test/autoplan-gate-judgment.test.ts | 44 +++++++++ test/autoplan-pairing.test.ts | 28 +++++- test/autoplan-residuals.test.ts | 25 ++++++ test/autoplan-run-recovery.test.ts | 11 +-- .../autoplan-pairing/run-20261014-pairs.json | 89 ++++++++++++++++++- 15 files changed, 274 insertions(+), 56 deletions(-) diff --git a/autoplan/DRIVER.md b/autoplan/DRIVER.md index 1e8fc3571..54a7e660e 100644 --- a/autoplan/DRIVER.md +++ b/autoplan/DRIVER.md @@ -127,7 +127,7 @@ is the completion check. Copy the whole directory, never single files. - `OWNER_REPLY_REQUIRED`: pass the owner's words exactly with `--owner-reply ""`, `--owner-reply -` (stdin) or `--owner-reply-file ` - `GATE_OWNER_REPLY_MISMATCH`: relay exactly what the owner chose (or their reply itself, e.g. `--reply rec`); ask the owner when their words and the tokens disagree - `PLAN_AMEND_PENDING`: edit the plan, then `gstack-autoplan revise --out --plan --incorporates `; settle a packaging or ordering change with `gstack-autoplan revise --out --settle `; or drop one with `gstack-autoplan revise --out --withdraw ` -- `AMENDMENT_NOT_SETTLEABLE`: edit the plan, then `gstack-autoplan revise --out --plan --incorporates `; settle only packaging or ordering changes on an unchanged plan +- `AMENDMENT_NOT_SETTLEABLE`: edit the plan, then `gstack-autoplan revise --out --plan --incorporates ` (add `--settle ` to settle packaging/ordering changes in the same call); settle alone only on the plan the run already reviews - `CHECKPOINT_INVALID`: restore from an intact copy of `//` (its `restore.md` names the command); nothing was written to --out - `GSTACK_VERSION_CHANGED`: nothing to do for an older run (journaled forward migrations ran); a run written by a newer gstack needs that gstack: upgrade with bin/gstack-upgrade diff --git a/bin/gstack-autoplan b/bin/gstack-autoplan index e2b8766f4..29aa65289 100755 --- a/bin/gstack-autoplan +++ b/bin/gstack-autoplan @@ -26,7 +26,7 @@ */ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { answerGate, applyGuidance, recommendGate } from '../lib/autoplan-gate'; +import { amendmentSteps, answerGate, recommendGate } from '../lib/autoplan-gate'; import { checkpointRun, restoreCheckpoint } from '../lib/autoplan-checkpoint'; import { admit, touchInstallLease } from '../lib/autoplan-admission'; import { exportRun } from '../lib/autoplan-export'; @@ -98,11 +98,7 @@ const resultLineFor = (status: string, out: string) => `GSTACK_RESULT: skill=aut const listFlag = (args: string[], name: string) => args.flatMap((a, i) => (a === name && args[i + 1] ? args[i + 1]!.split(',').map(x => x.trim()).filter(Boolean) : [])); /** The NEXT line after an answer or revise: per pending change, settle it or edit the plan and revise; else next. */ function nextLine(state: RunState, out: string): string { - const pending = pendingAmendments(state); - const settle = pending.filter(a => applyGuidance(a).startsWith('no plan edit')).map(a => a.item ?? a.decision); - const edit = pending.filter(a => !applyGuidance(a).startsWith('no plan edit')).map(a => a.decision); - if (!pending.length) return `NEXT: gstack-autoplan next --out ${out}`; - return `NEXT: ${[edit.length ? `edit the plan, then gstack-autoplan revise --out ${out} --plan ${state.plan_input ?? state.plan_source} --incorporates ${edit.join(',')}` : '', settle.length ? `gstack-autoplan revise --out ${out} --settle ${settle.join(',')}` : ''].filter(Boolean).join('; ')}`; + return pendingAmendments(state).length ? `NEXT: ${amendmentSteps(state, out)}` : `NEXT: gstack-autoplan next --out ${out}`; } /** diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index ca5df4506..7c3bda6d8 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2809,14 +2809,18 @@ pending, revise without `--incorporates`: **Meaning.** `gstack-autoplan revise --settle ` discharges an accepted change without a plan edit, which is honest only for a packaging or ordering change -(the gate item's `changes` is `packaging` or `ordering`) on a plan nobody has -edited since it was reviewed. The named change alters scope, cost or direction, -names no `changes` value, or the plan file's Implementation plan no longer -matches the reviewed snapshot. Nothing was changed. +(the gate item's `changes` is `packaging` or `ordering`) on the plan the run +already reviews: the plan the owner accepted it on, or one a later `revise` +recorded. The named change alters scope, cost or direction, names no `changes` +value, the plan file has edits no `revise` has recorded, or `--settle` came with +a `--plan` that changes the Implementation plan while incorporating nothing. +Nothing was changed. **Fix.** Edit the plan, then -`gstack-autoplan revise --out --plan --incorporates `. -The `APPLY:` line for each change says which path it takes. +`gstack-autoplan revise --out --plan --incorporates `, +adding `--settle ` to settle packaging or ordering changes in the same call +(or settle them after the revise). The `APPLY:` line for each change says which +path it takes, and `NEXT:` lists the settle step first. ### `GATE_REC_STALE` @@ -2825,10 +2829,14 @@ The `APPLY:` line for each change says which path it takes. was rendered, so a `rec` (or `all`) reply the owner gave to the earlier render would take recommendations they never saw. Every render prints `rec_hash=`; once `recommend` has run, a `rec` answer must name the hash of -the render the owner answered. +the render the owner answered. The same refusal meets a `rec`/`all` reply to a +gate written before `rec` existed (a `gate.json` with no `rec_rev`, from +1.91.101.0 or earlier): that render told the owner `all` leaves items +recommended `b` pending, and `all` now takes them. **Fix.** Render again (`gstack-gate render /gate.json --all`), relay it, -and answer with `--rec-hash ` from that render. +and answer with `--rec-hash ` from that render. Explicit `