/** * Where a finding came from. * * The rule this type exists to enforce: a finding names the artefact that * produced it. SideDoor already did this — it prints the service, the port * binding and the compose file — and it is noticeably the most useful of these * outputs to read. A result that is asserted without a source leaves the * reader to take it on trust, and an unverified claim should count as absent. */ /** A location in source or configuration. */ export interface FileEvidence { kind: 'file' path: string /** 1-indexed. Omitted when the finding is about the file as a whole. */ line?: number /** The matched text. Keep it short — one line, trimmed. */ excerpt?: string } /** A row in a database. */ export interface RecordEvidence { kind: 'record' table: string id: string /** The column that carried the value the finding turns on. */ field?: string value?: string } /** Output of a command that was actually run. */ export interface CommandEvidence { kind: 'command' command: string exitCode?: number excerpt?: string } /** * A number that was measured, together with how much data stood behind it. * * `samples` is not decoration. An availability of 100% over zero samples and * an availability of 100% over 8,640 samples are different claims, and only * one of them is evidence. */ export interface MeasurementEvidence { kind: 'measurement' metric: string /** Null when it could not be computed. Never substitute a flattering default. */ value: number | null unit?: string samples: number /** e.g. "2026-07-01 → 2026-07-31". */ window?: string } /** A written source: a policy, a standard, a control catalogue. */ export interface DocumentEvidence { kind: 'document' title: string section?: string url?: string } export type EvidenceSource = | FileEvidence | RecordEvidence | CommandEvidence | MeasurementEvidence | DocumentEvidence export interface Evidence { source: EvidenceSource /** ISO 8601. When the observation was made, if the caller tracks it. */ observedAt?: string /** One line on why this artefact supports the finding. */ note?: string } /** * A short, copy-pasteable citation — the thing a reader scans for. * * Formats follow the conventions each kind is already read in: `path:line` * for source (clickable in most terminals), `table#id` for rows. */ export function cite(e: Evidence): string { const s = e.source switch (s.kind) { case 'file': return s.line ? `${s.path}:${s.line}` : s.path case 'record': return s.field ? `${s.table}#${s.id} (${s.field})` : `${s.table}#${s.id}` case 'command': return s.exitCode === undefined ? `$ ${s.command}` : `$ ${s.command} → exit ${s.exitCode}` case 'measurement': { const v = s.value === null ? 'not computable' : `${s.value}${s.unit ?? ''}` const n = `${s.samples} sample${s.samples === 1 ? '' : 's'}` return s.window ? `${s.metric} = ${v} (${n}, ${s.window})` : `${s.metric} = ${v} (${n})` } case 'document': return s.section ? `${s.title} § ${s.section}` : s.title } } /** Longer form: the citation plus whatever context was attached to it. */ export function citeVerbose(e: Evidence): string[] { const out = [cite(e)] const s = e.source if ((s.kind === 'file' || s.kind === 'command') && s.excerpt) out.push(` ${s.excerpt.trim()}`) if (s.kind === 'record' && s.value !== undefined) out.push(` value: ${s.value}`) if (e.note) out.push(` ${e.note}`) return out } /** Convenience constructors, so call sites stay one line. */ export const evidence = { file(path: string, line?: number, excerpt?: string, note?: string): Evidence { return { source: { kind: 'file', path, line, excerpt }, note } }, record(table: string, id: string, field?: string, value?: string, note?: string): Evidence { return { source: { kind: 'record', table, id, field, value }, note } }, command(command: string, exitCode?: number, excerpt?: string, note?: string): Evidence { return { source: { kind: 'command', command, exitCode, excerpt }, note } }, measurement( metric: string, value: number | null, samples: number, opts: { unit?: string, window?: string, note?: string } = {}, ): Evidence { return { source: { kind: 'measurement', metric, value, samples, unit: opts.unit, window: opts.window }, note: opts.note, } }, document(title: string, section?: string, url?: string, note?: string): Evidence { return { source: { kind: 'document', title, section, url }, note } }, } /** * Read a measurement out loud, including the case where there is nothing to * read. * * `uptime null, met=false` was correct and unreadable. A reader should not * have to know that zero samples is not 100% in order to understand the line. */ export function describeMeasurement(s: MeasurementEvidence): string { if (s.samples === 0) { return `No samples for ${s.metric}${s.window ? ` in ${s.window}` : ''}; it cannot be computed.` } if (s.value === null) { return `${s.metric} could not be computed from the ${s.samples} available sample${s.samples === 1 ? '' : 's'}.` } return `${s.metric} is ${s.value}${s.unit ?? ''} over ${s.samples} sample${s.samples === 1 ? '' : 's'}${s.window ? ` (${s.window})` : ''}.` }