Files
IUMBTEMS/packages/core/src/codeaudit/store.ts
T
BillyOutlastandClaude Opus 5.5 020ba87f08 Release 1.6.0: 1.0 cutover, phases 0–5 and the review remediation (#151)
- 1.0 cutover: the rewrite monorepo lands on main. 1.1.5 through 1.5.0
  never published on their own and ship here as 1.6.0, in lockstep for
  es-core, es-cli and epistemic-swarm (ADR 0001).
- Phase 0 (#140): fail-closed seat models, one CLI grammar for the
  human-only rule, sealed source-cache readers, pinned MCP identity, a
  registry-generated permission probe suite, one package list.
- Phase 1 (#141): research backends with failover, the Scraper-Swarm
  gateway, callable brainstorm/harvest, research-only runs, the
  deep-research coordinator, Markdown/HTML dossiers, domain-pack tiers.
- Phase 2 (#142): ADR 0002 and the shared approveStage service, masked
  TUI approvals, the headless runner (JSONL events, metrics, cancel).
- Phase 3 (#144): the human-only es-fleet daemon, DAG scheduler, worktree
  coordinator with a gated integration branch, telemetry bus and watch.
- Phase 4 (#146): the loopback web control plane (SolidJS, ADR 0003):
  dashboard, browser approvals, config preview, evidence explorer.
- Phase 5 (#149): telemetry harvest, distillation into proposals, the
  self-dogfood preset.
- Cutover review remediation (#152): CLI approvals bound to the preview
  with attempt limiting (#143), human-only `factory run` and fleet token,
  DNS-resolving SSRF guard, value-level secret scrubbing, fleet gc and
  allocation races, CodeQL #84, boundary-gate gaps, one REPORT.md writer
  per run mode.

Closes #96.
Closes #97.
Closes #98.
Closes #99.
Closes #100.
Closes #101.
Closes #103.
Closes #106.
Closes #107.
Closes #108.
Closes #109.
Closes #110.
Closes #111.
Closes #112.
Closes #113.
Closes #114.
Closes #117.
Closes #118.
Closes #119.
Closes #120.
Closes #122.
Closes #123.
Closes #124.
Closes #125.
Closes #126.
Closes #127.
Closes #129.
Closes #130.
Closes #131.
Closes #132.
Closes #133.
Closes #135.
Closes #136.
Closes #147.
Closes #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 21:33:44 -04:00

170 lines
7.6 KiB
TypeScript

// Code-audit artifacts under .factory/audits/<id>/ (control files, written
// only here): records.json (every seat's verdict and findings, per round),
// dossier.json (the findings as witnessed location claims) and REPORT.md.
import { normalizeClaim } from "../claims/claim.ts"
import { buildDossier, writeDossier } from "../claims/dossier.ts"
import { explainDossierRank, rankDossiers } from "../claims/rank.ts"
import { type Audit, describeTarget } from "../factory/state.ts"
import { stateDir as defaultStateDir, factoryLayout } from "../layout.ts"
import { researchCache } from "../research/ops.ts"
import type { Dossier } from "../schema/claims.ts"
import { type AuditFinding, type AuditRecord, AuditRecordSchema } from "../schema/codeaudit.ts"
import { atomicWrite, readJson, withLock, writeJson } from "../util/fs.ts"
import { isDefect, pointer } from "./findings.ts"
export async function readAuditRecords(root: string, auditId: string): Promise<AuditRecord[]> {
const file = factoryLayout(root).auditRecords(auditId)
const raw = await readJson<unknown[]>(file)
if (raw === undefined) return []
if (!Array.isArray(raw)) throw new Error(`${file} is corrupt (expected an array)`)
return raw.map((entry) => AuditRecordSchema.parse(entry))
}
export async function appendAuditRecord(root: string, record: AuditRecord): Promise<void> {
const file = factoryLayout(root).auditRecords(record.audit)
await withLock(file, async () => {
const records = await readAuditRecords(root, record.audit)
records.push(AuditRecordSchema.parse(record))
await writeJson(file, records)
})
}
/** The latest record per seat in a round. */
export function roundRecords(records: readonly AuditRecord[], round: number): AuditRecord[] {
const latest = new Map<string, AuditRecord>()
for (const record of records) if (record.round === round) latest.set(record.seat, record)
return [...latest.values()].sort((a, b) => (a.seat < b.seat ? 1 : -1)) // thesis, then antithesis
}
export const findingStatement = (finding: AuditFinding) =>
`${finding.kind}${finding.cwe ? ` ${finding.cwe}` : ""} (${finding.severity}): ${finding.title} — ${finding.detail}`
const SEVERITY = ["critical", "high", "medium", "low", "info"] as const
/** Write the round's dossier (findings as witnessed claims) and REPORT.md. */
export async function writeAuditArtifacts(
root: string,
audit: Audit,
round: number,
tree: { root: string },
/** Engine-key dir for the sealed source cache (#98); defaults to the global state dir. */
stateDir?: string,
): Promise<{ dossier: Dossier; report: string }> {
const layout = factoryLayout(root)
const records = roundRecords(await readAuditRecords(root, audit.id), round)
const claims = records.flatMap((record) =>
record.findings.map((finding) =>
normalizeClaim({
tag: "VERIFIED",
statement: findingStatement(finding),
location: { file: finding.file.replace(/^\.\//, ""), lines: finding.lines, excerpt: finding.excerpt },
severity: finding.severity,
}),
),
)
const dossier = await writeDossier(
layout.auditDossier(audit.id),
buildDossier({
mode: "code-audit",
subject: `${describeTarget(audit.target)} (round ${round})`,
claims,
verdicts: records.map((record) => ({
by: record.by,
verdict: record.verdict,
at: record.at,
notes: record.notes,
})),
}),
{ cache: researchCache(root, stateDir ?? defaultStateDir()), root: tree.root },
)
const report = renderAuditReport(audit, round, records)
await atomicWrite(layout.auditReport(audit.id), report)
return { dossier, report }
}
/** Re-render a round's report from its records (no disk witness), after the verdict settles the audit. */
export async function rewriteAuditReport(root: string, audit: Audit, round: number): Promise<void> {
const records = roundRecords(await readAuditRecords(root, audit.id), round)
await atomicWrite(factoryLayout(root).auditReport(audit.id), renderAuditReport(audit, round, records))
}
/** Deterministic markdown: verdicts, then findings by severity, file and line. */
export function renderAuditReport(audit: Audit, round: number, records: readonly AuditRecord[]): string {
const seat = (name: AuditRecord["seat"]) => records.find((record) => record.seat === name)
const findings = records
.flatMap((record) => record.findings.map((finding) => ({ finding, seat: record.seat.replace("auditor-", "") })))
.sort(
(a, b) =>
SEVERITY.indexOf(a.finding.severity) - SEVERITY.indexOf(b.finding.severity) ||
(a.finding.file < b.finding.file ? -1 : a.finding.file > b.finding.file ? 1 : 0) ||
a.finding.lines[0] - b.finding.lines[0],
)
const lines = [
`# Code audit ${audit.id}: ${describeTarget(audit.target)} (round ${round})`,
"",
`Status: ${audit.round === round ? audit.status : "superseded"} · thesis: ${seat("auditor-thesis")?.verdict ?? "—"} · antithesis: ${seat("auditor-antithesis")?.verdict ?? "—"}${audit.tiebreak && audit.round === round ? ` · tiebreak: ${audit.tiebreak.verdict}` : ""}`,
"",
"Every finding below was witnessed against the audited tree: the file exists, the lines are in range and the excerpt is verbatim.",
"",
"## Findings",
"",
]
if (!findings.length) lines.push("No findings were recorded.", "")
else {
lines.push("| Severity | Kind | CWE | Location | Finding | Seat |", "| --- | --- | --- | --- | --- | --- |")
for (const { finding, seat: who } of findings)
lines.push(
`| ${finding.severity} | ${finding.kind}${finding.kind === "invariant" ? (finding.holds ? " (holds)" : " (broken)") : ""} | ${finding.cwe ?? ""} | \`${pointer(finding)}\` | ${finding.title.replaceAll("|", "\\|")} | ${who} |`,
)
lines.push("")
for (const { finding, seat: who } of findings) {
lines.push(
`### ${finding.title}`,
"",
`\`${pointer(finding)}\` · ${finding.severity}${finding.cwe ? ` · ${finding.cwe}` : ""} · ${who}`,
"",
"```",
finding.excerpt,
"```",
"",
finding.detail,
"",
)
if (finding.remediation) lines.push(`**Remediation:** ${finding.remediation}`, "")
}
}
lines.push("## Verdict notes", "")
for (const record of records)
lines.push(`- **${record.seat.replace("auditor-", "")}** (${record.verdict}): ${record.notes || "—"}`)
return `${lines.join("\n").trimEnd()}\n`
}
/** The two seats' findings for a round, ranked as rival dossiers (shown to the manager at a tiebreak). */
export async function auditRankView(root: string, auditId: string, round: number): Promise<string[]> {
const records = roundRecords(await readAuditRecords(root, auditId), round)
const dossiers = records.map((record) =>
buildDossier({
mode: "code-audit",
subject: `${record.seat.replace("auditor-", "")} (${record.verdict})`,
claims: record.findings.map((finding) => ({
...normalizeClaim({
tag: "VERIFIED",
statement: findingStatement(finding),
location: { file: finding.file, lines: finding.lines, excerpt: finding.excerpt },
severity: finding.severity,
}),
witness: { ok: true, checkedAt: record.at },
})),
}),
)
const defects = (record: AuditRecord) =>
record.findings.filter(isDefect).map((finding) => `${finding.cwe ?? finding.kind} ${pointer(finding)}`)
return [
...rankDossiers(dossiers).map(explainDossierRank),
...records.map(
(record) =>
`${record.seat.replace("auditor-", "")}: ${record.verdict}, ${record.findings.length} finding(s)${defects(record).length ? `; defects: ${defects(record).join(", ")}` : ""}`,
),
]
}