feat(ms-ai-architect): Spor 3 Port 2 — create-time-guard (born-verified contract) [skip-docs]
Type-aware create-guard for reference-filer (Spor 3 Port 1/Port 2): - kb-headers.mjs: parseTypeHeader/parseVerifiedHeader/parseVerifiedByHeader (samme top-500-byte bold-label-skann som parseSourceHeader). - transform.mjs: buildKbHeader er type-aware (reference krever source+verified+verified_by; non-reference kaster pa MS-Learn-source) + emitterer Type/Verified/Verified by; validateKbFile type-aware; stampVerifiedMeta = fodt-verifisert-gate (stempler KUN ved bestatt judge-verdikt, ellers kaster). - validate-kb-file.mjs (ny): kjorbar gate generatorer kaller for commit (exit != 0 ved kontraktbrudd). - generate-skills.md + kb-update.md + transform-prompt.md: wiret til fodt-verifisert kontrakt (header-felt + judge-steg + create-guard-gate). Suite 586/586 (33 nye + 1 invariant). Plugin-validering 239/0/0. Utenfor scope (flagget): generate-skills.sh legacy (sonnet/Cosmo/no-source), Cosmo-persona i generatorene (S-Cosmo), korpus-migrering av 306 filer (Spor 1).
This commit is contained in:
parent
0ded2f9413
commit
5a0ba1fc9e
10 changed files with 621 additions and 54 deletions
|
|
@ -24,8 +24,14 @@ import {
|
|||
buildToc,
|
||||
composeKbFile,
|
||||
insertToc,
|
||||
stampVerifiedMeta,
|
||||
} from '../../scripts/kb-update/lib/transform.mjs';
|
||||
import { parseSourceHeader } from '../../scripts/kb-update/lib/kb-headers.mjs';
|
||||
import {
|
||||
parseSourceHeader,
|
||||
parseTypeHeader,
|
||||
parseVerifiedHeader,
|
||||
parseVerifiedByHeader,
|
||||
} from '../../scripts/kb-update/lib/kb-headers.mjs';
|
||||
import { classifyChange } from '../../scripts/kb-update/lib/verify-out.mjs';
|
||||
import { loadTaxonomy } from '../../scripts/kb-update/lib/taxonomy.mjs';
|
||||
// The N4 (TOC-in-large-files) check is the cross-subsystem best-practice contract.
|
||||
|
|
@ -44,12 +50,17 @@ const SHORT_BODY = '## Introduksjon\n\nKort.\n';
|
|||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// A born-verified reference meta: Spor 3 Port 1 contract adds type/verified/verified_by
|
||||
// (reference files are "born verified" — stamped only after the v2 judge clears them).
|
||||
const META = {
|
||||
title: 'Azure AI Search - Hybrid Retrieval',
|
||||
status: 'GA',
|
||||
category: 'rag-architecture',
|
||||
type: 'reference',
|
||||
source: 'https://learn.microsoft.com/azure/search/hybrid-search-overview',
|
||||
lastUpdated: '2026-06',
|
||||
verified: '2026-06-29',
|
||||
verified_by: 'judge-v2',
|
||||
};
|
||||
|
||||
// --- buildKbHeader: mandatory fields, parseable by the real header scanners ----
|
||||
|
|
@ -76,6 +87,154 @@ test('buildKbHeader throws when a mandatory field is missing (status-felt obliga
|
|||
assert.throws(() => buildKbHeader({ ...META, lastUpdated: undefined }), /last.?updated/i);
|
||||
});
|
||||
|
||||
// --- Spor 3 Port 1/Port 2: type-aware create-guard contract --------------------
|
||||
// Every reference file is "born verified": carries Type + Verified + Verified by in
|
||||
// the header, all within the top-500-byte region the scanners read.
|
||||
|
||||
test('buildKbHeader emits Type, Verified, Verified by in the header (Port 1 contract)', () => {
|
||||
const h = buildKbHeader(META);
|
||||
assert.match(h, /\*\*Type:\*\*\s*reference/);
|
||||
assert.match(h, /\*\*Verified:\*\*\s*2026-06-29/);
|
||||
assert.match(h, /\*\*Verified by:\*\*\s*judge-v2/);
|
||||
});
|
||||
|
||||
test('buildKbHeader: the new fields are parseable in the top-500-byte region', () => {
|
||||
const h = buildKbHeader(META);
|
||||
assert.equal(parseTypeHeader(h), 'reference');
|
||||
assert.equal(parseVerifiedHeader(h), '2026-06-29');
|
||||
assert.equal(parseVerifiedByHeader(h), 'judge-v2');
|
||||
});
|
||||
|
||||
test('buildKbHeader: Verified by (the last header line) stays inside the 500-byte scan region for a realistic long title + URL', () => {
|
||||
// Verified by sits after Source (the long field) — guard that a realistic worst case
|
||||
// still fits, so a valid file is never wrongly flagged as missing verified_by.
|
||||
const h = buildKbHeader({
|
||||
title: 'Azure AI Foundry Model Router and Deployment Strategies for Enterprise',
|
||||
status: 'Public Preview',
|
||||
category: 'azure-ai-services',
|
||||
type: 'reference',
|
||||
source: 'https://learn.microsoft.com/en-us/azure/ai-foundry/concepts/model-router-deployment-strategies-overview',
|
||||
lastUpdated: '2026-06',
|
||||
verified: '2026-06-29',
|
||||
verified_by: 'judge-v2',
|
||||
});
|
||||
assert.equal(parseVerifiedByHeader(h), 'judge-v2', 'verified_by fell outside the 500-byte header region');
|
||||
});
|
||||
|
||||
test('buildKbHeader: a reference file MUST carry verified + verified_by (born-verified guard)', () => {
|
||||
assert.throws(() => buildKbHeader({ ...META, verified: undefined }), /verified/i);
|
||||
assert.throws(() => buildKbHeader({ ...META, verified_by: '' }), /verified_by|verified by/i);
|
||||
});
|
||||
|
||||
test('buildKbHeader: type defaults to reference when omitted (back-compat for callers)', () => {
|
||||
const { type, ...noType } = META;
|
||||
const h = buildKbHeader(noType);
|
||||
assert.equal(parseTypeHeader(h), 'reference');
|
||||
});
|
||||
|
||||
test('buildKbHeader: an unknown type is rejected', () => {
|
||||
assert.throws(() => buildKbHeader({ ...META, type: 'blogpost' }), /type/i);
|
||||
});
|
||||
|
||||
test('buildKbHeader: a non-reference file (methodology) needs NO source/verified', () => {
|
||||
// Template/methodology/regulatory are out of correctness-scope — they carry no MS source.
|
||||
const h = buildKbHeader({
|
||||
title: 'ROS-metodikk (NS 5814)',
|
||||
status: 'Stable',
|
||||
category: 'norwegian-public-sector-ai-governance',
|
||||
type: 'methodology',
|
||||
lastUpdated: '2026-06',
|
||||
});
|
||||
assert.match(h, /\*\*Type:\*\*\s*methodology/);
|
||||
assert.doesNotMatch(h, /\*\*Source:\*\*/);
|
||||
assert.doesNotMatch(h, /\*\*Verified:\*\*/);
|
||||
});
|
||||
|
||||
test('buildKbHeader: a non-reference file may NOT carry a Microsoft Learn source (contract)', () => {
|
||||
assert.throws(
|
||||
() => buildKbHeader({
|
||||
title: 'X', status: 'Stable', category: 'c', type: 'template',
|
||||
lastUpdated: '2026-06', source: 'https://learn.microsoft.com/azure/x',
|
||||
}),
|
||||
/source/i,
|
||||
);
|
||||
});
|
||||
|
||||
// --- validateKbFile: type-aware enforcement of the full contract ---------------
|
||||
|
||||
test('validateKbFile: a reference file missing verified/verified_by is invalid', () => {
|
||||
// Header has Source but no Verified / Verified by → not born-verified → rejected.
|
||||
const file =
|
||||
'# T\n\n**Last updated:** 2026-06\n**Status:** GA\n**Type:** reference\n' +
|
||||
'**Source:** https://learn.microsoft.com/x\n\n---\n\n## A\n\ntekst\n';
|
||||
const r = validateKbFile(file);
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(r.missing.includes('verified'), `expected 'verified' in ${r.missing}`);
|
||||
assert.ok(r.missing.includes('verified_by'), `expected 'verified_by' in ${r.missing}`);
|
||||
});
|
||||
|
||||
test('validateKbFile: a complete born-verified reference file is valid', () => {
|
||||
const file = buildKbHeader(META) + '\n## Introduksjon\n\nNoe innhold.\n';
|
||||
assert.equal(validateKbFile(file).valid, true);
|
||||
});
|
||||
|
||||
test('validateKbFile: a methodology file without source/verified is valid (out of scope)', () => {
|
||||
const file =
|
||||
'# ROS-metodikk\n\n**Last updated:** 2026-06\n**Status:** Stable\n**Type:** methodology\n\n' +
|
||||
'---\n\n## Metode\n\nNS 5814.\n';
|
||||
const r = validateKbFile(file);
|
||||
assert.equal(r.valid, true, `unexpected missing: ${r.missing}`);
|
||||
});
|
||||
|
||||
// --- stampVerifiedMeta: the born-verified gate (judge clears → stamp; else refuse) --
|
||||
// transform.mjs never calls the judge (pure lib). The command runs the v2 claim judge,
|
||||
// aggregates per-claim results into {pass}, and calls this. A failing verdict throws —
|
||||
// "nytt innhold gjennom judgen FØR commit; ellers ingen fil." (§4 Port 2.)
|
||||
|
||||
test('stampVerifiedMeta stamps verified=today + verified_by=judge-vN on a passing verdict', () => {
|
||||
const base = { title: 'T', status: 'GA', category: 'c',
|
||||
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
|
||||
const m = stampVerifiedMeta(base, { pass: true }, '2026-06-29');
|
||||
assert.equal(m.type, 'reference');
|
||||
assert.equal(m.verified, '2026-06-29');
|
||||
assert.equal(m.verified_by, 'judge-v2'); // current GATE-PASS judge
|
||||
});
|
||||
|
||||
test('stampVerifiedMeta honours an explicit judgeVersion', () => {
|
||||
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 }, '2026-06-29').verified_by, 'judge-v3');
|
||||
});
|
||||
|
||||
test('stampVerifiedMeta supports a human verification path', () => {
|
||||
const base = { title: 'T', status: 'GA', category: 'c',
|
||||
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
|
||||
assert.equal(stampVerifiedMeta(base, { pass: true, by: 'human' }, '2026-06-29').verified_by, 'human');
|
||||
});
|
||||
|
||||
test('stampVerifiedMeta REFUSES to stamp when the judge verdict is not passing', () => {
|
||||
const base = { title: 'T', status: 'GA', category: 'c',
|
||||
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
|
||||
assert.throws(() => stampVerifiedMeta(base, { pass: false }, '2026-06-29'), /judge|verdict|verified/i);
|
||||
assert.throws(() => stampVerifiedMeta(base, null, '2026-06-29'), /judge|verdict|verified/i);
|
||||
});
|
||||
|
||||
test('stampVerifiedMeta rejects an invalid today date', () => {
|
||||
const base = { title: 'T', status: 'GA', category: 'c',
|
||||
source: 'https://learn.microsoft.com/x', lastUpdated: '2026-06' };
|
||||
assert.throws(() => stampVerifiedMeta(base, { pass: true }, 'i går'), /date/i);
|
||||
});
|
||||
|
||||
test('stampVerifiedMeta → composeKbFile is a complete born-verified file (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 }, '2026-06-29');
|
||||
const file = composeKbFile(stamped, '## Introduksjon\n\nKort.\n');
|
||||
assert.equal(validateKbFile(file).valid, true);
|
||||
assert.equal(parseVerifiedHeader(file), '2026-06-29');
|
||||
assert.equal(parseVerifiedByHeader(file), 'judge-v2');
|
||||
});
|
||||
|
||||
// --- validateKbFile: catches files missing the mandatory contract -------------
|
||||
|
||||
test('validateKbFile accepts a header-conformant file', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue