ponder

tests lens

The subject of this lens is the promises the system makes, not the files in test/. A suite is a means. Grading files produces a comfortable number and misses the only question worth asking: if this behaviour broke tomorrow, would anything go red, and would the red thing tell you what broke.

What a unit is

A unit is one behaviour: one observable promise, written as a sentence with a condition, a subject and an outcome.

When coverage < 1.0, ponder decide stop exits 2 and names every ungraded unit.

When the OCA DSN is unreachable, every oca/bridge.js export returns null, appends a line to oca.log, and never throws.

When a delta arrives with an empty why, ponder delta rejects the whole file and applies nothing.

Consequences of that definition:

Unit ids. behaviour.<area>.<verb-phrase>, lowercase dotted, stable across passes: behaviour.verdict.blocked-on-partial-coverage, behaviour.bridge.never-throws, behaviour.delta.rejects-empty-why, behaviour.ledger.append-only, behaviour.cli.next-line-on-every-command.

How to enumerate exhaustively

Enumerate from the contract first. Opening test/ first anchors you to what already exists and guarantees you miss the gaps.

1. From the contract

Every normative sentence in the spec is a candidate behaviour.

grep -nE '\b(must|never|always|refuses?|blocks?|rejects?|exits?|cannot|may not)\b' SPEC.md

For ponder specifically, section 8 is a hand-written behaviour list — every non-negotiable is a unit, and each one should be checkable by a test that goes red when the guarantee is broken.

2. From the public surface

grep -rhoE '^export (async )?(function|class|const) [A-Za-z0-9_$]+' lib/ oca/ \
  | awk '{print $NF}' | sort -u

Each export contributes at least three behaviours: the documented success outcome, the documented failure outcome, and the boundary (empty input, missing input, hostile input).

3. From the CLI surface

Every command crossed with every exit code it can produce.

grep -rnE 'process\.exit\(|process\.exitCode\s*=' lib/*.js
grep -rn "NEXT:" lib/*.js | wc -l

The spec says every command exits 0, 1 or 2, and every command prints a NEXT: line. That is commands × 3 plus commands × 1 behaviours, and most suites cover the 0 column only.

4. From the branch points

Every branch that changes user-visible output is a promise about which side you land on.

grep -rnE '\bif \(|\bswitch \(|\?\?|&& |\|\| ' lib/verdict.js

# every place the code gives up. Find the idiom first — `throw new` is one dialect.
grep -rhoE 'throw [A-Za-z.]+\(' lib/ oca/ | sort | uniq -c | sort -rn
grep -rnE 'throw (new [A-Za-z]*Error|userError|stateError)\(' lib/ oca/

# every deadline. Grep the word, not one implementation of it.
grep -rniE 'timeout|deadline|abortcontroller|abortsignal|signal:|SIGKILL' lib/ oca/

Both of those greps fail silently in the obvious form, and both failures cost you the behaviours this lens exists to find:

The verdict rules in spec section 4.1 are eight ordered branches. Eight behaviours, plus the ordering itself — “rule 1 wins over rule 4 when both match” is its own promise and is the one that usually has no test.

5. From history

git log --oneline --since='1 year ago' -E --grep='fix|bug|regression|broke' -i

Pass -E and a plain |. The \| form returns nothing at all for anyone who has grep.patternType = extended set in their gitconfig, and an empty result here reads as “this project has never shipped a bug”, which is never the finding.

Every fix in that list was a behaviour somebody assumed. Each one is a unit; whether a regression test exists is what you are grading.

6. Only now, the test tree

Find the runner before you grep for test names — the suite is not always where the language’s default runner would look. ponder’s own suite is test/lib.sh, a shell harness, so node --test test/ reports a failure that says nothing about ponder.

# what actually runs the tests
sed -n '/"scripts"/,/}/p' package.json 2>/dev/null
ls test/ tests/ spec/ 2>/dev/null; grep -rn 'run:' .github/workflows/*.yml 2>/dev/null

# then the test names, in whichever dialect that runner uses
grep -rhoE "(test|it)\(\s*['\"\`][^'\"\`]+" test/ | sed -E "s/.*\(\s*['\"\`]//" | sort

# a shell suite names its tests through its own helper. The most-repeated leading call is it:
grep -rhoE "^[a-z_]+ [\"']" test/*.sh 2>/dev/null | sort | uniq -c | sort -rn | head

On ponder that last line returns 44 run_test ', and grep -h "run_test" test/*.sh is then the real list of test names. Two minutes spent finding the declaration helper is the difference between enumerating 44 tests and enumerating none.

Map each test name onto a behaviour from steps 1–5. Two outcomes matter:

Line coverage, and what it is actually for

Whatever your runner’s coverage flag is — node --test --experimental-test-coverage test/, pytest --cov, go test -cover, cargo llvm-cov. A suite with no coverage tooling at all is one finding, not a reason to skip the step.

Run it. Then use it only as a negative signal. A file at 0% contains behaviours nobody enumerated — that is real information. A file at 95% tells you which lines executed, and execution is not verification: a test with no assertions executes every line it touches. High line coverage is compatible with a suite that cannot fail.

If a finding on this lens cites a coverage percentage as its evidence and names no behaviour, it is not a finding.

Closing the universe

Coverage is 1.0 when every behaviour from steps 1–5 has a grade — including every behaviour with zero tests. A pass that grades the 40 behaviours the suite already covers and skips the 12 it does not has coverage 0.77 and should be blocked.

Two engine mechanics decide whether this session can ever stop:

A well-tested behaviour still needs a finding. The engine counts a unit as graded when it carries at least one finding that is not retracted. The behaviours with excellent tests are exactly the ones you have nothing to say about — so they end the pass ungraded, coverage lands at 0.9, and stop is refused with no override. Record them: score 5, severity note, the test anchor, and the mutation you used to confirm the test can fail. That note is not bureaucracy; it is the only durable record that somebody verified this promise on purpose.

Retracting a unit’s last live finding un-grades the unit. Merging two duplicate behaviours into one — which this lens tells the Skeptic to do — retracts a finding, and if it was the only one on its unit, coverage falls and the session re-blocks. Retire the duplicate unit with a unit-delete delta rather than retracting the finding that was holding its coverage up, or leave a one-line pointer finding behind.

The dimensions

behaviour-coverage

Is this promise checked at all, at the level it is made. Not “is there a test near it” — is the promise, whole, verified.

failure-mode-coverage

The ways this behaviour breaks under conditions that actually occur — not exotic conditions, real ones: the dependency is down, the disk is full, the input is malformed, the call times out, the binary is missing from PATH.

honesty

Does the test fail when the behaviour breaks. A test that cannot fail scores 1 no matter how thorough it looks. This is the dimension that separates a suite from a ritual.

speed

Fast enough that the suite is a feedback loop instead of a gate. Speed is a correctness property — a suite nobody runs before pushing does not protect anything.

isolation

The result depends on the behaviour and nothing else — not test order, not the developer’s environment, not the previous run’s leftovers, not a neighbour running in parallel.

fixture-realism

The data in the test resembles the data in production closely enough that the test exercises the code path production will take.

assertion-specificity

What the failure message tells you when it goes red. A red test is a message to a tired person; grade it as one.

What evidence looks like

Evidence is path:line or path:line-line, and this lens needs two kinds of anchor.

Finding kind Required evidence
Weak or missing coverage Implementation anchor (lib/verdict.js:44-58) and every test claimed to cover it (test/verdict.test.js:12-30), or the note that there is none
Dishonest test (honesty = 1) The test anchor plus the exact mutation that leaves it green: "set severity to 'should' at lib/verdict.js:51 — suite stays green"
Failure-mode gap The code that declares the failure mode (oca/bridge.js:64) plus the enumerator command that surfaced it
Speed A measured number, from node --test output or time, in detail. No number, no finding
Isolation The shared resource, by path or variable: test/report.test.js:8 writing to .ponder/
Fixture realism The fixture anchor plus the production shape it fails to resemble
Assertion specificity The assertion line, and the failure message it currently produces

A finding that says “needs more tests” with a coverage percentage and no behaviour sentence has no evidence. The Skeptic deletes it.

Severity calibration

Situation Severity
A dishonest test — score 1 on honesty must
An untested behaviour that appears in the contract as a guarantee must
An untested failure mode on a component whose contract is its failure behaviour must
A behaviour covered only by a slow end-to-end test nobody runs locally should
Order-dependence or shared state between tests should
Unrealistic fixture scale could
Vague assertion on a behaviour that is otherwise well covered could

A dishonest test outranks a missing test. A missing test is a known hole; a test that cannot fail is a hole with a sign over it saying the hole is filled, and it actively prevents anyone from writing the real one.

What each perspective hunts

Skeptic — emits delete

Builder — emits edit

Dreamer — emits add

Empath — emits change

What a later pass does with this lens

Pass 1 enumerates behaviours and grades them from reading. Pass 2 and pass 3 exist to convert those readings into verified facts, and re-reading the same test files is not that.

Common failure modes of this lens

  1. Enumerating test files. Then coverage 1.0 means “we looked at every file we already have”, which is precisely the claim this lens exists to refuse.
  2. Substituting line coverage for behaviour coverage. They are different measurements and only one of them is about promises.
  3. Grading the suite instead of the behaviours. “The suite is slow” is not a finding against a unit. “This behaviour is only checked by a 4-minute run” is.
  4. Skipping the honesty check because it takes effort. Mutating the code and re-running is the single highest-yield thing this lens does. A pass that never ran a mutation has not graded honesty, it has guessed at it — cap confidence at 0.5 and say so.