First public release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:46:17 -04:00
co-authored by Claude Opus 5.5
commit 7640ad722c
17 changed files with 2433 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
/**
* The four things a check can conclude — and the three of them that are not
* a pass.
*
* A boolean cannot carry this. `passed: true` collapses "we checked and it
* held" together with "there was nothing to check", and those are the two
* cases a reviewer most needs kept apart. An earlier version of these
* libraries said VACUOUS for the second, which is precise and unreadable; this
* replaces the jargon without softening the meaning.
*/
export type Determination =
/** Something was examined against the criterion, and it held. */
| 'pass'
/** Something was examined against the criterion, and it did not hold. */
| 'fail'
/** The criterion was examined and genuinely does not apply here. */
| 'not-applicable'
/** Nothing was examined. Never a pass. */
| 'not-assessed'
/**
* One-word label for a column or badge.
*
* `not-assessed` deliberately does NOT get a neutral word like "skipped" —
* "skipped" reads as a decision someone made, and this is usually an absence
* nobody noticed.
*/
export function label(d: Determination): string {
switch (d) {
case 'pass': return 'MET'
case 'fail': return 'NOT MET'
case 'not-applicable': return 'N/A'
case 'not-assessed': return 'NOT ASSESSED'
}
}
/**
* A full sentence a reviewer can read without a glossary.
*
* `subject` names what was being checked, so the sentence stands alone when
* it is lifted out of the report and pasted into an email — which is how
* these findings actually travel.
*/
export function describe(d: Determination, subject = 'This item'): string {
switch (d) {
case 'pass':
return `${subject} was checked and met the criterion.`
case 'fail':
return `${subject} was checked and did not meet the criterion.`
case 'not-applicable':
return `${subject} was examined and the criterion does not apply to it.`
case 'not-assessed':
return `${subject} was not assessed. This is not a pass — an unassessed control is not an implemented control.`
}
}
/** True only for the one determination that is genuinely a clean result. */
export function isPass(d: Determination): boolean {
return d === 'pass'
}
/**
* Roll several determinations into one.
*
* Order matters and is deliberate:
* any fail → fail
* nothing examined → not-assessed (NOT a pass; this is the vacuous case)
* everything N/A → not-applicable
* otherwise → pass
*
* The empty list returns `not-assessed` rather than `pass`. `[].every(...)`
* being true is the exact trap this type exists to close.
*/
export function rollUp(ds: Determination[]): Determination {
if (ds.some(d => d === 'fail')) return 'fail'
if (ds.length === 0) return 'not-assessed'
if (ds.some(d => d === 'not-assessed')) return 'not-assessed'
if (ds.every(d => d === 'not-applicable')) return 'not-applicable'
return 'pass'
}
+157
View File
@@ -0,0 +1,157 @@
/**
* 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})` : ''}.`
}
+22
View File
@@ -0,0 +1,22 @@
export type { Determination } from './determination.js'
export { label, describe, isPass, rollUp } from './determination.js'
export type {
Evidence,
EvidenceSource,
FileEvidence,
RecordEvidence,
CommandEvidence,
MeasurementEvidence,
DocumentEvidence,
} from './evidence.js'
export { cite, citeVerbose, evidence, describeMeasurement } from './evidence.js'
export type {
Severity,
Finding,
CapabilityReport,
ReportProblem,
FormatOptions,
} from './report.js'
export { overall, validateReport, formatReport, formatRollUp } from './report.js'
+197
View File
@@ -0,0 +1,197 @@
import { type Determination, label, describe, rollUp } from './determination.js'
import { type Evidence, cite, citeVerbose } from './evidence.js'
export type Severity = 'critical' | 'high' | 'medium' | 'info'
const RANK: Record<Severity, number> = { critical: 0, high: 1, medium: 2, info: 3 }
export interface Finding {
/**
* Stable within one report only. These identify a row in THIS run, not a
* durable record — do not persist them as entity keys.
*/
id: string
/** One line, in the reader's language, not the checker's. */
summary: string
determination: Determination
severity: Severity
/**
* What produced this finding. Required, and allowed to be empty only for
* `not-assessed` — where the whole point is that nothing was observed.
*/
evidence: Evidence[]
/** Optional paragraph: why it matters, what to do. */
detail?: string
}
export interface CapabilityReport {
/** The capability that produced this, e.g. "SideDoor". */
capability: string
/** What was examined, in a phrase: "49 compose services on the build host". */
scope: string
/** How many items were actually examined. Zero forces `not-assessed`. */
examined: number
findings: Finding[]
/** Free-form lines printed under the summary — caveats, exclusions. */
notes?: string[]
}
/**
* The report's overall determination, derived rather than asserted.
*
* Deriving it closes the gap where a capability reports zero findings and a
* reader infers a pass. Zero findings over zero examined items is
* `not-assessed`, and it says so.
*/
export function overall(r: CapabilityReport): Determination {
if (r.examined === 0) return 'not-assessed'
if (r.findings.length === 0) return 'pass'
return rollUp(r.findings.map(f => f.determination))
}
export interface ReportProblem {
findingId: string
problem: string
}
/**
* Check the report against the standard before it is shown to anyone.
*
* This is the enforcement half of "evidence attached, not asserted". A
* capability that concludes something without naming what it looked at fails
* here, in its own test suite, rather than in front of a reviewer.
*/
export function validateReport(r: CapabilityReport): ReportProblem[] {
const problems: ReportProblem[] = []
const seen = new Set<string>()
for (const f of r.findings) {
if (seen.has(f.id)) problems.push({ findingId: f.id, problem: 'duplicate finding id' })
seen.add(f.id)
if (f.determination !== 'not-assessed' && f.evidence.length === 0) {
problems.push({
findingId: f.id,
problem: `determination "${f.determination}" carries no evidence — an asserted result is not a verified one`,
})
}
if (!f.summary.trim()) {
problems.push({ findingId: f.id, problem: 'empty summary' })
}
}
if (r.examined > 0 && r.findings.length === 0 && r.notes?.length === 0) {
// Not a problem, but worth surfacing: a clean run should still say what
// it looked at, and `scope` is the only place that lives.
if (!r.scope.trim()) problems.push({ findingId: '(report)', problem: 'clean run with no scope described' })
}
return problems
}
export interface FormatOptions {
/**
* `inline` — one citation per finding (default; fits a CI log)
* `full` — every citation, with excerpts and notes
* `none` — findings only, for a summary email
*/
evidence?: 'inline' | 'full' | 'none'
/** Drop findings below this severity. Defaults to showing everything. */
minSeverity?: Severity
}
/**
* Render a report for a terminal, a CI log, or an email.
*
* Worst first, so a critical never hides under a page of info lines, and the
* unflattering cases stay unflattering: a not-assessed run leads with the
* fact that nothing was checked instead of a green tick.
*/
export function formatReport(r: CapabilityReport, opts: FormatOptions = {}): string {
const mode = opts.evidence ?? 'inline'
const floor = opts.minSeverity ? RANK[opts.minSeverity] : RANK.info
const verdict = overall(r)
const out: string[] = []
out.push(`${r.capability} — ${r.scope}`)
out.push(` ${label(verdict)} · ${r.examined} item${r.examined === 1 ? '' : 's'} examined · ${r.findings.length} finding${r.findings.length === 1 ? '' : 's'}`)
if (verdict === 'not-assessed' && r.examined === 0) {
out.push('')
out.push(` ${describe('not-assessed', `${r.capability}'s scope`)}`)
for (const n of r.notes ?? []) out.push(` ${n}`)
return out.join('\n')
}
for (const n of r.notes ?? []) out.push(` ${n}`)
const shown = r.findings
.filter(f => RANK[f.severity] <= floor)
.sort((a, b) => RANK[a.severity] - RANK[b.severity] || a.id.localeCompare(b.id))
if (shown.length > 0) out.push('')
for (const f of shown) {
const tag = f.severity === 'critical' ? '✗ CRITICAL'
: f.severity === 'high' ? '✗ HIGH '
: f.severity === 'medium' ? '· MEDIUM '
: '◦ info '
out.push(`${tag} ${f.summary} [${label(f.determination)}]`)
if (f.detail) out.push(` ${f.detail}`)
if (mode === 'none') continue
if (f.evidence.length === 0) {
// A not-assessed finding has nothing to cite by definition — that IS
// its content, and labelling it a missing citation would read as a
// defect in the report rather than the gap it is reporting.
//
// Anything else with no evidence is loud on purpose: a conclusion with
// nothing behind it should look worse than one with a citation.
out.push(f.determination === 'not-assessed'
? ' (nothing was observed)'
: ' (no evidence attached)')
continue
}
if (mode === 'inline') {
out.push(` evidence: ${cite(f.evidence[0]!)}${f.evidence.length > 1 ? ` (+${f.evidence.length - 1} more)` : ''}`)
} else {
for (const e of f.evidence) {
for (const line of citeVerbose(e)) out.push(` ${line}`)
}
}
}
if (shown.length === 0 && r.findings.length > 0) {
out.push('')
out.push(` ${r.findings.length} finding(s) below the ${opts.minSeverity} threshold, not shown.`)
}
if (r.findings.length === 0 && verdict === 'pass') {
out.push('')
// Scope-neutral on purpose: "49 compose services was checked" is the
// grammar you get from interpolating a count into a sentence, and the
// count is already on the line above.
out.push('✓ Every item examined met the criterion.')
}
return out.join('\n')
}
/**
* Roll several capabilities into one portfolio line.
*
* The same asymmetry as the per-report case: a capability that was never run
* must not average away as a pass, so it drags the roll-up to not-assessed
* rather than being skipped.
*/
export function formatRollUp(reports: CapabilityReport[]): string {
const out: string[] = []
const verdict = rollUp(reports.map(overall))
out.push(`Portfolio — ${reports.length} capabilit${reports.length === 1 ? 'y' : 'ies'} · ${label(verdict)}`)
for (const r of reports) {
const v = overall(r)
const fails = r.findings.filter(f => f.determination === 'fail').length
out.push(` ${label(v).padEnd(12)} ${r.capability.padEnd(18)} ${r.examined} examined, ${fails} not met`)
}
return out.join('\n')
}