* test(#4600): pin explicit-flag-overrides-gate precedence * fix(#4600): explicit --converge overrides the convergence feature gate PLAN_STRATEGY=converge is set only by an explicit --converge/--cross-ai, so gating it on workflow.plan_review_convergence made an explicit operator flag lose to a config default and stop the run with a question-shaped success. The config remains the default for non-flag invocation; the flag now wins, and the step says so instead of gate-and-exit. * test(#4600): pin the precedence contract sentences as written * fix(#4600): keep the precedence sentence on one line The pinned contract phrase wrapped across a line break, so the writer-contract assertion could not match it. * fix(#4600): document the flag-overrides-gate precedence on user surfaces commands/gsd/autonomous.md and docs/COMMANDS.md still said --converge requires workflow.plan_review_convergence=true; both now state the override. Changeset typed Changed with the docs update alongside. * docs(#4600): backfill changeset PR number * fix(#4600): restore the convergence gate mention in user surfaces * test(#4600): pin the dispatched convergence run against the config gate * fix(#4600): override the convergence gate on the dispatched run Emitted-Drift-Ack-Growth: autonomous.md — #4600: the converge dispatch appends --override-gate inside the PLAN_STRATEGY conditional, and the precedence sentences replace the stale fail-fast instruction Emitted-Drift-Ack-Growth: plan-review-convergence.md — #4600: config gate 1.5 honors an explicit --override-gate dispatch (token-anchored) while the standalone veto and config-get default are preserved --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -26,6 +26,7 @@ const STEP_FAIL_FAST_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'auton
|
||||
const STEP_DISPATCH_BG_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-dispatch-bg.md');
|
||||
const STEP_DISPATCH_INLINE_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-dispatch-inline.md');
|
||||
const STEP_LOOP_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-loop.md');
|
||||
const CONVERGENCE_WORKFLOW_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'plan-review-convergence.md');
|
||||
|
||||
function read(filePath) {
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
@@ -56,27 +57,95 @@ describe('autonomous --converge flag (#711)', () => {
|
||||
assert.match(workflow, /converge\|cross-ai/, 'workflow should accept --converge and --cross-ai');
|
||||
});
|
||||
|
||||
test('workflow fails fast when convergence is requested but disabled', () => {
|
||||
// #2994: this check lives in the converge-fail-fast step file now
|
||||
test('explicit --converge overrides the config gate (#4600)', () => {
|
||||
// #2994: this contract lives in the converge-fail-fast step file now
|
||||
// (state:plan-strategy-converge) — the host only carries the gated
|
||||
// conditional-read stub.
|
||||
// #4600: an explicit `--converge`/`--cross-ai` (PLAN_STRATEGY=converge is
|
||||
// set by nothing else) must WIN over `workflow.plan_review_convergence`
|
||||
// — the config is the default for non-flag invocation, not a veto over an
|
||||
// explicit operator request. The step must therefore not gate on the
|
||||
// config at all, and must state the precedence so a runtime agent
|
||||
// executes it as written.
|
||||
const workflow = read(WORKFLOW_PATH);
|
||||
const step = read(STEP_FAIL_FAST_PATH);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/gsd:section id="converge-fail-fast" when="state:plan-strategy-converge"/,
|
||||
'workflow should gate the fail-fast check behind state:plan-strategy-converge',
|
||||
'workflow should keep the step behind state:plan-strategy-converge',
|
||||
);
|
||||
assert.match(
|
||||
assert.doesNotMatch(
|
||||
step,
|
||||
/config-get workflow\.plan_review_convergence/,
|
||||
'converge-fail-fast step should check workflow.plan_review_convergence before planning',
|
||||
'the step must not gate an explicit flag on workflow.plan_review_convergence (#4600)',
|
||||
);
|
||||
assert.doesNotMatch(step, /exit 1/, 'the step must not stop an explicit-flag run');
|
||||
assert.match(
|
||||
step,
|
||||
/OVERRIDES the `workflow\.plan_review_convergence` config gate/,
|
||||
'the step must state that the explicit flag overrides the config gate',
|
||||
);
|
||||
assert.match(
|
||||
step,
|
||||
/gsd config-set workflow\.plan_review_convergence true/,
|
||||
'converge-fail-fast step should print the enable command instead of silently downgrading',
|
||||
/invocation carries `--override-gate`/,
|
||||
'the step must state that the dispatched convergence run carries the override flag (#4600)',
|
||||
);
|
||||
assert.match(
|
||||
step,
|
||||
/without the flag, `PLAN_STRATEGY` is `local`/,
|
||||
'the step must state that the config is not consulted on the non-flag autonomous path',
|
||||
);
|
||||
});
|
||||
|
||||
test('the dispatched convergence run bypasses the config gate (#4600)', () => {
|
||||
// End-to-end contract: the fail-fast step proceeding is not enough — the
|
||||
// dispatched gsd-plan-review-convergence workflow has its own §1.5 gate
|
||||
// that would veto the same run one step later. The autonomous dispatch
|
||||
// must carry an explicit override the gate honors; the veto itself stays
|
||||
// for standalone invocation, where the config gate is documented behavior.
|
||||
const workflow = read(WORKFLOW_PATH);
|
||||
const convergence = read(CONVERGENCE_WORKFLOW_PATH);
|
||||
const howTo = read(HOW_TO_PATH);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/--override-gate/,
|
||||
'the autonomous converge dispatch must carry --override-gate so the dispatched run cannot be vetoed by the config gate (#4600)',
|
||||
);
|
||||
assert.match(
|
||||
convergence,
|
||||
/--override-gate/,
|
||||
'plan-review-convergence must honor a --override-gate dispatch instead of failing fast (#4600)',
|
||||
);
|
||||
assert.match(
|
||||
convergence,
|
||||
/gsd-plan-review-convergence is disabled \(workflow\.plan_review_convergence=false\)/,
|
||||
'the §1.5 veto must remain for standalone invocation, where the config gate decides (#4600)',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/fail fast unless the existing convergence feature gate/,
|
||||
'the host must not instruct a runtime agent to fail fast on the gate before the converge step (#4600)',
|
||||
);
|
||||
assert.match(
|
||||
howTo,
|
||||
/overrides the gate for that run/,
|
||||
'the how-to must document that an explicit --converge overrides the gate on /gsd-autonomous (#4600)',
|
||||
);
|
||||
// Security-review constraint: the override must be appended conditionally on the converge
|
||||
// strategy (never ride on local-strategy runs) and parsed token-anchored by §1.5.
|
||||
// Search from the conditional: the host prose at the precedence sentence also names the
|
||||
// flag, so a bare indexOf would resolve there and the ordering check could never pass.
|
||||
const conditionalAt = workflow.indexOf('if [ "${PLAN_STRATEGY}" = "converge" ]; then');
|
||||
const overrideAt = workflow.indexOf('--override-gate', conditionalAt);
|
||||
assert.ok(
|
||||
conditionalAt !== -1 && overrideAt !== -1,
|
||||
'the PLAN_STRATEGY=converge conditional must exist and append --override-gate (#4600)',
|
||||
);
|
||||
assert.ok(
|
||||
convergence.includes('(^|[[:space:]])--override-gate([[:space:]]|$)'),
|
||||
'§1.5 must match --override-gate token-anchored so no other argument can carry it (#4600)',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user