feat(ms-ai-architect): G2 — generaliser stamp-guard til version-label-streng (TDD, kaster på malformert) [skip-docs]

This commit is contained in:
Kjell Tore Guttormsen 2026-07-04 22:51:52 +02:00
commit 6f82572dba
2 changed files with 82 additions and 8 deletions

View file

@ -148,6 +148,30 @@ export function buildKbHeader(meta) {
return `${header}\n---\n`;
}
/**
* Format the `verified_by` judge-version label. Returns the WHOLE label (`judge-v3.1`).
* Absence (undefined) the current JUDGE_VERSION default; otherwise the value an integer
* major (`3` judge-v3) or a `major.minor` string/float (`'3.2'`/`3.2` judge-v3.2) is
* validated against a strict numeric-label regex and honoured. Mirrors the DATE_RE throw
* discipline (stampVerifiedMeta): a malformed label THROWS rather than silently falling back
* to the default, so a caller bug surfaces instead of shipping a mislabelled provenance stamp
* into a public file.
*
* @param {number|string} [judgeVersion] integer major or `major.minor`; absent default
* @returns {string} the `judge-v<label>` provenance label
* @throws if judgeVersion is present but not a `\d+(\.\d+)?` numeric label
*/
function formatJudgeVersion(judgeVersion) {
if (judgeVersion === undefined) return `judge-v${JUDGE_VERSION}`;
const label = String(judgeVersion);
if (!/^\d+(?:\.\d+)?$/.test(label)) {
throw new Error(
`formatJudgeVersion: malformed judge version '${judgeVersion}' (expected integer or major.minor)`,
);
}
return `judge-v${label}`;
}
/**
* Born-verified gate (Spor 3 Port 2): stamp `verified`/`verified_by` onto a reference
* meta IFF the judge verdict passed otherwise REFUSE (throw). This encodes
@ -157,7 +181,7 @@ export function buildKbHeader(meta) {
* failing verdict means no file is born.
*
* @param {object} baseMeta the reference meta (title/status/category/source/lastUpdated)
* @param {{pass: boolean, judgeVersion?: number, by?: 'human'}} verdict
* @param {{pass: boolean, judgeVersion?: number|string, by?: 'human'}} verdict
* @param {string} today YYYY-MM or YYYY-MM-DD, supplied by the caller (lib stays pure)
* @returns {object} baseMeta with type='reference' + verified + verified_by set
* @throws if the verdict is not passing, or today is not a valid date
@ -169,14 +193,12 @@ export function stampVerifiedMeta(baseMeta, verdict, today) {
if (!DATE_RE.test(String(today ?? ''))) {
throw new Error(`stampVerifiedMeta: invalid today date '${today}' (expected YYYY-MM[-DD])`);
}
// Default stamp = the current JUDGE_VERSION label ('3.1'). An explicit integer override
// is still honoured (judgeVersion:3 → judge-v3); a minor-bearing string override is not
// yet (falls back to default) — tracked as a §8 follow-up, harmless since the pipeline
// always uses the default.
// Default stamp = the current JUDGE_VERSION label ('3.1'). An explicit override — integer
// (judgeVersion:3 → judge-v3) OR minor-bearing string/float (judgeVersion:'3.2' → judge-v3.2)
// — is honoured via formatJudgeVersion, which THROWS on a malformed label rather than silently
// falling back (§8 follow-up closed: the string override is no longer dropped).
const verifiedBy =
verdict.by === 'human'
? 'human'
: `judge-v${Number.isInteger(verdict.judgeVersion) ? verdict.judgeVersion : JUDGE_VERSION}`;
verdict.by === 'human' ? 'human' : formatJudgeVersion(verdict.judgeVersion);
return { ...baseMeta, type: 'reference', verified: today, verified_by: verifiedBy };
}

View file

@ -208,6 +208,58 @@ test('stampVerifiedMeta honours an explicit judgeVersion', () => {
assert.equal(stampVerifiedMeta(base, { pass: true, judgeVersion: 3 }, '2026-06-29').verified_by, 'judge-v3');
});
// --- G2: version-label generalisation (formatJudgeVersion via stampVerifiedMeta) -----
// The stamp guard now honours a minor-bearing label ('3.2' → judge-v3.2), preserves the
// integer path (regression above), defaults on absence, and THROWS on a malformed label
// rather than silently falling back — a mislabel would be a provenance lie in a public file.
test('stampVerifiedMeta honours a minor-bearing string judgeVersion override', () => {
const base = { title: 'T', status: 'GA', category: 'c',
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
assert.equal(
stampVerifiedMeta(base, { pass: true, judgeVersion: '3.2' }, '2026-06-29').verified_by,
'judge-v3.2',
);
});
test('stampVerifiedMeta honours a float judgeVersion override', () => {
const base = { title: 'T', status: 'GA', category: 'c',
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
assert.equal(
stampVerifiedMeta(base, { pass: true, judgeVersion: 3.2 }, '2026-06-29').verified_by,
'judge-v3.2',
);
});
test('stampVerifiedMeta defaults verified_by to judge-v3.1 when judgeVersion is absent', () => {
const base = { title: 'T', status: 'GA', category: 'c',
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
assert.equal(
stampVerifiedMeta(base, { pass: true }, '2026-06-29').verified_by,
'judge-v3.1',
);
});
test('stampVerifiedMeta throws on a malformed judgeVersion label (no silent fallback)', () => {
const base = { title: 'T', status: 'GA', category: 'c',
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
for (const bad of ['3.2.1', '', 'v3', '3.2 x']) {
assert.throws(
() => stampVerifiedMeta(base, { pass: true, judgeVersion: bad }, '2026-06-29'),
/judge version|malformed/i,
`expected throw for judgeVersion=${JSON.stringify(bad)}`,
);
}
});
test('stampVerifiedMeta → composeKbFile round-trips a minor-bearing judge version (end-to-end)', () => {
const base = { title: 'Azure AI Search', status: 'GA', category: 'rag-architecture',
source: 'https://learn.microsoft.com/azure/search/x', lastUpdated: '2026-06' };
const stamped = stampVerifiedMeta(base, { pass: true, judgeVersion: '3.2' }, '2026-06-29');
const file = composeKbFile(stamped, '## Introduksjon\n\nKort.\n');
assert.equal(parseVerifiedByHeader(file), 'judge-v3.2');
});
test('stampVerifiedMeta supports a human verification path', () => {
const base = { title: 'T', status: 'GA', category: 'c',
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };