auditor: kb_index aggregator + nine-consecutive empirical test
lakehouse/auditor 1 blocking issue: cloud: claim not backed — "the proven escalation ladder with learning context, collects"

Phase 1 — definition-layer over append-only JSONL scratchpads.

auditor/kb_index.ts is the single shared aggregator:

  aggregate<T>(jsonlPath, { keyFn, scopeFn, checkFn, tailLimit })
      → Map<signature, {count, distinct_scopes, confidence,
                        first_seen, last_seen, representative_summary, ...}>

  ratingSeverity(agg) — confidence × count severity policy shared
    across all KB readers. Kills the "same unfixed PR inflates its
    own recurrence score" failure mode by design: confidence =
    distinct_scopes/count, so same-scope noise stays below the 0.3
    escalation threshold no matter how many times it repeats.

checkAuditLessons now routes through aggregate + ratingSeverity.
Net effect: the recurrence detector's bespoke Map/Set bookkeeping is
gone; same behavior, shared discipline, reusable by scrum/observer.

Also: symbolsExistInRepo now skips files >500KB so the audit can't
get stuck slurping a fixture.

Phase 2 — nine-consecutive audit runner.

tests/real-world/nine_consecutive_audits.ts pushes 9 empty commits,
waits for each verdict, captures the audit_lessons aggregate state
after each run, reports:

  - sig_count trajectory (should stabilize, not grow linearly)
  - max_count trajectory (same-signature repeat rate)
  - max_confidence trajectory (must stay LOW on same-PR noise)
  - verdict_stable across runs (must NOT oscillate)

This is the empirical proof that the KB compounds favorably:
noise doesn't escalate itself, and signal stays distinguishable.

Unit-tested both failure modes: same-PR × 9 repeats = conf=0.11
(info); cross-PR × 5 distinct = conf=1.00 (block). The rating
function correctly discriminates.
This commit is contained in:
profit
2026-04-22 21:49:46 -05:00
parent f4be27a879
commit 9d12a814e3
4 changed files with 369 additions and 44 deletions
+5 -1
View File
@@ -260,10 +260,13 @@ function extractSymbols(text: string): string[] {
// Scan the repo for at least one definition of each symbol. Uses Bun's
// Glob to walk TS/Rust/Python/JS sources; ignores node_modules, data/,
// and target/.
// and target/. Skips files > 500KB — those are fixtures/snapshots that
// won't contain a definition line and slurping them slows the audit.
async function symbolsExistInRepo(symbols: string[]): Promise<string[]> {
const patterns = ["**/*.ts", "**/*.tsx", "**/*.rs", "**/*.py", "**/*.js"];
const skip = (p: string) => p.includes("/node_modules/") || p.startsWith("data/") || p.includes("/target/") || p.startsWith("dist/");
const MAX_FILE_BYTES = 500_000;
const { stat } = await import("node:fs/promises");
const resolved = new Set<string>();
const toFind = new Set(symbols);
for (const pat of patterns) {
@@ -271,6 +274,7 @@ async function symbolsExistInRepo(symbols: string[]): Promise<string[]> {
const glob = new Glob(pat);
for await (const f of glob.scan({ cwd: REPO_ROOT, onlyFiles: true })) {
if (skip(f)) continue;
try { const s = await stat(`${REPO_ROOT}/${f}`); if (s.size > MAX_FILE_BYTES) continue; } catch { continue; }
let content: string;
try { content = await readFile(`${REPO_ROOT}/${f}`, "utf8"); } catch { continue; }
for (const sym of Array.from(toFind)) {
+22 -43
View File
@@ -18,6 +18,7 @@
import { readFile, readdir, stat } from "node:fs/promises";
import { join } from "node:path";
import type { Claim, Finding } from "../types.ts";
import { aggregate, ratingSeverity, formatAgg } from "../kb_index.ts";
const KB_DIR = "/home/profit/lakehouse/data/_kb";
const OBSERVER_OPS = "/home/profit/lakehouse/data/_observer/ops.jsonl";
@@ -26,11 +27,6 @@ const SCRUM_REVIEWS_JSONL = "/home/profit/lakehouse/data/_kb/scrum_reviews.jsonl
const AUDIT_LESSONS_JSONL = "/home/profit/lakehouse/data/_kb/audit_lessons.jsonl";
const TAIL_LINES = 500;
const MAX_BOT_CYCLE_FILES = 30;
// Recurrence threshold — at this count a warn becomes a block.
// The rationale: three independent audits all flagging the SAME
// pattern signature is strong evidence the pattern is a real
// problem, not noise. One occurrence = info, two = warn, three+ = block.
const RECURRENCE_BLOCK_THRESHOLD = 3;
export async function runKbCheck(claims: Claim[], prFiles: string[] = []): Promise<Finding[]> {
const findings: Finding[] = [];
@@ -212,52 +208,35 @@ function observerBySource(ops: any[]): string {
}
// Audit-lessons — reads data/_kb/audit_lessons.jsonl (populated by
// every audit's appendAuditLessons). Groups rows by `signature` (the
// check-normalized dedup key) and emits a finding per signature that
// has 2+ occurrences. Severity ramps with count: 2 = info, 3-4 = warn,
// 5+ = block. This is how the auditor accumulates institutional
// memory: without this check, a recurring flaw (placeholder code
// class X, unbacked claim pattern Y) looks new every audit.
// every audit's appendAuditLessons). Uses the shared kb_index
// aggregator: groups by `signature`, distinct-scopes keyed by PR
// number, severity from ratingSeverity(agg) which applies the
// confidence × count rating (see kb_index.ts). This is the same
// aggregation any other KB reader uses — shared discipline, not
// per-check custom logic.
async function checkAuditLessons(): Promise<Finding[]> {
const rows = await tailJsonl<any>(AUDIT_LESSONS_JSONL, TAIL_LINES * 4);
if (rows.length === 0) return [];
type Agg = { count: number; last_summary: string; last_pr: number; last_sha: string; checks: Set<string>; prs: Set<number> };
const bySig = new Map<string, Agg>();
for (const r of rows) {
const sig = String(r.signature ?? "");
if (!sig) continue;
const a = bySig.get(sig) ?? {
count: 0, last_summary: "", last_pr: 0, last_sha: "",
checks: new Set<string>(), prs: new Set<number>(),
};
a.count += 1;
a.last_summary = String(r.summary ?? a.last_summary);
a.last_pr = Number(r.pr_number ?? a.last_pr);
a.last_sha = String(r.head_sha ?? a.last_sha);
if (r.check) a.checks.add(String(r.check));
if (r.pr_number) a.prs.add(Number(r.pr_number));
bySig.set(sig, a);
}
const bySig = await aggregate<any>(AUDIT_LESSONS_JSONL, {
keyFn: (r) => r?.signature,
scopeFn: (r) => (r?.pr_number !== undefined ? `pr-${r.pr_number}` : undefined),
checkFn: (r) => r?.check,
tailLimit: TAIL_LINES * 4,
});
if (bySig.size === 0) return [];
const findings: Finding[] = [];
// Emit only signatures with 2+ prior PRs (not just 2+ rows — a
// single unresolved PR being re-audited on every push would
// otherwise self-inflate). Distinct-PRs count is the real signal.
for (const [sig, a] of bySig) {
if (a.prs.size < 2) continue;
const sev: "block" | "warn" | "info" =
a.prs.size >= RECURRENCE_BLOCK_THRESHOLD + 2 ? "block" :
a.prs.size >= RECURRENCE_BLOCK_THRESHOLD ? "warn" : "info";
for (const [sig, agg] of bySig) {
// Silent on first-ever occurrence — not yet signal.
if (agg.count < 2) continue;
const sev = ratingSeverity(agg);
findings.push({
check: "kb_query",
severity: sev,
summary: `recurring audit pattern (${a.prs.size} distinct PRs, ${a.count} total flaggings): ${a.last_summary.slice(0, 180)}`,
summary: `recurring audit pattern (${agg.distinct_scopes} distinct PRs, ${agg.count} flaggings, conf=${agg.confidence.toFixed(2)}): ${agg.representative_summary.slice(0, 160)}`,
evidence: [
`signature=${sig}`,
`checks: ${Array.from(a.checks).join(",")}`,
`PRs: ${Array.from(a.prs).sort((x,y)=>x-y).join(",")}`,
`most recent: PR #${a.last_pr} @ ${a.last_sha.slice(0, 12)}`,
`checks: ${agg.checks.join(",")}`,
`scopes: ${agg.scopes.slice(-6).join(",")}`,
formatAgg(agg),
],
});
}