The package is on the GitLab registry, and a bare npm install looks on npmjs.org and fails. The install section now sets the scope's registry first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
5.1 KiB
Markdown
141 lines
5.1 KiB
Markdown
# @extant2000/evidence-record
|
|
|
|
A small vocabulary for checks that have to show their work: four-way
|
|
determinations, cited evidence, and reports that derive their own verdict.
|
|
|
|
Zero runtime dependencies. Pure functions. Ships ESM and CommonJS.
|
|
|
|
## Why it exists
|
|
|
|
Automated checks tend to fail their readers in the same few ways. This package
|
|
gives every check one shared way to avoid them.
|
|
|
|
1. **A boolean hides the case that matters most.** `passed: true` can mean "we
|
|
looked and it held" or "there was nothing to look at". Those need to stay
|
|
apart, so a determination has four values, and three of them are not a
|
|
pass.
|
|
2. Findings that do not say where they came from ask the reader to take them
|
|
on trust. Every finding here carries `Evidence`: a file and line, a database
|
|
row, a command, a measurement, or a document.
|
|
3. A measurement without its sample count is not evidence. An availability of
|
|
100% over zero samples and one over 8,640 samples are different claims, so
|
|
the sample count travels with the number.
|
|
4. A report with zero findings is not a pass unless something was examined.
|
|
The verdict is computed from the report, and `examined === 0` always gives
|
|
`not-assessed`.
|
|
|
|
The unflattering outputs stay unflattering. A missing value is not a pass, and
|
|
nothing in this package turns one into a pass.
|
|
|
|
## Determinations
|
|
|
|
```ts
|
|
type Determination = 'pass' | 'fail' | 'not-applicable' | 'not-assessed'
|
|
```
|
|
|
|
`rollUp` combines several. Any fail wins. An empty list is `not-assessed`,
|
|
because `[].every(...)` being true is the trap this type closes. One unassessed
|
|
item keeps the roll-up at `not-assessed`, so healthy items cannot dilute it.
|
|
|
|
```ts
|
|
rollUp([]) // 'not-assessed'
|
|
rollUp(['pass', 'not-assessed']) // 'not-assessed'
|
|
rollUp(['pass', 'pass', 'fail']) // 'fail'
|
|
```
|
|
|
|
`label(d)` gives a short badge (`MET`, `NOT MET`, `N/A`, `NOT ASSESSED`).
|
|
`describe(d, subject)` gives a sentence that still makes sense when pasted into
|
|
an email on its own.
|
|
|
|
## Evidence
|
|
|
|
```ts
|
|
import { evidence, cite, describeMeasurement } from '@extant2000/evidence-record'
|
|
|
|
evidence.file('docker-compose.yml', 42, 'ports: ["2222:22"]')
|
|
evidence.record('requirements', 'a1b2c3', 'parent_id', 'null')
|
|
evidence.command('docker compose config', 0)
|
|
evidence.measurement('uptime', null, 0, { unit: '%', window: '2026-07' })
|
|
evidence.document('NIST SP 800-53 Rev 5', 'AC-2')
|
|
```
|
|
|
|
`cite()` renders each kind in the form people already read it in: `path:line`
|
|
for source, `table#id (field)` for rows, `$ command → exit 0` for commands.
|
|
`citeVerbose()` adds the excerpt and note. `describeMeasurement()` reads the
|
|
null case out loud: "No samples for Availability in 2026-07; it cannot be
|
|
computed."
|
|
|
|
## Reports
|
|
|
|
```ts
|
|
import { evidence, overall, validateReport, formatReport } from '@extant2000/evidence-record'
|
|
|
|
const report = {
|
|
capability: 'PortCheck',
|
|
scope: '12 compose services',
|
|
examined: 12,
|
|
findings: [
|
|
{ id: 'f1', summary: 'SSH published on a host port', determination: 'fail',
|
|
severity: 'critical', evidence: [evidence.file('docker-compose.yml', 42, 'ports: ["2222:22"]')] },
|
|
{ id: 'f2', summary: 'Admin UI bound to loopback only', determination: 'pass',
|
|
severity: 'info', evidence: [evidence.file('docker-compose.yml', 88)] },
|
|
],
|
|
}
|
|
|
|
overall(report) // 'fail', computed from the findings
|
|
validateReport(report) // [] when every conclusion carries a citation
|
|
formatReport(report) // worst first, one citation per finding
|
|
```
|
|
|
|
Output of `formatReport(report)`:
|
|
|
|
```
|
|
PortCheck — 12 compose services
|
|
NOT MET · 12 items examined · 2 findings
|
|
|
|
✗ CRITICAL SSH published on a host port [NOT MET]
|
|
evidence: docker-compose.yml:42
|
|
◦ info Admin UI bound to loopback only [MET]
|
|
evidence: docker-compose.yml:88
|
|
```
|
|
|
|
The same report with nothing examined:
|
|
|
|
```
|
|
PortCheck — no services found
|
|
NOT ASSESSED · 0 items examined · 0 findings
|
|
|
|
PortCheck's scope was not assessed. This is not a pass — an unassessed control is not an implemented control.
|
|
```
|
|
|
|
`validateReport` flags a finding that reaches `pass`, `fail` or
|
|
`not-applicable` with no evidence, plus duplicate ids and empty summaries. A
|
|
`not-assessed` finding may have no evidence, since having nothing to cite is
|
|
what it reports. `formatReport` accepts `{ evidence: 'inline' | 'full' | 'none',
|
|
minSeverity }`. `formatRollUp(reports)` gives one line per report and a
|
|
combined verdict, where an unrun report pulls the total to `not-assessed`.
|
|
|
|
## Conformance
|
|
|
|
A check built on this package conforms when its own tests assert that:
|
|
|
|
- `validateReport(report)` is empty for every report it can produce;
|
|
- an empty input produces `not-assessed`, and the rendered output contains
|
|
neither `✓` nor the word "clean";
|
|
- every `fail` finding carries at least one `Evidence`;
|
|
- the rendered output of a failure names a file, a row, or a command.
|
|
|
|
## Install
|
|
|
|
The package is published to the GitLab package registry, so point the
|
|
`@extant2000` scope there first:
|
|
|
|
```
|
|
npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
|
|
npm install @extant2000/evidence-record
|
|
```
|
|
|
|
## License
|
|
|
|
MIT. Copyright (c) 2026 Extant 2000 LLC.
|