fix(#3786): authorize mutable scope from live observation only in the quick planner (#4005)

* test(#3786): the quick planner constraints must carry a mutable-scope authority rule

* fix(#3786): authorize mutable scope from live observation only in the quick planner

The quick planner could commit HISTORICAL scope as authorized edit or
verification scope before any live observation of the mutable state: a
minimized probe used cached PR-diff paths (65 of them, "pending
replacement") or broadened verification to the PR integration surface in
2 of 3 trials (#3786). One explicit authority requirement reduced that
to 0 of 3.

The new planner constraint authorizes mutable-state scope ONLY from a
live observation made at planning time (for conflict resolution, the
fresh merge index via git diff --name-only --diff-filter=U) or keeps
files/verify CONDITIONAL on it; historical STATE.md entries, recovery
notes, and cached PR/base diffs may guide investigation only. A
structural guard pins the rule in the shipped constraints block.

Emitted-Drift-Ack-Growth: quick.md — #3786: +620 bytes — one MUTABLE-SCOPE AUTHORITY constraint bullet added to the planner <constraints> block

* chore(#3786): changeset fragment (pr number backfilled after PR creation)

* chore(#3786): backfill changeset PR number (4005)

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-28 12:31:02 -04:00
committed by GitHub
parent c8f08b61fb
commit c3e667df34
3 changed files with 62 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 4005
---
**`/gsd-quick` no longer authorizes edit/verification scope from historical state** — when scope depends on mutable external state (a fresh merge index, PR diffs, the working tree), the planner must observe it live or keep the plan's scope conditional; cached PR-diff paths and stale recovery notes are investigation guidance only, so a merge-conflict task can no longer provisionally "authorize" 65 historical paths. (#3786)

View File

@@ -315,6 +315,7 @@ ${AGENT_SKILLS_PLANNER}
<constraints>
- Create a SINGLE plan with 1-3 focused tasks
- Quick tasks should be atomic and self-contained
- MUTABLE-SCOPE AUTHORITY (#3786): when concrete edit or verification scope depends on mutable external state (a merge index, PR/base diffs, the working tree), authorize scope ONLY from a live observation made at planning time — for conflict resolution that is the fresh merge index via `git diff --name-only --diff-filter=U` — or keep `files`/`verify` CONDITIONAL on that observation. Historical STATE.md entries, recovery notes, and cached PR/base diff paths may guide investigation only; they are never edit or verification authority, and a plan must not enumerate them as authorized files "pending replacement".
${RESEARCH_MODE ? '- Research findings are available — use them to inform library/pattern choices' : '- No research phase'}
${VALIDATE_MODE ? '- Target ~40% context usage (structured for verification)' : '- Target ~30% context usage (simple, focused)'}
${VALIDATE_MODE ? '- MUST generate `must_haves` in plan frontmatter (truths, artifacts, key_links)' : ''}

View File

@@ -0,0 +1,56 @@
'use strict';
// ─────────────────────────────────────────────────────────────────────────────
// #3786 — the /gsd-quick planner constraints must carry a mutable-scope
// AUTHORITY rule.
//
// A minimized planner probe committed HISTORICAL scope as authorized edit /
// verification scope in 2 of 3 trials: for a merge-conflict task whose fresh
// merge had not run, one trial provisionally authorized 65 cached PR-diff
// paths, another broadened verification to the whole PR integration surface.
// Adding one explicit requirement — authorized scope comes only from the
// fresh merge index — reduced failures to 0 of 3. The rule lives in the
// shipped planner prompt (quick.md's <constraints> block), so a structural
// guard over that text pins it.
// ─────────────────────────────────────────────────────────────────────────────
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const QUICK_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'quick.md');
// quick.md is shipped workflow text — the bytes ARE what the runtime loads,
// so a structural scan over it tests the deployed contract (same shape as
// tests/config-get-raw-guard.test.cjs; no allow-test-rule marker needed for
// .md reads).
function plannerConstraints() {
const md = fs.readFileSync(QUICK_MD, 'utf-8');
// quick.md carries TWO <constraints> blocks (planner at ~315, executor at
// ~479). Anchor to the PLANNER's: the last block opening before its
// subagent_type declaration — step reordering can never silently redirect
// the guard to another agent's block.
const plannerDispatch = md.indexOf('subagent_type="gsd-planner"');
assert.ok(plannerDispatch > 0, 'quick.md must dispatch the gsd-planner agent');
const start = md.lastIndexOf('<constraints>', plannerDispatch);
const end = md.indexOf('</constraints>', start);
assert.ok(start > 0 && end > start, 'quick.md must contain the planner <constraints> block');
return md.slice(start, end);
}
test('#3786: the quick planner constraints carry a mutable-scope authority rule', () => {
const constraints = plannerConstraints();
assert.ok(
/live observation/i.test(constraints) && /conditional/i.test(constraints),
'#3786: scope derived from mutable external state must be live-observed or kept conditional',
);
assert.ok(
/may guide investigation only/i.test(constraints),
'#3786: historical STATE.md/recovery/cached-diff paths must be labeled investigation-only, never edit/verification authority',
);
assert.ok(
constraints.includes('git diff --name-only --diff-filter=U'),
'#3786: the conflict-resolution case must name the fresh-merge-index command the probe validated',
);
});