test(#3090): read the body Status, not the frontmatter key that shadows it

stateExtractField tries bold **Field:**, then a plain ^Field: line, first match
wins. STATE.md's frontmatter carries a `status:` key that appears before the
body's plain `Status:` line, so extracting "Status" from unstripped content
returns the frontmatter value and never the body prose the test was written
against. Scoping the lookup to stripFrontmatter() fixes it.

The tempting fix was to assert the frontmatter enum instead, on the reasoning
that a typed value beats matching prose. That would have been wrong and would
have weakened the test: normalizeStateStatus maps both "Phase complete — ready
for verification" and "Verifying Phase N" onto the same 'verifying' enum, so
the enum cannot tell phase-complete from mid-verification, which is precisely
the distinction this case exists to prove. Typed is not automatically stronger
when the type conflates the cases under test.

Case 4 carried the same collision and was passing only because
normalizeStateStatus falls through to the raw text when no known pattern
matches, so frontmatter and body happened to agree. Fixed alongside it rather
than left for the next person to trip over.

All fourteen conversions on this branch were audited against the same failure
mode. The collision can only arise where the frontmatter key and the body field
name are identical case-insensitively — Status is the only such field, since
every other frontmatter key is snake_case against a Title Case body label.

Refs #3057

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
sim
2026-08-05 17:54:32 -04:00
parent 7dd9e59f6b
commit a4560c3669

View File

@@ -7085,6 +7085,7 @@ const path = require('node:path');
const { cleanup } = require('./helpers.cjs');
const { stateExtractField } = require('../gsd-core/bin/lib/state-document.cjs');
const { collectSection } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
const { stripFrontmatter } = require('../gsd-core/bin/lib/frontmatter.cjs');
const ROOT = path.join(__dirname, '..');
const TOOLS_PATH = path.join(ROOT, 'gsd-core', 'bin', 'gsd-tools.cjs');
@@ -7336,8 +7337,14 @@ describe('bug #397: executor-authored STATE.md fields must be preserved', () =>
const r = runGsdState(['advance-plan'], dir);
assert.ok(r.success, `advance-plan failed: ${r.error}`);
const after = readState(dir);
// The Configuration-level Status must not be clobbered
const status = stateExtractField(after, 'Status');
// The Configuration-level Status must not be clobbered. Scope extraction
// to the body (frontmatter stripped): the frontmatter `status:` key and
// the body `Status:` field name collide case-insensitively under
// stateExtractField's plain-line pattern (#1255's landmine — see
// state-transition.cts), and frontmatter's `status` is a normalized
// enum (normalizeStateStatus), not the executor-authored prose this
// assertion is proving was preserved.
const status = stateExtractField(stripFrontmatter(after), 'Status');
assert.ok(status, 'Status field not found after advance-plan');
assert.strictEqual(
status,
@@ -7357,7 +7364,14 @@ describe('bug #397: executor-authored STATE.md fields must be preserved', () =>
const r = runGsdState(['advance-plan'], dir);
assert.ok(r.success, `advance-plan failed: ${r.error}`);
const after = readState(dir);
const status = stateExtractField(after, 'Status');
// Scope extraction to the body (frontmatter stripped): stateExtractField's
// plain-line pattern matches the frontmatter `status:` key before the body
// `Status:` field (case-insensitive collision — #1255). Frontmatter status
// is normalizeStateStatus's coarser enum (e.g. both "Verifying Phase N"
// and "Phase complete — ready for verification" normalize to the SAME
// 'verifying' value), so asserting it here would be a WEAKER check than
// the body prose this test exists to prove was replaced.
const status = stateExtractField(stripFrontmatter(after), 'Status');
assert.ok(status, 'Status field not found after advance-plan');
// 'Ready to execute' is a known default and should be replaced
assert.notStrictEqual(