fix(voyage): coerce brief_version to string + quote template + update doc pin (closes #8 #11)

v5.1.0 shipped with an unquoted brief_version: 2.1 in trekbrief-template.md.
parseScalar coerced it to Number 2.1, and the sequencing gate guarded on
typeof === 'string', silently bypassing BRIEF_V51_MISSING_SIGNALS.

Three-part atomic fix:
- brief-validator.mjs:87+149 now accepts both string and number forms via
  String(fm.brief_version) coercion.
- trekbrief-template.md quotes the value so new briefs parse as String.
- doc-consistency.test.mjs pins the QUOTED form going forward.

Three regression tests added in brief-validator.test.mjs.
This commit is contained in:
Kjell Tore Guttormsen 2026-05-14 21:36:10 +02:00
commit 4c85a2c22b
4 changed files with 44 additions and 8 deletions

View file

@ -84,8 +84,12 @@ export function validateBriefContent(text, opts = {}) {
}
}
// Sequencing gate: brief_version ≥ 2.1 requires phase_signals OR phase_signals_partial.
if (typeof fm.brief_version === 'string') {
const vm = fm.brief_version.match(/^(\d+)\.(\d+)$/);
// Coerce to String so the gate fires regardless of whether YAML parsed the value as
// a string ("2.1") or a number (2.1). v5.1.0 shipped with an unquoted-2.1 template
// that silently bypassed this gate — fix locked in by quoting the template AND
// accepting both shapes here as defense-in-depth (v5.1.1, finding 3c834097/df1435a2).
if (typeof fm.brief_version === 'string' || typeof fm.brief_version === 'number') {
const vm = String(fm.brief_version).match(/^(\d+)\.(\d+)$/);
if (vm) {
const major = Number(vm[1]);
const minor = Number(vm[2]);
@ -146,8 +150,8 @@ export function validateBriefContent(text, opts = {}) {
}
}
if (typeof fm.brief_version === 'string') {
const m = fm.brief_version.match(/^(\d+)\.(\d+)$/);
if (typeof fm.brief_version === 'string' || typeof fm.brief_version === 'number') {
const m = String(fm.brief_version).match(/^(\d+)\.(\d+)$/);
if (!m) {
warnings.push(issue('BRIEF_VERSION_FORMAT', `brief_version "${fm.brief_version}" not in N.M form`));
}