Your ledger says it’s covered. Did the test run?
A loss ledger names the outcomes that must never happen to the people your software touches, and the tests that stand in front of each one. ledgerlight reads the ledger and the reports from a real test run, and tells you, loss by loss, whether every cited check actually executed and passed in that run.
$ npx ledgerlight@0.1.0 verify loss-ledger.md --report unit=reports/unit.json ledger: loss-ledger.md reports: unit L8 DARK-IN-PRACTICE NO-REPORT test test/integration/erasure-completeness.test.ts (project: integration) no report supplied for project "integration"; nothing shows this test ever ran L1 LIT LIT test test/unit/refund-guards.test.ts (project: unit) 3 of 3 cases passed in the "unit" report 2 losses: 1 lit, 1 dark-in-practice, 0 unclaimed. A loss is only lit when every claim standing in front of it ran, in a run you supplied.
A test that exists is not a test that ran
You are about to say “that’s covered” and point at a green test run. The failure ledgerlight catches is not a missing test. The ledger claims a loss is covered by a test, the test exists, it is well written, and it does not execute in the run people quote as proof.
A typical way it happens: the test lives in a separate integration project behind describe.skipIf(!HAS_DB), so the default npm test never runs it, and the green default run gets cited anyway. Above, L8’s test is real and passes when it runs. Nothing supplied shows that it ran.
Reports come from the command line
The ledger says what should guard a loss. The --report flags say what you actually ran. They never live in the same file, so a ledger cannot vouch for itself.
It refuses to guess
No loss-list section, an unknown key, a typo’d projet:: each is an error with the file and line, exit 2. A claim silently dropped would be the very failure it exists to catch.
Install
npx ledgerlight@0.1.0 verify docs/loss-ledger.md \ --report unit=reports/unit.json \ --report integration=reports/integration.json
Nothing to install and no config file. Node 20 or newer, zero runtime dependencies. Pin the version in anything you write down. Reports are vitest or Playwright JSON: vitest run --reporter=json --outputFile=unit.json, playwright test --reporter=json > e2e.json.
| exit | meaning |
|---|---|
| 0 | every loss is LIT |
| 1 | some loss is DARK-IN-PRACTICE or UNCLAIMED |
| 2 | usage or parse error |
The verdicts
Per claim. A loss is LIT only when every one of its claims is; otherwise it is DARK-IN-PRACTICE, headlined by its weakest claim, and the output is ordered worst first. A loss in the list with no claim block is UNCLAIMED.
| verdict | meaning |
|---|---|
LIT | resolved, and for tests, in a supplied report with every case passing |
SKIPPED | in a report, but some or all cases were skipped or todo; says how many of how many |
FAILED | in a report, with a failing case (a flaky pass counts as failed) |
NOT-RUN | a report was supplied for this project, but this file is not in it |
NO-REPORT | no report was supplied for this claim’s project at all. Never a pass. A ledger checked with no reports is entirely NO-REPORT, and exits 1. |
UNRESOLVED | the file, symbol or commit is not in the repo |
The ledger blocks
Two fenced block types in your markdown ledger. Everything else is prose, and ignored. One config block gives the git root every citation resolves against, relative to the ledger file:
```ledgerlight-config repo: ../app ```
Then one claim block right after each light’s prose. A test: claim needs a project:, which is what --report <project>= keys off:
```ledgerlight
loss: L8
covers:
- test: test/unit/refund-guards.test.ts
project: unit
- file: lib/refund-guards.ts
- symbol: applyRefund
file: lib/order-router.ts
- commit: 7c3a1e9
```
The loss list itself is any heading containing “loss list”, with **L1**-style identifiers in bold or table-leading position. The pre-release tags lantern-config and lantern still work, with one warning per ledger.
In CI
- run: npx vitest run --project unit --reporter=json --outputFile=reports/unit.json
- run: npx vitest run --project integration --reporter=json --outputFile=reports/integration.json
- run: >
npx ledgerlight@0.1.0 verify docs/loss-ledger.md
--report unit=reports/unit.json
--report integration=reports/integration.json
Generate the reports in the same job, so they are the run you are talking about. Exit 1 fails the job. --json prints the whole result as data.
What it can’t tell you
- A report is taken at its word. ledgerlight cannot tell whether the JSON came from the run you are talking about or a lucky run last Tuesday.
symbol:is a text search, not AST analysis. It will match inside a comment and cannot tell an export from a local.- Only four claim kinds exist: test, file, symbol, commit. A light that is a manual procedure or a monitor is invisible to it.
- One command.
verifyis the whole tool for now.