fix(gate): part 1 verifier findings — stop-rule wording, guard tests, unreadable round
- plan-gate: the bare gate's stale-verdict reason no longer says "run the next round" once the stop rule fired; it says there is no next round, as acceptOpen already does. - trekplan Phase 9: "REVISE after the last round" (Fix now with --extra-round) is qualified "and the stop rule did not fire", pinned in review-loop-docs.test.mjs next to the [STP] pins. - plan-gate-stop-rule tests: a planted stop-rule acceptance claiming an open security blocker stays STOP; a planted acceptance without under: "stop-rule" stays STOP; the "a blocker -> refused" test now asserts the blocker refusal; --accept-open under the stop rule covers an open MAJOR and the gate passes with a WARN. - review-rounds prepare: when the stop rule is judged (P >= 2) and either round's JSON block cannot be read, exit 2 (not measured) with "stop rule: a round has no readable JSON block" instead of skipping the rule. The [RND] differing-archive and [CAP] fixtures now use readable archives; one new [STP] test covers an unreadable archive and an unreadable review.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
08e8aa1c66
commit
07a3c06c57
6 changed files with 153 additions and 13 deletions
|
|
@ -30,8 +30,8 @@
|
|||
// `carried_from`; class = `classOf`. Fired -> exit 1 with
|
||||
// `review-stop-rule: <describeStop line>` and `stop_rule`; nothing is
|
||||
// archived. A round whose trailing JSON block cannot be read gives no
|
||||
// summary, and the rule is then not judged (as a plan verdict without
|
||||
// history). The cap stays.
|
||||
// summary: exit 2 (not measured), `stop rule: a round has no readable
|
||||
// JSON block`, nothing archived. The cap stays.
|
||||
// - The round base is the previous `scope_sha_end`: a full sha that is an
|
||||
// ancestor of HEAD, else exit 2 (not measured).
|
||||
// - review.md is copied to `review-round-<run>-<P>.md`; an existing file
|
||||
|
|
@ -245,10 +245,9 @@ export function prepare({ projectDir, since, repo, dryRun = false } = {}) {
|
|||
const at = repo ?? projectDir;
|
||||
const a = prevBytes === null ? null : reviewRoundSummary(prevBytes.toString('utf8'), at);
|
||||
const b = reviewRoundSummary(text, at);
|
||||
if (a && b) {
|
||||
const d = decideStop([{ round: P - 1, ...a }, { round: P, ...b }]);
|
||||
if (d.fired) return { exit: 1, reason: `review-stop-rule: ${describeStop(d)}`, stop_rule: d };
|
||||
}
|
||||
if (!a || !b) return notMeasured('stop rule: a round has no readable JSON block');
|
||||
const d = decideStop([{ round: P - 1, ...a }, { round: P, ...b }]);
|
||||
if (d.fired) return { exit: 1, reason: `review-stop-rule: ${describeStop(d)}`, stop_rule: d };
|
||||
}
|
||||
const base = ownEnd;
|
||||
if (!base || !isAncestorOfHead(repo ?? projectDir, base)) {
|
||||
|
|
|
|||
|
|
@ -489,7 +489,12 @@ export function gatePlan(rawText) {
|
|||
reasons.push({ code: 'PLAN_GATE_VERDICT_MALFORMED', message: ex.error });
|
||||
} else {
|
||||
if (v.plan_sha256 !== planHash(text)) {
|
||||
reasons.push({ code: 'PLAN_GATE_VERDICT_STALE', message: `The plan changed after critic round ${v.round}; run the next round.` });
|
||||
reasons.push({
|
||||
code: 'PLAN_GATE_VERDICT_STALE',
|
||||
message: v.stop_rule?.fired === true
|
||||
? `The plan changed after critic round ${v.round}, and the stop rule fired, so there is no next round; restore the reviewed text, or stop and take the direction to the operator.`
|
||||
: `The plan changed after critic round ${v.round}; run the next round.`,
|
||||
});
|
||||
}
|
||||
const sev = f => normalizeSeverity(f.severity);
|
||||
const blockers = v.findings.filter(f => sev(f) !== 'major' && sev(f) !== 'minor');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue