feat(ms-ai-architect): R7.1 — apply-verified-stamp driver (surgical insertVerifiedFields over ledger-pass-records; invariant-assertert, atomisk, idempotent; ustempelbar → samlet feil, TDD 3 tester) [skip-docs]
This commit is contained in:
parent
312c3c0369
commit
83ef8eaf7a
2 changed files with 249 additions and 0 deletions
121
scripts/kb-update/apply-verified-stamp.mjs
Normal file
121
scripts/kb-update/apply-verified-stamp.mjs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env node
|
||||
// apply-verified-stamp.mjs — R7–R10 surgical born-verified stamp driver.
|
||||
//
|
||||
// Applies the judge-pass ledger's `pass` verdicts (scripts/kb-eval/data/judge-pass-manifest.json,
|
||||
// R6 Step 4) to disk: for every record with per_file_verdict='pass', insert/update ONLY the
|
||||
// **Verified:** and **Verified by:** header lines via transform.insertVerifiedFields — the body
|
||||
// below the header is byte-identical (NFR non-destruktivt; composeKbFile would rebuild the file
|
||||
// and diff the body, which is why this driver exists — R6 plan Step 2). `flagged` records are
|
||||
// never touched. An unstampable file (stamp would land past the 500-byte window —
|
||||
// insertVerifiedFields throws) is collected as an error and reported; R7 flips that record to
|
||||
// flagged «header-slanking kreves», never a forced stamp. Other files still stamp (exit 1).
|
||||
//
|
||||
// INVARIANT (asserted per file BEFORE any write; breach aborts that file, never writes):
|
||||
// - body (from first `## `/`---`) byte-identical
|
||||
// - header minus Verified/Verified by-lines byte-identical (only the stamp lines differ)
|
||||
// - parseVerifiedHeader/parseVerifiedByHeader read back exactly the stamped values
|
||||
// Idempotent: a re-run finds the stamp in place and is a no-op. Crash-safe: atomicWriteSync
|
||||
// (tmp+rename) — a reader sees the old file or the new one, never a partial.
|
||||
//
|
||||
// Usage: node scripts/kb-update/apply-verified-stamp.mjs [--manifest <path>] [--root <dir>] [--write]
|
||||
// (default: dry-run — prints the plan, writes nothing)
|
||||
// Exit codes: 0 ok · 1 one or more files failed (unstampable/missing/invariant) · 2 usage
|
||||
|
||||
import { readFileSync, existsSync, realpathSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { insertVerifiedFields } from './lib/transform.mjs';
|
||||
import { parseVerifiedHeader, parseVerifiedByHeader } from './lib/kb-headers.mjs';
|
||||
import { atomicWriteSync } from './lib/atomic-write.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
||||
const DEFAULT_MANIFEST = join(__dirname, '..', 'kb-eval', 'data', 'judge-pass-manifest.json');
|
||||
|
||||
/**
|
||||
* Pure stamp plan for one file: returns { content, changed }. `changed:false` means the exact
|
||||
* stamp is already in place (idempotent re-run). Throws when the stamp cannot land inside the
|
||||
* 500-byte header window (caller records the failure — the R7 «header-slanking kreves» path).
|
||||
*/
|
||||
export function planVerifiedStamp(content, { verified, verified_by }) {
|
||||
const stamped = insertVerifiedFields(content, { verified, verified_by });
|
||||
return { content: stamped, changed: stamped !== String(content) };
|
||||
}
|
||||
|
||||
// Header/body split mirroring strip-stale-verified-pipe.mjs / transform.mjs headerEndIndex.
|
||||
function splitHeaderBody(content) {
|
||||
const lines = String(content).split('\n');
|
||||
let i = 0;
|
||||
for (; i < lines.length; i++) {
|
||||
if (/^##\s/.test(lines[i]) || /^---\s*$/.test(lines[i])) break;
|
||||
}
|
||||
return { header: lines.slice(0, i).join('\n'), body: lines.slice(i).join('\n') };
|
||||
}
|
||||
|
||||
const RE_STAMP_LINE = /^\*\*Verified(?: by)?:\*\*/i;
|
||||
|
||||
// The hard per-file invariant: only the stamp lines may differ; the body never.
|
||||
function assertInvariant(rel, oldC, newC, { verified, verified_by }) {
|
||||
const o = splitHeaderBody(oldC);
|
||||
const n = splitHeaderBody(newC);
|
||||
if (o.body !== n.body) throw new Error(`${rel}: body changed — refusing to write`);
|
||||
const strip = (h) => h.split('\n').filter((l) => !RE_STAMP_LINE.test(l)).join('\n');
|
||||
if (strip(o.header) !== strip(n.header)) {
|
||||
throw new Error(`${rel}: header lines other than Verified/Verified by changed — refusing to write`);
|
||||
}
|
||||
if (parseVerifiedHeader(newC) !== verified || parseVerifiedByHeader(newC) !== verified_by) {
|
||||
throw new Error(`${rel}: stamped values do not read back — refusing to write`);
|
||||
}
|
||||
}
|
||||
|
||||
function usageExit(msg) {
|
||||
process.stderr.write(`${msg}\nusage: apply-verified-stamp.mjs [--manifest <path>] [--root <dir>] [--write]\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
function main(argv) {
|
||||
const args = argv.slice(2);
|
||||
let manifestPath = DEFAULT_MANIFEST;
|
||||
let root = PLUGIN_ROOT;
|
||||
let write = false;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--manifest') { if (!args[i + 1]) usageExit('--manifest requires a path'); manifestPath = args[++i]; }
|
||||
else if (args[i] === '--root') { if (!args[i + 1]) usageExit('--root requires a dir'); root = args[++i]; }
|
||||
else if (args[i] === '--write') write = true;
|
||||
else usageExit(`unknown flag: ${args[i]}`);
|
||||
}
|
||||
if (!existsSync(manifestPath)) usageExit(`manifest not found: ${manifestPath}`);
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
const passRecs = (manifest.files ?? []).filter((r) => r?.per_file_verdict === 'pass');
|
||||
|
||||
let stamped = 0, noop = 0;
|
||||
const failed = [];
|
||||
for (const rec of passRecs) {
|
||||
const abs = join(root, rec.file);
|
||||
try {
|
||||
if (!rec.verified || !rec.verified_by) throw new Error('pass record missing verified/verified_by');
|
||||
if (!existsSync(abs)) throw new Error('file not found');
|
||||
const oldC = readFileSync(abs, 'utf8');
|
||||
const { content: newC, changed } = planVerifiedStamp(oldC, { verified: rec.verified, verified_by: rec.verified_by });
|
||||
if (!changed) { noop++; continue; }
|
||||
assertInvariant(rec.file, oldC, newC, { verified: rec.verified, verified_by: rec.verified_by });
|
||||
if (write) atomicWriteSync(abs, newC);
|
||||
stamped++;
|
||||
console.log(`${write ? 'stamped' : 'would stamp'} ${rec.file}`);
|
||||
} catch (e) {
|
||||
failed.push([rec.file, e.message]);
|
||||
console.error(`FAILED ${rec.file} — ${e.message}`);
|
||||
}
|
||||
}
|
||||
console.log(`\n${write ? '' : '(dry-run) '}pass records: ${passRecs.length} · stamped: ${write ? stamped : 0}${write ? '' : ` (would stamp: ${stamped})`} · already stamped: ${noop} · failed: ${failed.length}`);
|
||||
if (failed.length > 0) process.exit(1);
|
||||
}
|
||||
|
||||
const isMain = (() => {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
if (isMain) main(process.argv);
|
||||
128
tests/kb-update/test-apply-verified-stamp.test.mjs
Normal file
128
tests/kb-update/test-apply-verified-stamp.test.mjs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// test-apply-verified-stamp.test.mjs — R7.1: the surgical born-verified stamp driver.
|
||||
// Applies the judge-pass ledger's `pass` verdicts to disk via insertVerifiedFields:
|
||||
// ONLY the **Verified:** / **Verified by:** header lines are added/updated inside the
|
||||
// 500-byte window; the body below the header is byte-identical; writes are atomic and
|
||||
// idempotent; a `flagged` record is never touched; an unstampable file is collected as
|
||||
// an error (R7 flips it to flagged «header-slanking kreves») without aborting the rest.
|
||||
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { planVerifiedStamp } from '../../scripts/kb-update/apply-verified-stamp.mjs';
|
||||
import { parseVerifiedHeader, parseVerifiedByHeader } from '../../scripts/kb-update/lib/kb-headers.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const CLI = join(__dirname, '..', '..', 'scripts', 'kb-update', 'apply-verified-stamp.mjs');
|
||||
|
||||
const FILE_A = `# Testfil A
|
||||
|
||||
**Last updated:** 2026-06
|
||||
**Status:** GA
|
||||
**Category:** Test
|
||||
**Type:** reference
|
||||
**Source:** https://learn.microsoft.com/test-a
|
||||
|
||||
---
|
||||
|
||||
## Innhold
|
||||
|
||||
Brødtekst som aldri skal endres.
|
||||
`;
|
||||
|
||||
// Header so fat the stamp insertion point lands at ~byte 488 — the two stamp lines then land
|
||||
// past the 500-byte window and insertVerifiedFields refuses (header-slanking required).
|
||||
const FILE_FAT = `# Testfil ${'x'.repeat(49)}
|
||||
|
||||
**Last updated:** 2026-06 ${'y'.repeat(400)}
|
||||
|
||||
---
|
||||
|
||||
## Body
|
||||
`;
|
||||
|
||||
function withTmp(fn) {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'avs-'));
|
||||
try { return fn(dir); } finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
}
|
||||
|
||||
function bodyOf(content) {
|
||||
const lines = content.split('\n');
|
||||
const i = lines.findIndex((l) => /^##\s/.test(l) || /^---\s*$/.test(l));
|
||||
return lines.slice(i).join('\n');
|
||||
}
|
||||
|
||||
test('planVerifiedStamp stamps both fields in-window, body byte-identical, idempotent re-plan', () => {
|
||||
const { content: stamped, changed } = planVerifiedStamp(FILE_A, { verified: '2026-07-18', verified_by: 'judge-v3.1' });
|
||||
assert.equal(changed, true);
|
||||
assert.equal(parseVerifiedHeader(stamped), '2026-07-18');
|
||||
assert.equal(parseVerifiedByHeader(stamped), 'judge-v3.1');
|
||||
assert.equal(bodyOf(stamped), bodyOf(FILE_A));
|
||||
const again = planVerifiedStamp(stamped, { verified: '2026-07-18', verified_by: 'judge-v3.1' });
|
||||
assert.equal(again.changed, false);
|
||||
assert.equal(again.content, stamped);
|
||||
});
|
||||
|
||||
function ledgerWith(files) {
|
||||
return {
|
||||
_meta: { purpose: 'test', derived_from: 'test', cadence: 'test', batch: 1, count: files.length, generated: '2026-07-18' },
|
||||
files,
|
||||
};
|
||||
}
|
||||
|
||||
function passRec(file) {
|
||||
return { file, batch: 1, judged_at: '2026-07-18', per_file_verdict: 'pass', claim_count: 1, verified: '2026-07-18', verified_by: 'judge-v3.1', flags: [] };
|
||||
}
|
||||
|
||||
test('CLI --write stamps pass records, skips flagged, is idempotent on re-run', () => {
|
||||
withTmp((dir) => {
|
||||
mkdirSync(join(dir, 'skills'), { recursive: true });
|
||||
writeFileSync(join(dir, 'skills', 'a.md'), FILE_A);
|
||||
writeFileSync(join(dir, 'skills', 'b.md'), FILE_A);
|
||||
const ledger = ledgerWith([
|
||||
passRec('skills/a.md'),
|
||||
{ file: 'skills/b.md', batch: 1, judged_at: '2026-07-18', per_file_verdict: 'flagged', claim_count: 1, verified: null, verified_by: null,
|
||||
flags: [{ id: 'b.md#1', judge_verdict: 'not_grounded', rule: 'R1', evidence_url: '', evidence_quote: '', reason: 'x', file: 'skills/b.md', line: 3, claim: 'c', disposition: 'outdated' }] },
|
||||
]);
|
||||
const mPath = join(dir, 'ledger.json');
|
||||
writeFileSync(mPath, JSON.stringify(ledger));
|
||||
|
||||
// Dry-run: nothing written.
|
||||
execFileSync('node', [CLI, '--manifest', mPath, '--root', dir], { encoding: 'utf8' });
|
||||
assert.equal(readFileSync(join(dir, 'skills', 'a.md'), 'utf8'), FILE_A);
|
||||
|
||||
const out1 = execFileSync('node', [CLI, '--manifest', mPath, '--root', dir, '--write'], { encoding: 'utf8' });
|
||||
assert.match(out1, /stamped: 1/);
|
||||
const stamped = readFileSync(join(dir, 'skills', 'a.md'), 'utf8');
|
||||
assert.equal(parseVerifiedHeader(stamped), '2026-07-18');
|
||||
assert.equal(bodyOf(stamped), bodyOf(FILE_A));
|
||||
// Flagged never touched.
|
||||
assert.equal(readFileSync(join(dir, 'skills', 'b.md'), 'utf8'), FILE_A);
|
||||
// Idempotent re-run: no change.
|
||||
const out2 = execFileSync('node', [CLI, '--manifest', mPath, '--root', dir, '--write'], { encoding: 'utf8' });
|
||||
assert.match(out2, /stamped: 0/);
|
||||
assert.equal(readFileSync(join(dir, 'skills', 'a.md'), 'utf8'), stamped);
|
||||
});
|
||||
});
|
||||
|
||||
test('CLI collects an unstampable pass record as error (exit 1) but still stamps the rest', () => {
|
||||
withTmp((dir) => {
|
||||
mkdirSync(join(dir, 'skills'), { recursive: true });
|
||||
writeFileSync(join(dir, 'skills', 'ok.md'), FILE_A);
|
||||
writeFileSync(join(dir, 'skills', 'fat.md'), FILE_FAT);
|
||||
const mPath = join(dir, 'ledger.json');
|
||||
writeFileSync(mPath, JSON.stringify(ledgerWith([passRec('skills/ok.md'), passRec('skills/fat.md')])));
|
||||
let failed = null;
|
||||
try {
|
||||
execFileSync('node', [CLI, '--manifest', mPath, '--root', dir, '--write'], { encoding: 'utf8', stdio: 'pipe' });
|
||||
} catch (e) { failed = e; }
|
||||
assert.ok(failed, 'expected exit 1');
|
||||
assert.equal(failed.status, 1);
|
||||
assert.match(String(failed.stdout) + String(failed.stderr), /fat\.md/);
|
||||
// The stampable sibling was still stamped.
|
||||
assert.equal(parseVerifiedHeader(readFileSync(join(dir, 'skills', 'ok.md'), 'utf8')), '2026-07-18');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue