* test(#2070): fail-first tests for adaptive model_profile and models tier validation
Encodes the three acceptance criteria from #2070 plus the boundary cases the
resolver silently ignores today (non-string values, empty string, mistyped
phase-type key), and pins VALID_TIERS to a catalog-derived set.
Red phase: these fail against current src/ by design.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2070): accept adaptive model_profile in validate health; warn on invalid models tiers (W022)
W004 sourced its profile list from a hand-maintained literal that predated the
adaptive profile, so `"model_profile": "adaptive"` was false-flagged. It now
reads VALID_PROFILES, which model-catalog.cts derives from model-catalog.json.
models.<phase_type> was validated nowhere: the resolver's tier gate silently
drops unknown values, so a typo like `"planning": "opuss"` was an undiagnosable
no-op. A new W022 flags unknown phase-type keys and invalid tier values
(including non-string values, which the same gate also drops).
VALID_TIERS moves from a function-local literal in model-resolver.cts to a
catalog-derived export, so health and the resolver cannot disagree by
construction rather than by parity test. Object.values(adaptiveTierMap) is
['opus','sonnet','haiku'] plus 'inherit' — identical to the previous literal,
so resolution behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2070): changeset for validate health adaptive profile + W022
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2070): close review findings — malformed models, tier-list duplication, changeset gate
Review of the initial fix surfaced three real defects, folded in per the
no-defer rule:
1. verify.cts: the W022 guard skipped a top-level `models` that is present but
not a plain object (`[]`, `"opus"`, `5`, `true`). The resolver ignores those
identically, so they were the same undiagnosable no-op #2070 targets — just
one level up. They now warn; absent/null/{} stay silent.
2. config-loader.cts: RUNTIME_OVERRIDE_TIERS was a second hardcoded copy of the
tier vocabulary this change had just de-hardcoded elsewhere. It now derives
from the catalog via ADAPTIVE_TIER_VALUES (no 'inherit' — runtime overrides
resolve to a concrete tier). Byte-equivalent to the old literal.
3. scripts/changeset/lint.cjs: USER_FACING_PREFIXES omitted `src/`. Post-ADR-457
the product source is src/*.cts compiled to a gitignored gsd-core/bin/lib,
so the `gsd-core/` prefix is dead coverage for library code and a src/-only
PR could merge with no release note — including this one. Adding `src/`
closes the gate; tests/ stays non-user-facing.
Also corrects a false docstring in the VALID_TIERS test: value-equality cannot
detect a re-hardcoded literal, so the test no longer claims it does.
Two review findings were rejected with evidence rather than actioned:
- W021 double-allocation is governed by ADR-612 ("W021 renumber -> void ...
kept, message-disambiguated"), not a defect.
- Global-defaults validation would be a false-positive generator: config-loader
reads ~/.gsd/defaults.json only on the "no .planning/" branch, and health
early-returns E001 without .planning/, so those values provably never affect
resolution in any context health can run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* test(#2070): regenerate install goldens for the changeset-lint change
scripts/ ships as an installed artifact, so scripts/changeset/lint.cjs's content
hash is pinned in all 18 runtime golden fixtures. Adding 'src/' to
USER_FACING_PREFIXES changed that hash and tripped every golden parity check.
Regenerated via `npm run gen:golden`; the only delta is the lint.cjs hash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2070): backfill PR number 2336 into changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>