* test(#2711): derive the omit-rule guarded set from the corpus instead of a hand list
The GUARDED array was a Goodhart metric: it reported green across 15
non-compliant workflows for no better reason than that nobody had added them to
it. The guard now derives its set — every workflow emitting a model="{…}"
dispatch site must state the omit-on-inherit/empty rule — and asserts the
derivation is non-empty so a broken scan fails rather than passes.
Rule detection stays a PROPERTY check, not a template match: plan-phase.md and
execute-phase.md state it in different words and both are correct.
RED expected on 15 workflows: audit-milestone, code-review, code-review-fix,
debug, discuss-phase-assumptions, docs-update, map-codebase, new-milestone,
new-project, quick, secure-phase, ui-phase, ui-review, validate-phase,
verify-work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPq9ovaovP2UvSVLjD4Lso
* fix(#2711): propagate the #2517 omit-on-inherit rule to all 15 unguarded workflows
15 of the 19 model=-dispatching workflows carried no omit-on-inherit/empty
guidance — 43 unguarded dispatch sites. Each would emit model="" whenever the
bound *_model resolved empty, which is the DEFAULT state on non-Claude runtimes:
the installer writes resolve_model_ids:"omit" into ~/.gsd/defaults.json for every
one of them (references/model-profiles.md:101), and resolveModelInternal returns
"" for that case (src/model-resolver.cts:383-386) and "inherit" for opus-tier
agents and the inherit profile (:395). Both 404 on runtimes without native tier
aliases — the failure #2517 documented and fixed in one file.
Each file now carries a `<!-- #2517 model-omit-on-inherit -->` blockquote naming
its own bound placeholders and linking the canonical statement in
references/model-profile-resolution.md, mirroring the `<!-- #2508
runtime-aware-dispatch -->` block already present in all 15. The rule text lives
in the reference; the workflows carry a pointer plus the one-line instruction, so
the next revision edits one file rather than fifteen.
plan-phase.md and execute-phase.md are deliberately untouched — they already
state the rule in their own wording, and the guard checks the property rather
than a template string.
No dispatch site is edited and no placeholder renamed: the #2684 binding guard
reports the same 19 files / 60 placeholders / 0 findings before and after, which
is the independence proof that this change is additive prose only. There is no
Hyrum's-Law routing change to disclose.
Placement is span-aware. An initial pass anchored to the #2508 marker, but in six
files that marker sits INSIDE the Agent(prompt="…") string, so the new paragraph's
literal model= landed in a dispatch call span and tripped the #2284 fail-closed
Hermes projection guard (bin/install.js:3704), refusing the install. Blocks are
now anchored before the opening Agent( of the span owning the first dispatch, and
verified to fall inside no span. gen:golden exits 0 across all 19 runtimes.
Fixes#2711
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPq9ovaovP2UvSVLjD4Lso
* fix(#2711): cite the issue number in the changeset body and tidy block placement
Review findings from the two orthogonal passes:
- The changeset body ended (#0). Repo convention across every prior fragment
(e.g. #2617/#2693, #2608, #2605) is that the trailing (#NNN) is the ISSUE
number, known at authoring time; only the frontmatter pr: field carries the 0
placeholder pending backfill. (#0) would have rendered a dead link in the
published release notes.
- new-milestone.md glued the inserted block directly under the preceding
paragraph with no blank line, inconsistent with the other 14 insertions.
- The derived-guard non-vacuity floor was >=17 against an actual derived count
of 19, tolerating a silent two-file regression. Tightened to >=19.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPq9ovaovP2UvSVLjD4Lso
* fix(#2711): reword the omit block so it survives Hermes projection, and exempt quick.md by size
The first block wording regressed two suites on the full matrix (4 failures on
both linux-node22 and linux-node24). gen:golden passing was not sufficient
evidence — it exercises the installer's own fail-closed guard, which is
narrower than the dedicated tests.
1. tests/fix-2284-hermes-agent-delegate-task-projection.test.cjs asserts that
the INSTALLED code-review-fix.md contains no `model=` anywhere outside a
string literal — masked whole-file, not merely inside call spans. The block's
backticked `model=` survived the mask. The assertion is right: on Hermes the
projection strips the parameter because delegate_task has no per-call model
at all, so instructing the orchestrator to "omit the model= parameter" is
advice about a parameter that does not exist there. The block now says "the
`model` parameter" and carries no bare `model=` token.
2. tests/prompt-injection-scan.security.test.cjs flagged quick.md at 50,164
normalized chars against a 50,000 prompt-stuffing threshold. quick.md sits
just under the line on next, so any insertion trips it — the situation
review.md is already documented for in SIZE_ONLY_WORKFLOWS ("sat at 49,971
chars — 29 below the threshold — so it was going to trip on whatever was
added to it next"). quick.md joins it with the same justification. This is a
size-finding exemption only: the file is still fully injection scanned, and
every other security check still runs on it.
Because the canonical block can no longer carry a literal `model=`, the guard's
detector now accepts the `<!-- #2517 model-omit-on-inherit -->` marker as the
canonical signal, falling back to the inline-prose property for the four files
that predate it (plan-phase, execute-phase, scan, ship — all four match the
legacy branch). That is strictly stronger than word-proximity matching, and it
keeps the guard a property check rather than a template match.
Verified: derived guard 19/19 with 0 missing; the #2684 binding guard unchanged
at 19 files / 60 placeholders / 0 findings; no inserted block contains a bare
model= token; the masked-projection assertion passes for code-review-fix.md;
gen:golden exits 0 across all 19 runtimes; lint:ci exits 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPq9ovaovP2UvSVLjD4Lso
* chore(#2711): backfill changeset PR number
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPq9ovaovP2UvSVLjD4Lso
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>