feat(harness): F6/F7 close on their last approval — no echo phase gate

Where completeness is machine-detectable, the reviewer's last substantive
approval now closes the phase itself (same pattern as F3/F4/F8):

- F6: approving the LAST CTE of the plan (kind:"cte_result" with next_cte
  now empty) advances the phase; a non-final CTE keeps the phase open and
  names the next one.
- F7: kind:"sql" records sql_approved — which IS F7's only advance
  prerequisite — and advances immediately.

Two reviewer interactions per session removed, both pure echoes. The
summary phase gate remains only where completeness is a human judgment
(F1, F2 with recorded memories, F5). SKILL.md states the rule and the
five self-closing gates; L1 tests cover last/non-last CTE and sql close.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 02:23:45 +02:00
co-authored by Claude Fable 5
parent c4951e2aa5
commit 83942c0b5c
3 changed files with 161 additions and 19 deletions
+35 -2
View File
@@ -1299,7 +1299,28 @@ export default function (pi) {
"",
);
if (err) return err;
return textResult(`CTE '${cteName}' approvato (sessione ${session}).`);
// Deterministic completeness: when no CTE remains in the plan the reviewer
// has approved everything F6 can ask — close the phase here instead of
// echoing a reviewer_confirm kind:"phase" that decides nothing new.
const remaining = tht(ctx, ["cte", "next", "--session", session]).trim();
if (remaining) {
return textResult(
`CTE '${cteName}' approvato (sessione ${session}). Prossimo CTE del piano: '${remaining}'.`,
);
}
const closed = advancePhaseAndFinalize(ctx, session, curNum);
if (closed.err) return closed.err;
if (closed.finalized) {
lockActive = false;
lastSteered = false;
return textResult(
`CTE '${cteName}' approvato — era l'ultimo del piano: fase chiusa e sessione finalizzata (${session}).`,
);
}
return textResult(
`CTE '${cteName}' approvato — era l'ultimo del piano: Fase ${curNum} chiusa automaticamente. ` +
'NON presentare reviewer_confirm kind:"phase" per questa fase; prosegui con la successiva.',
);
}
if (kind === "sql") {
const err = relayIfThtFails(
@@ -1317,7 +1338,19 @@ export default function (pi) {
"",
);
if (err) return err;
return textResult(`SQL approvato (sessione ${session}).`);
// F7's only prerequisite IS sql_approved (workflow.yaml): the phase gate
// after it decides nothing new — close the phase here.
const closed = advancePhaseAndFinalize(ctx, session, curNum);
if (closed.err) return closed.err;
if (closed.finalized) {
lockActive = false;
lastSteered = false;
return textResult(`SQL approvato — fase chiusa e sessione finalizzata (${session}).`);
}
return textResult(
`SQL approvato — Fase ${curNum} chiusa automaticamente. ` +
'NON presentare reviewer_confirm kind:"phase" per questa fase; prosegui con la successiva (datamart/memorie).',
);
}
return textResult(`Approvato (kind=${kind}, sessione ${session}).`);
} catch (fatal) {