fix(infra): a failed measurement must never render as a reassuring value
Tier 3, all five the same defect class (Verifiseringsloven ansikt 4): a
broken or uninstrumented query returning a positive-looking null, consumed
as a fact about the world.
F5 coord-count.sh: a mailbox root that does not exist was byte-identical
to one where nobody has pending mail - zero lines, exit 0, silent
stderr. Now exit 3 + a named stderr line; an existing-but-empty root
stays a silent, clean 0. 3 rather than 2 because 2 already means "you
called me wrong" and this means "the world you named is not there".
F14 coord-count.sh: the header promised exit 0 unconditionally while
--exclude with no value already exited 2. Contract restated as
0/2/3 and pinned as a check on the help TEXT.
F6 board.sh: `git status | wc -l` yields 0 lines whether the tree is
clean or git refused to answer, so a failure printed DRT=0. Now "?",
and BOTH awk consumers handle it - --plan's free-capacity test
compares the field as a string against "0" (a "?" coerces to 0 in
arithmetic and would certify an unmeasured tree as free), and the SUM
roll-up names what it could not add.
F10 board.sh: a scan root that does not exist was skipped in silence and
the empty scan exited 0. Bad roots are now named on stderr; exit 3
only when NO root was scanned. A mix still exits 0 and prints the
board. Replaces an assertion that encoded this defect as a pass.
F13 pre-state-line-guard.mjs: MAX_LINES is overridable via
CLAUDE_STATE_MAX_LINES so the boundary is testable without hardcoding
120 twice. An unusable value denies by name rather than falling back
to the default - a limit that silently did not take effect is the
same defect one layer up.
Every design choice mutation-tested; every negative check carries a
known-positive control. Section 11's first cut was vacuously green (wrong
basename + unexported fixture path) - recorded in CLAUDE.md rather than
quietly fixed, and the section now asserts its own ground truth.
Denominator measured, not estimated: coord-inbox.sh:57 and
coord-order-inbox.sh:60/64 carry the same `|| exit 0` shape and are
deliberately left alone (injection path, prose output, must never fail a
SessionStart) - stated in CLAUDE.md as a bounded gap.
Suites: coord 230->242, board 281->300, guard 40->54, route 69, orders
110, npm 11/11. Verified under system bash 3.2, not just Homebrew 5.3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3beef2a603
commit
d8fdeaa991
7 changed files with 606 additions and 16 deletions
118
CLAUDE.md
118
CLAUDE.md
|
|
@ -16,7 +16,7 @@ marketplace plugin. Three components, one boundary:
|
|||
`coord-done.sh` archives, `coord-count.sh` counts without delivering,
|
||||
`coord-sweep.sh` closes the aged FYI backlog machine-wide.
|
||||
Everything is pinned by `coord-selftest.sh`
|
||||
(230 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
||||
(242 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
||||
|
||||
**`ktg-plugin-marketplace` is a RETIRED `--to` address (operator decision
|
||||
2026-08-15), rejected rather than redirected.** It is a polyrepo directory,
|
||||
|
|
@ -102,6 +102,39 @@ marketplace plugin. Three components, one boundary:
|
|||
field means a reply IS owed, because every message written before 0.11.0
|
||||
lacks it.
|
||||
|
||||
**F5+F14 (2026-08-26): the header promised exit 0 unconditionally, and both
|
||||
halves of that were false - one of them silently.** `--exclude` with no value
|
||||
already exited 2 (F14), so the documented contract contradicted the code a
|
||||
reader could run. Worse, `[ -d "$COORD" ] || exit 0` (F5) made a mailbox root
|
||||
that DOES NOT EXIST byte-identical to one where nobody has pending mail: zero
|
||||
lines on stdout, exit 0, nothing on stderr. `board.sh` consumes this TSV.
|
||||
That is Verifiseringsloven ansikt 4 in the transport itself - a broken query
|
||||
returning a positive-looking null, read as a fact about the world. The
|
||||
contract is now three statuses: 0 = counted, 2 = usage error, 3 = mailbox root
|
||||
absent. **3 rather than 2 on purpose:** 2 already means "you called me wrong"
|
||||
and this means "the world you named is not there" - two different repairs,
|
||||
and a consumer that only ever sees one integer cannot tell them apart. Both
|
||||
nonzero paths print NOTHING on stdout, so neither can be mistaken for a count
|
||||
of zero. What the old "always 0" claim was protecting is kept and made
|
||||
precise: no state OF THE MAILBOX can produce a nonzero exit - not an empty
|
||||
root, not a malformed message, not an unreadable date - so a SessionStart
|
||||
still cannot be failed by mail. An EXISTING but empty root stays a silent,
|
||||
clean 0, and selftest section 36 pins that silence as hard as it pins the
|
||||
failure: a warning on the ordinary path would train every reader to ignore
|
||||
the channel the defect case needs. Section 37 pins the header TEXT itself
|
||||
(the same technique board-selftest uses on the FLY legend) - a doc line
|
||||
nothing tests is a doc line that drifts, which is how this one drifted.
|
||||
|
||||
**Bounded gap, stated rather than closed:** `coord-inbox.sh:57` and
|
||||
`coord-order-inbox.sh:60/64` carry the identical `[ -d "$COORD" ] || exit 0`
|
||||
shape. The denominator was measured, not estimated (`grep -n '|| exit 0'
|
||||
scripts/*.sh`), and they were deliberately left alone: they are the
|
||||
INJECTION path, their output is prose a session reads rather than a TSV a
|
||||
program parses, and their contract really is "never fail a SessionStart".
|
||||
Changing them would change what every session sees, in every repo, and that
|
||||
was not ordered. Naming them here is the point - the next session that greps
|
||||
for this shape finds a decision, not an oversight.
|
||||
|
||||
**Reading is delivering — counting is not.** `coord-inbox.sh` records a
|
||||
broadcast as seen once it has printed it, so it can never be used to survey
|
||||
other repos: doing so would consume each one's backlog silently, and the seen
|
||||
|
|
@ -227,7 +260,7 @@ marketplace plugin. Three components, one boundary:
|
|||
otherwise only the first, mirroring what the real Edit tool does. Getting
|
||||
`replace_all` wrong in either direction is not a hypothetical: a hook that
|
||||
only ever replaced the first occurrence would silently pass a bulk edit that
|
||||
balloons the file, so `state-line-guard-selftest.sh` (21 checks) pins a
|
||||
balloons the file, so `state-line-guard-selftest.sh` pins a
|
||||
fixture where only counting every `replace_all` occurrence produces the
|
||||
correct denial. Anything the hook cannot project with confidence — a
|
||||
missing file, an `old_string` that is not present, fields of the wrong
|
||||
|
|
@ -291,6 +324,37 @@ marketplace plugin. Three components, one boundary:
|
|||
Pinned by state-line-guard-selftest.sh section 9 (`$\`` as the real repro,
|
||||
`$&` as a second sequence proving the fix is general).
|
||||
|
||||
**F13 (2026-08-26): `MAX_LINES` is now overridable by
|
||||
`CLAUDE_STATE_MAX_LINES`, and an UNUSABLE override is refused by name rather
|
||||
than ignored.** As a bare constant the limit forced every boundary fixture to
|
||||
hardcode the same number the code carried - two copies of one policy, both
|
||||
rewritten by hand the last time the operator moved it (60 -> 120). The knob is
|
||||
the same kind `CLAUDE_COORD_DIR` is for the mailbox root. It is **not** a
|
||||
bypass claim: this guard has always been escapable by writing the file another
|
||||
way, exactly as the sibling pathguard is. The one thing it must never do is
|
||||
silently fail to take effect, so an unusable value (`""`, `"0"`, negatives,
|
||||
anything not made of digits) **denies with a message naming the variable**
|
||||
instead of falling back to 120 - a caller who set it and got the default
|
||||
anyway would be reading a limit that was never in force, which is the exact
|
||||
positive-looking null this whole tier of work closes. Empty counts as
|
||||
unusable, not as unset: it is what an expansion of an unset variable produces,
|
||||
and a value was meant. Recovery is one `unset`, and the message says so.
|
||||
Resolution happens AFTER the STATE.md basename gate, so a bad override can
|
||||
never block a write this guard would not have judged anyway.
|
||||
|
||||
**Section 11's first cut was VACUOUSLY GREEN, and that is worth recording
|
||||
rather than quietly fixing.** The ratchet fixture wrote to
|
||||
`f13-ratchet-STATE.md` - a basename that is not `STATE.md`, so the guard
|
||||
allowed the write without measuring anything - and its path variable was not
|
||||
exported when `node` read it, so the fixture file was never created either.
|
||||
Two independent faults, one passing check, testing nothing. It was caught by
|
||||
an `ERR_INVALID_ARG_TYPE` trace in `npm test` output that a green summary
|
||||
line would otherwise have buried. The section now asserts its own ground
|
||||
truth (the oversized file really is 40 lines) before the check that depends
|
||||
on it, and carries the inverse case (growing that same file is still denied),
|
||||
so neither fault can recur silently. Mutation-verified: removing the ratchet
|
||||
now turns the check red, which it could not do before.
|
||||
|
||||
**Since ORDRE 42 (operator, 2026-08-16) it carries a SECOND invariant: the
|
||||
projected content may not claim `status=done` in its board line while the
|
||||
repo holds commits the branch's upstream does not have.** Measured that day:
|
||||
|
|
@ -347,7 +411,7 @@ marketplace plugin. Three components, one boundary:
|
|||
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
|
||||
next-step blocks + board lines, `git status`, and mailbox pending counts, and
|
||||
prints one line per repo. Read-only by construction: it writes to no repo, no
|
||||
STATE.md and no mailbox. Pinned by `board-selftest.sh` (281 checks).
|
||||
STATE.md and no mailbox. Pinned by `board-selftest.sh` (300 checks).
|
||||
|
||||
**It lives here because the mailbox is one of its three inputs, and it carries
|
||||
the same axis distinction the mailbox does.** A pending count means *others
|
||||
|
|
@ -651,6 +715,48 @@ marketplace plugin. Three components, one boundary:
|
|||
find a live one when planted - never behaviourally, because there is no
|
||||
behaviour to pin.**
|
||||
|
||||
**F6+F10 (2026-08-26): two of the board's own readings turned a FAILED
|
||||
measurement into a reassuring value.** `git -C "$d" status --porcelain
|
||||
2>/dev/null | wc -l` yields zero lines whether the tree is CLEAN or git
|
||||
refused to answer at all, so DRT printed **0** - and every reader took that
|
||||
as "nothing uncommitted here" (F6). It is reachable, not theoretical:
|
||||
discovery tests `.git` with `-e` precisely so worktrees are found, and a
|
||||
worktree whose parent checkout was deleted exits **128** here (measured
|
||||
directly before the fixture was written, and the fixture asserts that ground
|
||||
truth itself so a future git that starts answering fails the check rather
|
||||
than silently disarming the section). The exit status is now the
|
||||
discriminator and an unmeasured tree reads **`?`** - the same token
|
||||
`coord-count.sh` already uses for an age it could not compute, for the same
|
||||
fail-safe reason. **The fix is worthless without its two awk consumers, and
|
||||
one of them is the whole point:** `--plan`'s free-capacity test compared
|
||||
`$7 + 0`, and `"?"` coerces to **0** in arithmetic, so an UNMEASURED tree
|
||||
would have been certified as free capacity - the defect reappearing one
|
||||
layer down, wearing the fix as a disguise. It now compares the field as a
|
||||
STRING against exactly `"0"`; only a tree measured clean is clean. Mutation
|
||||
test: restoring the numeric comparison turns exactly that one check red
|
||||
while both controls (clean repo still free, dirty repo still not) stay
|
||||
green. The SUM roll-up sums the same field, where a `?` adds 0 and quietly
|
||||
understates the total, so it now NAMES the repos it could not add -
|
||||
labelled, never silently dropped, the same rule `--plan` already applies to
|
||||
repos with no board line. Every SHELL consumer prints the field with `%s`
|
||||
and was unaffected; that denominator was measured, not assumed.
|
||||
|
||||
**F10 is the same defect at the scan boundary.** `[ -d "$root" ] || continue`
|
||||
skipped a bad root in silence, and a scan that then found nothing exited 0
|
||||
printing nothing - so a typo in `--roots`, a moved home directory and a
|
||||
genuinely empty tree were one single output, with the nevner never reported.
|
||||
Every bad root is now NAMED on stderr; the exit status changes only when NO
|
||||
root was scanned at all (**3**, matching `coord-count.sh`'s F5 fix for the
|
||||
same reason). **A mix still exits 0 and still prints the board** - some repos
|
||||
really were scanned, so the board is real; the reader just also learns that
|
||||
one root contributed nothing because it is absent rather than because it is
|
||||
empty. An EXISTING but empty root stays a silent, clean 0. This REPLACES an
|
||||
older assertion reading `check "missing root is a clean no-op"` - the defect
|
||||
encoded as a passing test, which is why nothing ever caught it.
|
||||
`brief-nightly.sh` needed no change and gains from this: it already treats a
|
||||
nonzero `--brief` as failure and keeps yesterday's briefing, so a mistyped
|
||||
root now preserves the file instead of overwriting it with an empty render.
|
||||
|
||||
**Ordre 20260818T124828Z (2026-08-18): repo discovery had no dotglob, so a
|
||||
dot-prefixed repo (`.profile`, the Forgejo/GitHub org-profile convention)
|
||||
was permanently invisible to the board - not listed, and `--dispatch
|
||||
|
|
@ -923,11 +1029,11 @@ obligations in another repo.
|
|||
- Zero dependencies everywhere: bash + coreutils in the engine, `node:`
|
||||
builtins only in hook and tests.
|
||||
- TDD: no behavior change without a failing selftest check first.
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (230/230),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (281/281),
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (242/242),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (300/300),
|
||||
`bash scripts/route-selftest.sh` must exit 0 (69/69),
|
||||
`bash scripts/orders-selftest.sh` must exit 0 (110/110) and
|
||||
`bash scripts/state-line-guard-selftest.sh` must exit 0 (40/40).
|
||||
`bash scripts/state-line-guard-selftest.sh` must exit 0 (54/54).
|
||||
- English for all code, docs, and commit messages (public repo). Norwegian
|
||||
trigger aliases in the skill description are deliberate.
|
||||
- Conventional Commits: `type(scope): description`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue