syntaxai/tdd.md · main · src / b32_sama_v2_verify.ts
// b32 — logic: the SAMA v2 verifier. Implements the seven §4
// conformance checks (Sorted, Architecture, Modeled-tests,
// Modeled-boundary, Atomic, the Law §1.2, Consistency §3) as pure
// functions over an in-memory (profile, files) input. Never reads
// the filesystem — the loader (c14_sama_profile + d21 handler)
// populates the input map. The shared pure helpers and the parse-
// boundary detector live in a31_sama_v2 so this verifier and the
// §5 metrics emitter agree by construction.
import {
PARSE_BOUNDARY_PATTERNS,
collectRelativeImports,
declaredLayer,
findParseBoundaryCallSites,
isSamaFile,
isTestFile,
resolveImport,
stripStringsAndComments,
type SamaV2Check,
type SamaV2Input,
type SamaV2Report,
type SamaV2Violation,
} from "./a31_sama_v2.ts";
// — Check 1: Sorted -------------------------------------------------
//
// "Every file carries a profile-recognised prefix; lexicographic
// prefix order equals layer order."
const checkSorted = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
// Collect (prefix, layer) pairs from the profile.
const pairs: Array<{ prefix: string; layer: number }> = [];
for (const [k, spec] of Object.entries(input.profile.layers)) {
const layer = parseInt(k, 10);
for (const sub of spec.sublayers) pairs.push({ prefix: sub.prefix, layer });
}
// For any two prefixes with layer(A) < layer(B), A must lex-sort < B.
for (let i = 0; i < pairs.length; i++) {
for (let j = 0; j < pairs.length; j++) {
if (i === j) continue;
const a = pairs[i]!;
const b = pairs[j]!;
if (a.layer < b.layer && a.prefix > b.prefix) {
violations.push({
file: a.prefix,
detail: `prefix \`${a.prefix}\` (layer ${a.layer}) sorts after \`${b.prefix}\` (layer ${b.layer}) — lex order must equal layer order`,
});
}
}
}
// Also count source files whose prefix isn't recognised by any
// sublayer. They'd be flagged by Architecture too, but the Sorted
// rule needs each file to have a recognised prefix.
for (const path of input.files.keys()) {
if (!isSamaFile(path)) continue;
examined++;
if (declaredLayer(path, input.profile) === null) {
violations.push({ file: path, detail: "no profile-recognised prefix" });
}
}
return {
id: 1, name: "Sorted", property: "Sorted",
passed: violations.length === 0, examined, violations,
};
};
// — Check 2: Architecture -------------------------------------------
//
// "Every file maps to exactly one canonical layer; no file is
// unprefixed or maps to two layers."
const checkArchitecture = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
for (const path of input.files.keys()) {
if (!isSamaFile(path) && !isTestFile(path)) continue;
examined++;
const base = path.split("/").pop() ?? path;
// Find every profile prefix that matches this filename. Exactly
// one is required; zero = unprefixed (caught by Sorted too) but
// we surface it here as the canonical "unmapped" failure.
const matches: Array<{ layer: number; prefix: string }> = [];
for (const [k, spec] of Object.entries(input.profile.layers)) {
const layer = parseInt(k, 10);
for (const sub of spec.sublayers) {
if (base.startsWith(sub.prefix)) matches.push({ layer, prefix: sub.prefix });
}
}
if (matches.length === 0) {
violations.push({ file: path, detail: "unprefixed — does not match any profile prefix" });
} else if (matches.length > 1) {
// Two prefixes claim the same file: profile ambiguity.
const distinctLayers = new Set(matches.map((m) => m.layer));
if (distinctLayers.size > 1) {
violations.push({
file: path,
detail: `ambiguous — matches multiple layers: ${matches.map((m) => `${m.prefix}→L${m.layer}`).join(", ")}`,
});
}
}
}
return {
id: 2, name: "Architecture", property: "Architecture",
passed: violations.length === 0, examined, violations,
};
};
// — Check 3: Modeled (tests) ----------------------------------------
//
// "Every Layer 1 and Layer 2 behavior file has a sibling test file."
const checkModeledTests = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
for (const path of input.files.keys()) {
if (!isSamaFile(path)) continue;
const decl = declaredLayer(path, input.profile);
if (!decl) continue;
if (decl.layer !== 1 && decl.layer !== 2) continue;
examined++;
const siblingPath = path.replace(/\.ts$/, ".test.ts");
if (!input.files.has(siblingPath)) {
violations.push({
file: path,
detail: `no sibling test at \`${siblingPath}\` — Layer ${decl.layer} requires one`,
});
}
}
return {
id: 3, name: "Modeled (tests)", property: "Modeled (tests)",
passed: violations.length === 0, examined, violations,
};
};
// — Check 4: Modeled (boundary) -------------------------------------
//
// "External input is parsed only in Layer 2."
//
// §4.4 is profile-dependent (spec §6). Our profile defines boundary
// parsing as `JSON.parse(` of arbitrary input (not constant strings)
// or `new URL(` of arbitrary input — i.e. patterns that turn bytes
// into typed structures. Platform-provided parsers called *through*
// Layer 3 entry handlers (`req.json()`, `req.formData()`, route
// params) are treated as delegation to the platform's own Layer 2,
// not parsing performed in our Layer 3. The verifier reports any
// raw JSON.parse / new URL calls landing outside Layer 2.
//
// The call-site detector lives in a31_sama_v2 (findParseBoundary-
// CallSites). This check consumes its output and groups by
// (file, pattern) so the violation list stays at file-pattern
// granularity — the same shape pre-refactor. The §5 boundaryRatio
// metric consumes the same detector and counts individual call
// sites, but does not change this check's verdict.
const checkModeledBoundary = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
// Bucket call sites by file → set of patterns observed.
const patternsByFile = new Map<string, Set<string>>();
for (const site of findParseBoundaryCallSites(input.files)) {
let s = patternsByFile.get(site.file);
if (!s) { s = new Set(); patternsByFile.set(site.file, s); }
s.add(site.pattern);
}
// Iterate files in input order; emit one violation per (file,
// pattern) for files outside Layer 2, preserving PARSE_BOUNDARY_-
// PATTERNS order. This matches the pre-refactor verdict bit-for-bit.
for (const path of input.files.keys()) {
if (!isSamaFile(path)) continue;
const decl = declaredLayer(path, input.profile);
if (!decl) continue;
examined++;
if (decl.layer === 2) continue; // Layer 2 is the legitimate site.
const observed = patternsByFile.get(path);
if (!observed) continue;
for (const pat of PARSE_BOUNDARY_PATTERNS) {
if (observed.has(pat.name)) {
violations.push({
file: path,
detail: `boundary pattern \`${pat.name}\` found in Layer ${decl.layer} — parsing belongs in Layer 2`,
});
}
}
}
return {
id: 4, name: "Modeled (boundary)", property: "Modeled (boundary)",
passed: violations.length === 0, examined, violations,
note: "profile-dependent (spec §4.4): boundary = raw `JSON.parse` / `new URL` outside Layer 2. Platform parsers reached via `req.json()` etc. are treated as delegation to the platform's own Layer 2.",
};
};
// — Check 5: Atomic -------------------------------------------------
//
// "No file exceeds the line cap (default ~700; profile may lower,
// never raise). No barrel re-export files."
const ATOMIC_LINE_CAP = 700;
const checkAtomic = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
for (const [path, content] of input.files.entries()) {
if (!isSamaFile(path) && !isTestFile(path)) continue;
examined++;
const lines = content.split("\n").length;
if (lines > ATOMIC_LINE_CAP) {
violations.push({
file: path,
detail: `${lines} lines (over the ${ATOMIC_LINE_CAP}-line cap — split per UI/data domain)`,
});
}
// Barrel detection: a file whose entire body is re-exports.
// Heuristic: every non-blank, non-comment line is `export ... from`.
const stripped = stripStringsAndComments(content);
const codeLines = stripped.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
if (codeLines.length >= 2 && codeLines.every((l) => /^export\s+(\*|\{)/.test(l) && /\bfrom\b/.test(l))) {
violations.push({ file: path, detail: "barrel re-export file (all lines are `export … from`)" });
}
}
return {
id: 5, name: "Atomic", property: "Atomic",
passed: violations.length === 0, examined, violations,
};
};
// — Check 6: The Law (§1.2) -----------------------------------------
//
// "Imports always point to a strictly lower layer number — never
// upward, never sideways across a higher number, never cyclic."
//
// Build the import graph from relative-.ts imports, then for each
// edge A → B require: layer(B) < layer(A), OR same layer + B's
// sublayer index <= A's sublayer index. Also run a DFS cycle detector.
const checkLaw = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
// Build adjacency.
const adj = new Map<string, string[]>();
for (const [path, content] of input.files.entries()) {
if (!isSamaFile(path) && !isTestFile(path)) continue;
examined++;
const out: string[] = [];
for (const imp of collectRelativeImports(content)) {
const resolved = resolveImport(path, imp);
// Only follow edges into known SAMA files (in-tree, in src/).
if (input.files.has(resolved)) out.push(resolved);
}
adj.set(path, out);
}
// Edge-by-edge layer/sublayer check.
for (const [from, outs] of adj.entries()) {
const aDecl = declaredLayer(from, input.profile);
if (!aDecl) continue; // Unmapped — caught by Architecture.
for (const to of outs) {
const bDecl = declaredLayer(to, input.profile);
if (!bDecl) continue;
if (bDecl.layer < aDecl.layer) continue; // strictly lower — OK
if (bDecl.layer > aDecl.layer) {
violations.push({
file: from,
detail: `imports \`${to}\` — Layer ${aDecl.layer} → Layer ${bDecl.layer} (upward, breaks §1.2)`,
});
continue;
}
// Same layer: sublayer ordering. The import target must be in
// an earlier-or-equal sublayer slot (spec §2.2: later may import
// earlier).
if (bDecl.sublayer.index > aDecl.sublayer.index) {
violations.push({
file: from,
detail: `imports \`${to}\` — same layer ${aDecl.layer} but sublayer order is reversed (${aDecl.sublayer.name} sublayer-index ${aDecl.sublayer.index} → ${bDecl.sublayer.name} sublayer-index ${bDecl.sublayer.index})`,
});
}
}
}
// DFS cycle detection on the same graph.
const WHITE = 0, GRAY = 1, BLACK = 2;
const color = new Map<string, number>();
for (const k of adj.keys()) color.set(k, WHITE);
const cycles: string[][] = [];
const stack: string[] = [];
const dfs = (node: string): boolean => {
color.set(node, GRAY);
stack.push(node);
for (const next of adj.get(node) ?? []) {
const c = color.get(next) ?? WHITE;
if (c === GRAY) {
const idx = stack.indexOf(next);
if (idx !== -1) cycles.push([...stack.slice(idx), next]);
return true;
}
if (c === WHITE && dfs(next)) {
// bubble up
}
}
stack.pop();
color.set(node, BLACK);
return false;
};
for (const k of adj.keys()) if (color.get(k) === WHITE) dfs(k);
for (const cyc of cycles) {
violations.push({
file: cyc[0] ?? "(unknown)",
detail: `import cycle: ${cyc.join(" → ")}`,
});
}
return {
id: 6, name: "Law (§1.2)", property: "Law",
passed: violations.length === 0, examined, violations,
};
};
// — Check 7: Consistency (§3) ---------------------------------------
//
// "Verifier FAILS if a file imports from a layer that its declared
// layer is not permitted to import." This is the same set of edges
// the Law check examines, framed from the file's own perspective:
// does the prefix lie about what the file actually does?
//
// We emit a separate verdict so the report can show both framings.
// In a profile where no §1.2 violation exists, §3 also passes by
// construction — both are derived from the same edge set.
const checkConsistency = (input: SamaV2Input): SamaV2Check => {
const violations: SamaV2Violation[] = [];
let examined = 0;
for (const [path, content] of input.files.entries()) {
if (!isSamaFile(path)) continue;
const aDecl = declaredLayer(path, input.profile);
if (!aDecl) continue;
examined++;
let ceiling = -1;
let ceilingFile: string | null = null;
for (const imp of collectRelativeImports(content)) {
const resolved = resolveImport(path, imp);
const bDecl = declaredLayer(resolved, input.profile);
if (!bDecl) continue;
if (bDecl.layer > ceiling) { ceiling = bDecl.layer; ceilingFile = resolved; }
}
// Consistency fails if any import goes to a strictly higher
// layer than the file's declared layer. Same-layer with bad
// sublayer order is the Law's concern, not Consistency's.
if (ceiling > aDecl.layer) {
violations.push({
file: path,
detail: `declared Layer ${aDecl.layer} (prefix \`${aDecl.sublayer.prefix}\`) but imports reach Layer ${ceiling} via \`${ceilingFile}\` — the prefix claims something the imports contradict`,
});
}
}
return {
id: 7, name: "Consistency (§3)", property: "Consistency",
passed: violations.length === 0, examined, violations,
};
};
// — Orchestrator ----------------------------------------------------
export const verifySamaV2 = (input: SamaV2Input): SamaV2Report => {
const checks: SamaV2Check[] = [
checkSorted(input),
checkArchitecture(input),
checkModeledTests(input),
checkModeledBoundary(input),
checkAtomic(input),
checkLaw(input),
checkConsistency(input),
];
// Architecture's examined count is the canonical total — it counts
// every file the profile assigns to a layer (or fails to).
const examined = checks.find((c) => c.id === 2)?.examined ?? 0;
return {
profile: input.profile.profile,
examined,
checks,
overallPassed: checks.every((c) => c.passed),
};
};