revert: fix(surface) #3666 — CONTRIBUTING.md test-pattern violations (#3688)

Reverts c5657fcbfd (squash of PR #3666).

The production change was correct in intent. The new test file
tests/surface-state.test.cjs violates two CONTRIBUTING.md rules
that the project enforces specifically because they produce
passing-but-useless tests:

  1. ~15 try { ... } finally { cleanup(dir); } blocks in test bodies
     — CONTRIBUTING.md "Never use try/finally inside test bodies."
     Approved forms are beforeEach/afterEach hooks or t.after(...).

  2. 17 assert.match calls on rendered console.warn prose
     — CONTRIBUTING.md "Prohibited: Raw Text Matching on Test
     Outputs." Tests must assert on a typed structured surface
     (frozen reason enum) instead of the rendered prose, otherwise
     they pass-but-rot the moment a warning string is reworded.

The .changeset/witty-birds-gather.md fragment is also reverted so
the v1.43 changelog does not advertise a fix that is not on main.

A reworked PR addressing items 1-4 from the re-review (linked on
the closed PR thread) is welcome — the intent of the fix is right.

Refs #3666 #3662

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-18 00:20:36 -04:00
committed by GitHub
parent c2dc9e2532
commit cb154569cf
3 changed files with 36 additions and 427 deletions

View File

@@ -34,52 +34,6 @@ const { CLUSTERS, allClusteredSkills } = require('./clusters.cjs');
const { findInstallSourceRoot } = require('./runtime-artifact-layout.cjs');
const SURFACE_FILE_NAME = '.gsd-surface.json';
const KNOWN_PROFILE_NAMES = new Set(Object.keys(PROFILES));
/**
* Split a `baseProfile` string (single name or comma-composed) into the
* non-empty trimmed mode list that resolveProfile() would see.
*
* @param {string} baseProfile
* @returns {string[]} effective modes after split/trim/empty-strip
*/
function effectiveProfileModes(baseProfile) {
return baseProfile
.split(',')
.map((m) => m.trim())
.filter((m) => m.length > 0);
}
/**
* Inspect a `baseProfile` string (single name or comma-composed) and return
* any modes that aren't registered in PROFILES. Callers keep the raw string —
* resolveProfile() decides the resolution fallback.
*
* @param {string} baseProfile
* @returns {string[]} unknown modes
*/
function unknownProfileModes(baseProfile) {
return effectiveProfileModes(baseProfile).filter((m) => !KNOWN_PROFILE_NAMES.has(m));
}
/**
* Collect optional array fields that are present but not an array, so the
* reader and writer can emit a single warn diagnostic before coercing them
* to `[]` in normalizeSurfaceState. A missing field is *not* flagged — it
* defaults to `[]` silently (that's the lenient #3662 behavior).
*
* @param {Object} input
* @returns {string[]} field names with wrong type
*/
function mistypedOptionalFields(input) {
const wrong = [];
for (const field of ['disabledClusters', 'explicitAdds', 'explicitRemoves']) {
if (Object.prototype.hasOwnProperty.call(input, field) && !Array.isArray(input[field])) {
wrong.push(field);
}
}
return wrong;
}
// ---------------------------------------------------------------------------
// State IO
@@ -93,100 +47,42 @@ function mistypedOptionalFields(input) {
* @property {string[]} explicitRemoves
*/
/**
* Normalize a partial SurfaceState into the full four-field shape.
* Missing or non-array optional fields default to []; baseProfile must already
* be a non-empty string (callers gate on that before normalizing).
*
* @param {Object} input
* @returns {SurfaceState}
*/
function normalizeSurfaceState(input) {
return {
baseProfile: input.baseProfile,
disabledClusters: Array.isArray(input.disabledClusters) ? input.disabledClusters.slice() : [],
explicitAdds: Array.isArray(input.explicitAdds) ? input.explicitAdds.slice() : [],
explicitRemoves: Array.isArray(input.explicitRemoves) ? input.explicitRemoves.slice() : [],
};
}
/**
* Read the surface state from a runtime config directory.
*
* Returns `null` only when there is no usable surface state:
* - file is absent (silent — expected when no profile has been pinned),
* - file is unreadable, malformed JSON, non-object root, or missing/invalid
* `baseProfile` (each of these emits a `console.warn` diagnostic so callers
* don't silently fall back to `'full'` with no explanation).
*
* Missing or wrong-typed optional array fields (`disabledClusters`,
* `explicitAdds`, `explicitRemoves`) default to `[]` — they are meaningfully
* empty and the writer/reader stayed symmetric only by accident before #3662.
*
* @param {string} runtimeConfigDir
* @returns {SurfaceState|null}
* @returns {SurfaceState|null} null if file missing or corrupt
*/
function readSurface(runtimeConfigDir) {
const filePath = path.join(runtimeConfigDir, SURFACE_FILE_NAME);
let raw;
try {
raw = fs.readFileSync(filePath, 'utf8');
} catch (err) {
if (err && err.code === 'ENOENT') return null;
console.warn(`[gsd] readSurface(${filePath}): unreadable (${err && (err.code || err.message)}); falling back to no surface state.`);
const raw = fs.readFileSync(filePath, 'utf8');
const parsed = JSON.parse(raw);
// Structural validation — must have these fields with expected types
if (typeof parsed !== 'object' || parsed === null) return null;
if (typeof parsed.baseProfile !== 'string') return null;
if (!Array.isArray(parsed.disabledClusters)) return null;
if (!Array.isArray(parsed.explicitAdds)) return null;
if (!Array.isArray(parsed.explicitRemoves)) return null;
return {
baseProfile: parsed.baseProfile,
disabledClusters: parsed.disabledClusters,
explicitAdds: parsed.explicitAdds,
explicitRemoves: parsed.explicitRemoves,
};
} catch {
return null;
}
let parsed;
try {
parsed = JSON.parse(raw);
} catch (err) {
console.warn(`[gsd] readSurface(${filePath}): malformed JSON (${err.message}); falling back to no surface state.`);
return null;
}
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
console.warn(`[gsd] readSurface(${filePath}): expected JSON object root; falling back to no surface state.`);
return null;
}
if (typeof parsed.baseProfile !== 'string' || effectiveProfileModes(parsed.baseProfile).length === 0) {
console.warn(`[gsd] readSurface(${filePath}): missing, non-string, blank, or comma-only 'baseProfile'; falling back to no surface state.`);
return null;
}
const unknownModes = unknownProfileModes(parsed.baseProfile);
if (unknownModes.length > 0) {
console.warn(`[gsd] readSurface(${filePath}): unknown profile mode(s) in 'baseProfile': ${unknownModes.join(', ')} (valid: ${[...KNOWN_PROFILE_NAMES].join(', ')}); resolveProfile() will skip unknowns and may fall back to 'full'.`);
}
const mistyped = mistypedOptionalFields(parsed);
if (mistyped.length > 0) {
console.warn(`[gsd] readSurface(${filePath}): optional field(s) with wrong type (expected array): ${mistyped.join(', ')}; coercing to [].`);
}
return normalizeSurfaceState(parsed);
}
/**
* Write the surface state atomically via the platform seam (mkdir + tmp+rename).
*
* Input is normalized to the full four-field shape so partial / hand-rolled
* objects cannot land on disk and trip readSurface later (#3662 symmetry fix).
* `baseProfile` is the only load-bearing field — callers must supply it as a
* non-empty string.
*
* @param {string} runtimeConfigDir
* @param {SurfaceState} surfaceState
*/
function writeSurface(runtimeConfigDir, surfaceState) {
if (!surfaceState || typeof surfaceState.baseProfile !== 'string' || effectiveProfileModes(surfaceState.baseProfile).length === 0) {
throw new TypeError("writeSurface: 'baseProfile' must be a non-blank string with at least one mode");
}
const unknownModes = unknownProfileModes(surfaceState.baseProfile);
if (unknownModes.length > 0) {
console.warn(`[gsd] writeSurface: unknown profile mode(s) in 'baseProfile': ${unknownModes.join(', ')} (valid: ${[...KNOWN_PROFILE_NAMES].join(', ')}); persisting anyway — resolveProfile() will skip unknowns.`);
}
const mistyped = mistypedOptionalFields(surfaceState);
if (mistyped.length > 0) {
console.warn(`[gsd] writeSurface: optional field(s) with wrong type (expected array): ${mistyped.join(', ')}; coercing to [].`);
}
const normalized = normalizeSurfaceState(surfaceState);
platformWriteSync(path.join(runtimeConfigDir, SURFACE_FILE_NAME), JSON.stringify(normalized, null, 2) + '\n');
platformWriteSync(path.join(runtimeConfigDir, SURFACE_FILE_NAME), JSON.stringify(surfaceState, null, 2) + '\n');
}
// ---------------------------------------------------------------------------