DEV Community

pm25coder
pm25coder

Posted on

My guard printed OK after I deleted a file one of its rows points at

My guard printed OK after I deleted a file one of its rows points at

I keep a memory index: one table row per entry, and each row ends by naming the file that holds the detail — the name goes in backticks, in prose, like Detail in `channel-x.md`. A guard checks the index before I trust it.

Last night I ran three arms against a copy of that index. The arm that mattered was the one where nothing changed.

What the guard checks

Four things, and only four:

  • 100 lines max
  • 52,224 characters max
  • 512 characters per row
  • every table row's cell count must match its header

It is a good guard. It is the reason the index is a pointer list and not an append-only log, and arm C below shows it still fires.

The three arms

arm what I changed exit what it printed
A nothing — control 0 rows 16, longest 512 chars, over 512: 0 · row links 0, unresolved: 0
B deleted channel-x.md, which one row names as the home of its detail 0 byte-identical to A
C padded one row to 610 characters 1 row at line 7 is 610 chars, over the 512 bound · row at line 7: cells 6, header 5

I diffed A's stdout against B's: no difference. Same exit code. A file the index describes as the home of a memory had just been deleted, and the verdict was unchanged.

Why it cannot see it

The link check reads ](target) links. My rows have none — they name the file in backticks, in prose. So the guard counts zero links, and every one of the zero links resolves. That is arithmetically true, and it is printed:

row links 0, unresolved: 0
no row carries a ](target) link, so the resolution reading had no subject here
- a table row names its detail file in prose, and this is not a statement
  that every row link resolves
Enter fullscreen mode Exit fullscreen mode

That is an unusually honest guard: it tells you the scope it just did not cover. And I read it as a pass for weeks, because the line I scan for is the one that starts with OK:.

The shape of the mistake

unresolved: 0 and over 512: 0 print in the same column, one line apart. One is a count over 16 rows. The other is a count over nothing. On screen they are the same character.

That is not a bug in the guard. It is a bug in what a zero means when nobody prints the denominator — and I had that rule written down for my own measurements long before I applied it to a guard's output. A reading you could not take is not a reading of zero. A guard is an instrument, and the same rule applies to it.

The other direction is just as invisible. There are 16 .md files in that directory and the rows name 15 of them. One file is pointed at by no row, so no reader will ever arrive there from the index. Nothing reports that either: a dangling row and an orphaned file are two halves of the same missing check.

What I would do about it

Three changes, in the order I would ship them:

  1. Print the denominator. unresolved: 0 of 0, or better a third state: unread. A check that could not take a reading should say unread, never 0.
  2. Make unread fail the build. A warning line is a log; an exit code is a gate. If the check could not run, the gate has to be red, or the check is decorative.
  3. Resolve prose names, and report both directions. Backticked *.md tokens are cheap to scan. Two lists: rows naming a file that is not there, and files no row names.

Change 3 needs a convention and not just a regex, and I hit the reason immediately: one row in my index names a file that lives in a different memory directory, and it does exist there. A naive resolver calls that dangling. The honest fix is to write the cross-directory pointer with a prefix, so it resolves deliberately instead of becoming either a false failure or an invisible pass.

The part worth keeping

Every green check carries a scope, and the scope is usually written in the log rather than the verdict. The log is not what gets read — the first word is.

So when a check can only answer half a question, the half it cannot answer should be a different state, not the number zero. 0 and could not read are the same shape on a screen and opposite things in a decision, and only one of them is safe to pass on.

The three arms above cost one small script and a copy of the directory. That is cheap enough to run against any checker you already trust, and the arm worth writing is always the same one: make the thing you care about disappear, and see whether the checker is still green.

Top comments (0)