Files
msd-core/tests/model-catalog-valid-tiers.test.cjs
Tom Boucher b2961c3f69 fix(#2070): accept adaptive model_profile in validate health; warn on invalid models tiers (W022) (#2336)
* 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>
2026-07-17 06:48:04 -04:00

81 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* Regression/parity tests for VALID_TIERS and RUNTIME_OVERRIDE_TIERS (#2070).
*
* src/model-resolver.cts:336 currently declares a function-local,
* non-exported `VALID_TIERS` set. src/model-catalog.cts now exports a
* `VALID_TIERS` computed as `[...Object.values(catalog.adaptiveTierMap),
* 'inherit']`. NOTE: a value-equality assertion against that same derivation
* cannot, by construction, distinguish "genuinely catalog-derived" from "a
* hardcoded literal that happens to equal today's catalog output" — both
* would pass this test. What this test actually guarantees is narrower but
* still useful: VALID_TIERS equals the catalog-derived set right now, so it
* fails loudly the moment the two go out of sync (e.g. someone drops
* 'inherit', or model-catalog.json gains/loses a tier and VALID_TIERS isn't
* updated to match) — an impl/catalog divergence guard, not a
* hardcoding detector.
*
* The RUNTIME_OVERRIDE_TIERS tests below cover review finding #7: a second,
* independent hardcoded tier list at src/config-loader.cts:193
* (`new Set(['opus', 'sonnet', 'haiku'])`, exported at line 769) is a
* parallel surface to VALID_TIERS. Fixed to derive from
* `Object.values(catalog.adaptiveTierMap)` (which — unlike VALID_TIERS —
* does NOT include 'inherit'), the parity assertion below fails loudly if
* RUNTIME_OVERRIDE_TIERS and VALID_TIERS ever drift apart.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const { catalog, VALID_TIERS } = require('../gsd-core/bin/lib/model-catalog.cjs');
const { RUNTIME_OVERRIDE_TIERS } = require('../gsd-core/bin/lib/config-loader.cjs');
describe('model-catalog VALID_TIERS derivation (#2070)', () => {
test('VALID_TIERS equals the catalog-derived set (adaptiveTierMap values plus "inherit") — catches impl/catalog divergence', () => {
const expected = new Set([...Object.values(catalog.adaptiveTierMap), 'inherit']);
assert.deepStrictEqual(
VALID_TIERS,
expected,
`VALID_TIERS must match catalog.adaptiveTierMap-derived tiers plus 'inherit': ${JSON.stringify([...(VALID_TIERS || [])])}`
);
});
test('VALID_TIERS pins current tier set to opus/sonnet/haiku/inherit', () => {
assert.deepStrictEqual(
VALID_TIERS,
new Set(['opus', 'sonnet', 'haiku', 'inherit']),
`Expected VALID_TIERS to equal {opus, sonnet, haiku, inherit}, got: ${JSON.stringify([...(VALID_TIERS || [])])}`
);
});
});
describe('config-loader RUNTIME_OVERRIDE_TIERS derivation (#2070 review finding 7)', () => {
test('RUNTIME_OVERRIDE_TIERS equals catalog.adaptiveTierMap values (no "inherit")', () => {
const expected = new Set(Object.values(catalog.adaptiveTierMap));
assert.deepStrictEqual(
RUNTIME_OVERRIDE_TIERS,
expected,
`RUNTIME_OVERRIDE_TIERS must be derived from catalog.adaptiveTierMap, not hardcoded: ${JSON.stringify([...(RUNTIME_OVERRIDE_TIERS || [])])}`
);
});
test('RUNTIME_OVERRIDE_TIERS pins current tier set to opus/sonnet/haiku', () => {
assert.deepStrictEqual(
RUNTIME_OVERRIDE_TIERS,
new Set(['opus', 'sonnet', 'haiku']),
`Expected RUNTIME_OVERRIDE_TIERS to equal {opus, sonnet, haiku}, got: ${JSON.stringify([...(RUNTIME_OVERRIDE_TIERS || [])])}`
);
});
test('RUNTIME_OVERRIDE_TIERS ∪ {"inherit"} equals VALID_TIERS (parity — fails loudly if the two surfaces drift)', () => {
const union = new Set([...RUNTIME_OVERRIDE_TIERS, 'inherit']);
assert.deepStrictEqual(
union,
VALID_TIERS,
`RUNTIME_OVERRIDE_TIERS plus 'inherit' must equal VALID_TIERS — these are two parallel tier-list surfaces that must never diverge: ` +
`RUNTIME_OVERRIDE_TIERS=${JSON.stringify([...(RUNTIME_OVERRIDE_TIERS || [])])}, VALID_TIERS=${JSON.stringify([...(VALID_TIERS || [])])}`
);
});
});