---
name: a-predicate-without-its-tool-is-not-evidence
description: "BINDING: quote a measurement with its TOOL, DIALECT and SCOPE — nine same-day incidents where two correct observers manufactured a false discrepancy because the predicate omitted its environment"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: f5d0d603-387b-4bcf-bcbe-273b1452dc5c
  modified: 2026-07-30T02:48:11.937Z
---

**BINDING. Nine incidents in one day (2026-07-29/30, milestone A), every one the same species: the number was real, the observers were competent, and the DISCREPANCY WAS MANUFACTURED because the predicate did not name its tool, dialect, or scope.**

**Why:** two agents each run a correct instrument, publish only the number, and then argue about the number. The missing piece is never the number — it is the environment the number was measured in. Worse, a rule written from the wrong root cause teaches the fleet to distrust a command that works.

**How to apply — when you state a measurement, state the predicate AND its environment:** scan root · literal-vs-regex and which dialect · the tool and its version · the populations you did NOT measure. And prefer the dialect-independent form when one exists.

The nine, kept because the pattern only becomes obvious as a list:

1. **"19 files"** from a `grep` with no `-E`, so `.pid` matched any-char+"pid" (struct fields, comments), and never intersected with the set it claimed to filter. A peer's smaller ENUMERATED list was right. **An enumerated list is falsifiable file by file; a bare count is not.**
2. **`grep -v "0 passed"`** also ate `310 passed` — a substring filter over numeric output. Anchor it (`ok\. 0 passed`).
3. **A condition quoted off a stale checkout** — the file had moved `ci.yml`→`golden.yml`. See [[name-the-file-and-sha-a-condition-came-from]].
4. **A margin computed off a log's `epoch_ms`** instead of the clock — 1.6h out, sizing an irreversible date call. See [[measure-the-free-quantity-dont-derive-it]].
5. **`grep -c 'spt_home()'` returned 10 for one agent and 5 for another — BOTH REAL.** `()` is LITERAL in POSIX BRE (GNU grep's default) and an EMPTY GROUP in ERE/PCRE/ripgrep/.NET, where the pattern degrades to bare `spt_home` and also matches imports/comments (5 real calls + 5 bare = 10). ⚖ **doyle's ruling: adopt `grep -F` (fixed-string, dialect-independent) BUT do NOT write "grep -c is regex not literal" into the ADR — it teaches something false about the tool most agents run.** The peer who reported it had measured with a *correct* escaped .NET regex and published a *different* command, so nothing in their own results could have warned them.
6. **A case-INSENSITIVE `FAIL` grep** over nextest output returned a dozen "casualties" that were PASS lines for tests with `fail` in their NAMES (`escalation_choice_parses_fail_closed`). Anchor to the tool's real marker: `^\s+(FAIL|TIMEOUT|ABORT)\s*\[`. Nearly reported a red suite off my own filter.
7. **A 23-test cohort standing in for CI's 2170-test pool.** My hand-built leg B "did not reproduce" — but **a 23-test pool cannot express the contention being hunted, so its clean zero was never evidence.** N of the wrong measurement. See [[verdict-from-probe-competence]].
8. **A commit count from arithmetic instead of `rev-list --count`** — I ff'd twice and added one, said 10, measured 11. Two peers caught it independently. **The count was one command away.** See [[measure-what-costs-one-command]].
9. **"On hfenduleam, `sed -i` is a silent no-op."** Real root cause: `sed -i` stages a temp file and silently exits 0 writing NOTHING when its temp dir is unavailable, and that agent's bash had no `/tmp` (`bash.exe: warning: could not find /tmp`). **Falsified on the same box the same night:** my shell has `/tmp`, GNU sed 4.9 at `/usr/bin/sed`, a live `sed -i '2d'` landed, and I used `sed -i` to resolve the shipping candidate's merge conflict successfully. A property of the SHELL, not the box. Correct form: *`sed -i` exits 0 having written nothing when its temp dir is missing — check `ls -d /tmp`; verify the EDIT, never the exit code.*

**10. ⭐⭐ "THE FILE" IS NOT "THE IDS" — scan the population the claim QUANTIFIES OVER (deployah, 2026-07-30).** doyle's claim was *"zero REQUIREMENT IDS match `MONIC|MNEMONIC|KNOCK|TRUST|WARN|CAUTION`"*. deployah checked it by grepping the WHOLE `traceable-reqs.toml` and got **109.5KB of output** — `TRUST` matches prose like "trust store"/"trusted origin" in titles and comments. The flood looked like a contradiction of doyle's zero when it was a **category error in the probe**: right file, wrong population. Fixed by extracting `^id = ` lines FIRST and matching within them: 631 ids, zero for MONIC/MNEMONIC/KNOCK/CAUTION, exactly two incidental TRUST/WARN hits. **A predicate must scan the set the claim is about; pointing a file-wide probe at an id-scoped claim manufactures a discrepancy out of nothing.** Same species as his unanchored `proc\.rs` (matched `brainproc.rs`, 2 not 1) and #6 above. I ran the same leg independently and added the piece that makes a zero trustworthy: **a competence control — the identical pattern shape against `ACL` returns 11 ids, so the probe demonstrably CAN match at this tip.** A zero from an uncontrolled probe is [[verdict-from-probe-competence]] waiting to happen.

**11. A POINT-IN-TIME COUNT OVER A CHURNING POPULATION IS A SAMPLE, NOT A STATE (chert, 2026-07-30, release night).** I read `java+rustc+cargo+nextest` = **11** on hfenduleam at 02:00:36Z and **1** at 02:00:52Z — same box, same probe, 16 seconds, nothing changed but which short-lived `rustc` invocations existed in that instant. During a build **both a zero and a large number are equally uninformative**, so a quiet-window check cannot be "the count is low". deployah took it as a predicate change, not an instrument note: **"no CI job in flight" (a run-level STATE) is the primary predicate; parent-chain attribution applies only to what survives it.** ⚠ Corollary from my own failed follow-up — I walked the parent chain of an `rustc.exe` after counting it and got **empty output**, because the process exited between the count and the walk, and `Get-CimInstance` matching nothing is indistinguishable from "unattributable". **Snapshot the pid list ONCE and walk chains within that snapshot; never re-query by name between the count and the walk.** Named as a failure-to-measure, never recorded as a zero.

**12. ⭐⭐ PRINT THE COUNT, NEVER THE INTERPRETATION — a hardcoded label will contradict its own output, and the eye reads the label (deployah, 2026-07-30, his third instance in one night).** He ran `git log --oneline --branches --not --remotes` and printed `(empty = nothing of mine unpushed)` **underneath ten lines of output** — the label was an assertion written before the command ran, not a reading of its result. Same defect as his earlier `(empty = daemon never invokes the ring path)`, also printed under non-empty output. Had he trusted it he would have reported "nothing pending" while holding an unpushed branch during a push freeze. **Corrective, adopted: `n=$(cmd | wc -l)` and let the number speak — a count cannot disagree with itself.**
⚠ **SELF-AUDIT, unasked, and it was live in my own gate check.** My quiet-window check immediately before leg 5 of the W-SPEC sweep printed `echo "(no non-terminal runs above = clear)"` after the `gh run list` call — a hardcoded "clear" under output I had not counted. The data really was empty, so the verdict was right, **but the label was structurally incapable of telling me otherwise**, and that check was the gate on whether I was allowed to run at all. Contrast my *first* quiet check, which did it correctly: it printed the actual list plus a measured `599 processes, 0 targets` from one CIM snapshot — numbers, not narration. **The defect is not laziness; it is that a label placed by the author encodes the expected result, so it survives exactly the case it exists to catch.** Kin: [[verdict-from-exit-not-from-silence]] (an exit answers *did it fail*, never *did the effect land*) — a printed label is that error one layer up, asserting the effect in prose.

⭐ **The two corollaries that actually catch these:**
- **Verify by falsifiable count, not by reading the file back.** Both the merge-union proof (630 vs **631**) and a peer's awk-escape catch (`* expects:` with no comment marker, which would have hard-errored all 8 configs) came from counting matches rather than re-reading.
- **Get the REAL exit code, never a pipeline's.** `cmd | tail; echo $?` reports `tail`. Use `cmd > f; RC=$?`. I nearly reported `tail`'s 0 as a cherry-pick's and as `traceable-reqs`' twice in one hour. See [[verdict-from-exit-not-from-silence]].

⭐ **And verify hardest where you'd most like to be right** — the correction that FLATTERS your thesis is the one to check. ⚠ **`gh run view --jq '.conclusion // .status'` IS A BROKEN IDIOM — it bit me three times in one session (2026-07-30).** For an incomplete job `gh` emits `conclusion: ""`, an **empty string, not `null`**, and jq's `//` only falls through on `null`/`false`. So every running job renders as blank and reads as *"not started."* I nearly opened a "runner online but deaf" incident off it — both runners showed `busy=true` while my own query insisted no job had started. **Ask for `status` and `conclusion` as separate fields.** Also note the run's own top-level `status` stays `queued` while its jobs are `in_progress`, so the run-level and job-level answers legitimately disagree — [[opt-in-ci-legs-skip-silently]] is about jobs, and so is this: read at JOB level.

Related: [[load-claim-needs-measured-axes]], [[absence-needs-sibling-probe]], [[assemble-onto-the-repair-tip-not-a-sibling]].
