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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user