enhance(#4095): checkpoint:decision auto-selection is opt-in via auto_select (#4912)

* enhance(#4095): checkpoint:decision auto-selection is opt-in via auto_select

Auto-mode used to auto-select a checkpoint:decision's first <option>
unconditionally, making a decision checkpoint's safety depend on option
presentation order rather than an authored choice. Add an optional
auto_select="<option-id>" attribute on the <task> tag: absent, auto-mode
now escalates to a human exactly like gate="blocking-human" does; present,
it names the option auto-mode selects; naming an id with no matching
<option id> is a hard structural-validation error at plan-parse time
rather than a silent fallback to the first option. gate="blocking-human"
continues to win over everything, unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4095): anchor auto_select/id attribute regexes past hyphenated decoys

An isolated adversarial review of the auto_select work found that both new
attribute regexes used \b as their left anchor, which is a word boundary,
not a "start of attribute name" boundary. A decoy attribute ending in the
same word (e.g. data-id="...") sitting before the real id="..." on the
same <option> tag matched first, silently corrupting the extracted option
id. Anchor on (?:^|\s) instead so only the real attribute name can match.
Adds a regression test reproducing the exact decoy-attribute shape, plus a
Unicode option-id test from the same review pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4095): register auto-select-attribute.test.cjs in the docs-guard lane

lint-docs-guard-registration failed: the new test reads docs/reference/
plan-md.md but was not registered, so a future edit to that doc could
silently desync from the test without the guard catching it on the PR
that changed the doc. Registered alongside its direct precedents
(precondition-element.test.cjs, reversibility-tagging.test.cjs), which
read the same file for the same reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4095): fit the decision bullet under execute-phase.md's frozen byte ceiling

The remote gsd-test run caught what local checks missed: execute-phase.md
carries a frozen ADR-857 Phase-6 byte ceiling (93600) with only 36 bytes
of headroom before this change, and the original checkpoint:decision
wording pushed it to 93772 (over the ceiling). Cascaded into failures in
phase6-capstone-conformance, execute-phase-completion-reconciliation,
claude-orchestration, and the compact-content drift-report test.

Also caught: tests/package-legitimacy-gate.test.cjs anchors a
"decision is conditional, not unconditional" safety check on the literal
phrase "first option" in the decision bullet — which #4095 deliberately
removes, since there is no more unconditional first-option pick. The test
was asserting an assumption this change intentionally makes obsolete;
re-anchored on tokens that still identify the bullet ('decision',
'auto-spawn') without weakening what the test actually verifies (the
bullet must still carry a blocking-human carve-out).

Also fixed a word-order mismatch between my own new test's regex and the
actual doc text it was asserting against (tests/auto-select-attribute.test.cjs).

Regenerated the compact-content benchmark baseline
(tests/fixtures/compact-content-benchmark-baseline.json) to match the new
byte counts.

Emitted-Drift-Ack-Growth: gsd-executor.md — +7 bytes (49138 -> 49145), from the auto_select carve-out added to the checkpoint:decision auto-mode bullet; already trimmed once to fit the 49152 hard cap.
Emitted-Drift-Ack-Growth: execute-phase.md — +12 bytes (93564 -> 93576), from the same carve-out in the orchestrator's decision bullet; kept 24 bytes under the frozen 93600 ADR-857 ceiling after two rounds of trimming for clarity vs. margin.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#4095): backfill changeset PR number

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-09-20 23:47:01 -04:00
committed by GitHub
parent 029acd9158
commit eea9247c93
11 changed files with 477 additions and 11 deletions

View File

@@ -244,6 +244,40 @@ Full taxonomy, emission rules, and anti-patterns (chiefly: rating everything `on
---
## Auto-select
`auto_select` is an **optional** attribute on a `<task type="checkpoint:decision">` element (issue #4095). It names the `id` of the `<option>` that auto-mode (`workflow._auto_chain_active` / `workflow.auto_advance`) should select when the checkpoint is reached unattended.
```xml
<task type="checkpoint:decision" gate="blocking" auto_select="nextauth">
<decision>Select authentication provider</decision>
<options>
<option id="supabase"><name>Supabase Auth</name></option>
<option id="clerk"><name>Clerk</name></option>
<option id="nextauth"><name>NextAuth.js</name></option>
</options>
<resume-signal>Select: supabase, clerk, or nextauth</resume-signal>
</task>
```
**Semantics:**
| `auto_select` | Auto-mode behavior |
|---|---|
| Absent | Escalates to a human — same treatment as `gate="blocking-human"`. Auto-mode does not guess an answer from option order. |
| Names a real `<option id="…">` | Selects that option and logs `⚡ Auto-selected: [option id]`, then continues. |
| Names an id that does not match any `<option id="…">` | `verify plan-structure` fails at plan-parse time. Never a silent fallback to the first option. |
`gate="blocking-human"` continues to win over everything, unchanged — it stops for a human in every mode regardless of `auto_select`.
**Why:** before this attribute existed, auto-mode always picked the first `<option>`, and the planner convention of front-loading the recommended choice meant the safety of every decision checkpoint depended on presentation order — a detail no plan author was told was load-bearing. `auto_select` makes the unattended answer an authored decision instead of a byproduct of layout.
**Optional and back-compat for the structural validator:** a plan that omits `auto_select` on every `checkpoint:decision` task still passes `verify plan-structure` — the validator only rejects a *declared* `auto_select` that doesn't match any option id. What changes is auto-mode's *runtime* behavior (escalate instead of guessing), not plan-structure validity.
Full behavioral reference: `gsd-core/references/checkpoints.md` → `checkpoint:decision`.
---
## Task types
| Type | Use | Autonomy |
@@ -251,7 +285,7 @@ Full taxonomy, emission rules, and anti-patterns (chiefly: rating everything `on
| `auto` | Everything the executor can do independently. | Fully autonomous. |
| `tracer` | The leading thin end-to-end slice a plan starts with by default (tracer-first) — production-quality, wired through every layer, with a real end-to-end `<verify>`. | Fully autonomous; after committing, the executor runs the tracer's `<verify>` as an early integration gate. A tracer carrying `gate="blocking-human"` STOPs for a human in every mode, auto included. Otherwise autonomous runs halt on failure before expansion, and interactive runs honor `workflow.human_verify_mode` (#3299): under the `end-of-phase` default a `<verify>` carrying only `<automated>` is re-run and, on success, expansion continues with **no** checkpoint (failure still halts); under `mid-flight`, or when the tracer carries `<human-check>`, a `checkpoint:human-verify` is presented. Full precedence chain: `gsd-core/references/checkpoints.md` → "Tracer feedback gate". |
| `checkpoint:human-verify` | Visual or functional verification that requires a human to look at a running UI or service. | Pauses execution; presents to the developer; resumes on approval. |
| `checkpoint:decision` | Implementation choices that arose during execution and require the developer's input. | Pauses execution; presents options; resumes on selection. |
| `checkpoint:decision` | Implementation choices that arose during execution and require the developer's input. | Pauses execution; presents options; resumes on selection. In auto-mode, an `auto_select="<option-id>"` attribute lets the plan name the unattended answer — see [Auto-select](#auto-select). |
| `checkpoint:human-action` | Truly unavoidable manual steps (account creation, hardware interaction). Used sparingly. | Pauses execution; resumes on confirmation. |
Plans that contain any checkpoint task must set `autonomous: false` in frontmatter.