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
|
|
@ -5,6 +5,10 @@
|
|||
// ~120-line convention (global CLAUDE.md's Kontinuitets-system section;
|
||||
// raised from ~60 by operator decision 2026-08-14).
|
||||
//
|
||||
// Env: CLAUDE_STATE_MAX_LINES overrides that limit (positive integer). An
|
||||
// UNUSABLE value is refused by name, never silently ignored - see
|
||||
// resolveMaxLines() below for why that direction is the safe one.
|
||||
//
|
||||
// PreToolUse, not PostToolUse: org-ops' work order (20260814T144553Z) asked
|
||||
// for a PostToolUse hook, but PostToolUse fires AFTER the tool already ran
|
||||
// and cannot undo the write (confirmed against the official hooks docs,
|
||||
|
|
@ -112,7 +116,34 @@ import { readFileSync } from 'node:fs';
|
|||
import { basename, dirname } from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
const MAX_LINES = 120;
|
||||
const DEFAULT_MAX_LINES = 120;
|
||||
|
||||
// F13: the limit was a bare constant, so a selftest of the BOUNDARY had to
|
||||
// hardcode the same number the code carries - two copies of one policy, and
|
||||
// every fixture had to be rewritten by hand the last time the operator moved
|
||||
// it (60 -> 120, 2026-08-14). CLAUDE_STATE_MAX_LINES is the same kind of knob
|
||||
// CLAUDE_COORD_DIR is for the mailbox root: it lets a test pin the boundary at
|
||||
// a cheap value, and it lets the operator move the limit without a release.
|
||||
//
|
||||
// It is not a bypass claim. This guard has always been escapable by writing
|
||||
// the file another way (Bash, an editor), exactly as the sibling pathguard is.
|
||||
// The one thing it must never do is silently fail to take effect, which is why
|
||||
// an UNUSABLE value returns null and is refused by name below rather than
|
||||
// falling back to the default: a caller who set the variable and got 120
|
||||
// anyway would be reading a limit that was never in force - the same
|
||||
// positive-looking null this whole class of fix exists to close.
|
||||
//
|
||||
// Refused: "" (a variable expanded from something unset - a value was meant),
|
||||
// "0" and negatives (a limit no write can satisfy), and anything not made of
|
||||
// digits ("abc", "12.5", "1e3"). Unset is NOT unusable; it is the normal case.
|
||||
function resolveMaxLines() {
|
||||
const raw = process.env.CLAUDE_STATE_MAX_LINES;
|
||||
if (raw === undefined) return DEFAULT_MAX_LINES;
|
||||
if (!/^[0-9]+$/.test(raw)) return null;
|
||||
const n = Number(raw);
|
||||
if (!Number.isSafeInteger(n) || n < 1) return null;
|
||||
return n;
|
||||
}
|
||||
|
||||
function allow() {
|
||||
process.exit(0);
|
||||
|
|
@ -150,6 +181,22 @@ if (
|
|||
allow();
|
||||
}
|
||||
|
||||
// Resolved here, AFTER the STATE.md gate above: an unusable override must not
|
||||
// block a Write this guard would never have judged in the first place.
|
||||
const MAX_LINES = resolveMaxLines();
|
||||
if (MAX_LINES === null) {
|
||||
process.stderr.write(
|
||||
`\n[repo-mailbox] STATE LINE GUARD: ${toolName} blocked\n` +
|
||||
` File: ${filePath}\n` +
|
||||
` CLAUDE_STATE_MAX_LINES is set to ${JSON.stringify(process.env.CLAUDE_STATE_MAX_LINES)}, ` +
|
||||
`which is not a positive whole number of lines.\n\n` +
|
||||
`The limit was NOT applied and the write was NOT judged. Set ` +
|
||||
`CLAUDE_STATE_MAX_LINES to a positive integer, or unset it to use the ` +
|
||||
`default of ${DEFAULT_MAX_LINES}.\n`
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
let projected;
|
||||
let currentLines;
|
||||
if (toolName === 'Write') {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue