ledgerlight

A FlossyPurse Studios tool

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.

MIT license zero dependencies
$ 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.

exitmeaning
0every loss is LIT
1some loss is DARK-IN-PRACTICE or UNCLAIMED
2usage 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.

verdictmeaning
LITresolved, and for tests, in a supplied report with every case passing
SKIPPEDin a report, but some or all cases were skipped or todo; says how many of how many
FAILEDin a report, with a failing case (a flaky pass counts as failed)
NOT-RUNa report was supplied for this project, but this file is not in it
NO-REPORTno 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.
UNRESOLVEDthe 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