muse-commit-pilot-evidence.mjs
141 lines 5.1 KB
Raw
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 1 day ago
1 /**
2 * Muse commit pilot evidence validators (Phase 7A, Step 7A-14).
3 *
4 * Pure functions for seven-tier tests to assert the pilot artifacts meet the
5 * acceptance bar without re-running Muse or the full shell driver.
6 */
7
8 import { readFileSync, existsSync } from 'node:fs';
9 import { join } from 'node:path';
10
11 /** Required paths under docs/evidence/7A-14 relative to the Knowtation repo root. */
12 export const MUSE_COMMIT_PILOT_EVIDENCE_REL = 'docs/evidence/7A-14';
13
14 /** Pilot workspace filenames (projections committed via Muse). */
15 export const PILOT_WORKSPACE_FILES = [
16 'overseer.AGENTS.md',
17 'overseer.cursor.mdc',
18 ] ;
19
20 /** Artifact filenames captured by run-pilot.sh. */
21 export const PILOT_ARTIFACT_FILES = [
22 'transcript.txt',
23 'muse-sha-before.txt',
24 'muse-sha-after.txt',
25 'overseer.AGENTS.v0.1.0.md',
26 'overseer.AGENTS.v0.2.0.md',
27 'overseer.v0.1.0.mdc',
28 'overseer.v0.2.0.mdc',
29 'overseer.runbook.v1-to-v2.diff',
30 'overseer.cursor.v1-to-v2.diff',
31 'overseer.AGENTS.handedited.md',
32 ];
33
34 const GENERATED_MARKER_RE =
35 /^<!-- GENERATED FROM CANONICAL FLOW flow_overseer_handover@(\d+\.\d+\.\d+) \(generator v1\)/;
36
37 const SECRET_SCAN_RE = /\b(token|oauth|refresh_token|api_key|secret|password|bearer)\b/i;
38
39 /**
40 * @param {string} repoRoot — absolute Knowtation repo root
41 * @returns {{ ok: true } | { ok: false; missing: string[] }}
42 */
43 export function assertPilotEvidencePathsExist(repoRoot) {
44 const missing = [];
45 const base = join(repoRoot, MUSE_COMMIT_PILOT_EVIDENCE_REL);
46 for (const name of ['README.md', 'run-pilot.sh', ...PILOT_WORKSPACE_FILES.map((f) => `pilot-workspace/${f}`)]) {
47 if (!existsSync(join(base, name))) {
48 missing.push(join(MUSE_COMMIT_PILOT_EVIDENCE_REL, name));
49 }
50 }
51 for (const name of PILOT_ARTIFACT_FILES) {
52 if (!existsSync(join(base, 'artifacts', name))) {
53 missing.push(join(MUSE_COMMIT_PILOT_EVIDENCE_REL, 'artifacts', name));
54 }
55 }
56 return missing.length === 0 ? { ok: true } : { ok: false, missing };
57 }
58
59 /**
60 * Parse the generated marker version from a projection file.
61 *
62 * @param {string} content
63 * @returns {string | null}
64 */
65 export function parseGeneratedMarkerVersion(content) {
66 const firstLine = content.split('\n').find((line) => line.includes('GENERATED FROM CANONICAL FLOW'));
67 if (!firstLine) return null;
68 const match = firstLine.match(GENERATED_MARKER_RE);
69 return match?.[1] ?? null;
70 }
71
72 /**
73 * Assert anti-drift diff carries only marker version + one canonical content line change.
74 *
75 * @param {string} diffText — unified diff content
76 * @returns {{ ok: true } | { ok: false; reason: string }}
77 */
78 export function assertCleanAntiDriftDiff(diffText) {
79 const changedLines = diffText
80 .split('\n')
81 .filter((line) => (line.startsWith('-') && !line.startsWith('---')) || (line.startsWith('+') && !line.startsWith('+++')));
82 if (changedLines.length !== 4) {
83 return { ok: false, reason: `expected 4 changed lines (2 pairs), got ${changedLines.length}` };
84 }
85 const minus = changedLines.filter((l) => l.startsWith('-'));
86 const plus = changedLines.filter((l) => l.startsWith('+'));
87 if (minus.length !== 2 || plus.length !== 2) {
88 return { ok: false, reason: 'expected exactly 2 removals and 2 additions' };
89 }
90 const markerMinus = minus.find((l) => l.includes('GENERATED FROM CANONICAL FLOW'));
91 const markerPlus = plus.find((l) => l.includes('GENERATED FROM CANONICAL FLOW'));
92 if (!markerMinus || !markerPlus) {
93 return { ok: false, reason: 'marker line must change (version bump only)' };
94 }
95 if (!markerMinus.includes('@0.1.0') || !markerPlus.includes('@0.2.0')) {
96 return { ok: false, reason: 'marker must bump 0.1.0 → 0.2.0' };
97 }
98 return { ok: true };
99 }
100
101 /**
102 * Scan rendered bytes for accidental secret leakage (contract §10 security bar).
103 *
104 * @param {string} content
105 * @returns {{ ok: true } | { ok: false; matches: string[] }}
106 */
107 export function assertNoSecretLeakageInProjection(content) {
108 const lines = content.split('\n');
109 const bad = [];
110 for (const line of lines) {
111 if (/no secrets/i.test(line)) continue;
112 if (SECRET_SCAN_RE.test(line)) {
113 bad.push(line.trim().slice(0, 120));
114 }
115 }
116 return bad.length === 0 ? { ok: true } : { ok: false, matches: bad };
117 }
118
119 /**
120 * Load pilot workspace projection and validate marker + version.
121 *
122 * @param {string} repoRoot
123 * @param {string} filename
124 * @param {string} expectedVersion
125 */
126 export function loadAndValidatePilotProjection(repoRoot, filename, expectedVersion) {
127 const path = join(repoRoot, MUSE_COMMIT_PILOT_EVIDENCE_REL, 'pilot-workspace', filename);
128 const content = readFileSync(path, 'utf8');
129 const version = parseGeneratedMarkerVersion(content);
130 if (version !== expectedVersion) {
131 throw new Error(`${filename}: expected marker @${expectedVersion}, got @${version}`);
132 }
133 const secretCheck = assertNoSecretLeakageInProjection(content);
134 if (!secretCheck.ok) {
135 throw new Error(`${filename}: secret scan failed: ${secretCheck.matches.join('; ')}`);
136 }
137 if (!content.includes('GENERATED FROM CANONICAL FLOW flow_overseer_handover@')) {
138 throw new Error(`${filename}: missing generated marker`);
139 }
140 return content;
141 }
File History 1 commit
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 1 day ago