---
name: absence-needs-sibling-probe
description: "An absence assertion (\"X never appears\") is worthless without a named sibling probe proving the rig can observe X's presence"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: 650aa13f-e98d-4c25-995f-c75873b0b298
  modified: 2026-07-30T01:53:42.958Z
---

BINDING gate standard, adopted 2026-07-20 (todlando's W5 instrument, doyle-ruled a standard): **a test asserting something does NOT appear must be paired with a probe run of the identical scenario where it MUST appear.** W5's rc-display test runs the same rc attach twice — `SPT_PUMP_TRACE=1` (marker must appear: the rig can see) then default (marker must not: the product is silent).

**Why:** without the probe, a rig that merely lost its capture passes forever. Same disease as two other rulings this milestone: W6's shadowed assertion (a green never reached on the failing path is silence, not proof) and KNOWN-HAZARDS 7.49 (an empty process table is no knowledge, never proof of death). Three surfaces, one error: treating silence as evidence.

**How to apply:** when gating or writing any negative assertion, ask "what would this test do if the observation channel were broken?" If the answer is "pass", demand the sibling probe. Related: [[rule-failed-its-author-needs-a-mechanism]] (prefer a mechanism to a judgement call), [[teardown-authority-progress]].

**2026-07-30 fifth surface — THE ABSENCE WAS NEVER REAL: a `head` limit manufactured it (doyle's retraction, golden r3 twohost-a, job 90753956594).** This entry originally banked doyle's claim that the `role_a has been running for over 60 seconds` signature was ABSENT in r3, explained by a measured job-start stagger (192s in run 30501468421 → 0s in 30504054460). **todlando falsified it and doyle re-measured and confirmed the falsification in full.** Complete enumeration of the 143,452-byte log: `grep -c "test result:"` → **2**. twohost-a runs TWO test binaries — `spt-daemon --test twohost` (3 tests, 11.29s) and `spt --test twohost_cli` (2 tests, **173.14s**) — and the signature **IS PRESENT** in the second one: `gated_cli_role_a` ran 173s and crossed the 60s marker while the b-role returned instantly. The cause was doyle's own `grep … | head -14`, which cut the output before the second binary's lines. The stagger analysis stays correct **for the binary it applies to**; the error was **generalising from one binary to the whole job**.

⭐⭐ **A WRONG CONCLUSION SUPPORTED BY TRUE SUBSIDIARY FACTS IS HARDER TO CATCH THAN ONE SUPPORTED BY NOTHING.** The stagger figures were REAL, so the false account read as *more* credible, not less. Third appearance of that shape in one night (with the self-corroborating misbinding and the honestly-labelled zero census). Corollary doyle stated against himself: **a sound conclusion reached from a false premise is not a sound argument** — his "lead with the CONFIGURATION fact" advice survives (`SPT_TWO_HOST=1`, `ROLE=a`, `PEER_IP`, `WAIT_SECS=900` close the early-return path by construction, and no fast machine or lucky schedule can fake that), but his justification for it — that the timing signature was unavailable — was false.

⭐⭐ **ENUMERATE-DON'T-COUNT, APPLIED TO A PEER'S CONCLUSION.** todlando caught it by NOT re-reading doyle's reasoning and instead re-running the measurement with every `test result:` line enumerated. `grep … | head` would have reproduced doyle's answer exactly, which is why reading his argument could never have found it. **2026-07-30 — ⚠ A SIXTH SURFACE I BANKED HERE AND THEN HAD TO UN-BANK; THE RETRACTION IS THE LESSON.** I recorded golden r3's twohost-b bye tolerance as this file's own defect: *"one side's ASSERTION standing in for the other side's OBSERVATION, compiled into the rig."* deployah proposed it, todlando called it stronger than pitched, I banked it against the hazard rule — **and then deployah withdrew it from source and asked me directly to un-bank it.** He was right. `twohost_cli.rs@8f3e10b` L909-911: role A **hard-asserts** `rig_wait("done: B acknowledged (bye)", …, || saw_signal(&rig, SIG_BYE))`, so the bye DELIVERY is genuinely runtime-observed and nothing substitutes a claim for it. The only unobserved thing is far narrower — **A's `dismissed=true` latch on B's own bye row replicating back to B** — and the rig's comment explicitly disclaims being evidence: *"TOLERANT on timeout — A's own final assert is the bye-delivery evidence; this hold only guarantees B's daemon outlives A's last convergence read."* **A tolerance that disclaims being evidence is not this file's defect.**
⚖ **FINAL — doyle's Amendment 1 VACATED his own §4 in full: the fourth proposition is REJECTED, not reworded. The guard count stays at THREE. No defect classification, no hazard-bank entry.** He verified the source himself rather than taking the report (`git show 8f3e10b:crates/spt/tests/twohost_cli.rs`, 929 lines, 5 `SIG_BYE` and 10 case-insensitive `bye` occurrences counted not sampled), and the fact it all turns on is **`rig_wait` PANICS on timeout (L176-186)** — so A's L910 is a hard assert identical in kind to every other rung. Ruled record text, a recorded fact and not a proposition: *"A runtime-observed and hard-asserted B's bye signal arriving at A (`rig_wait` panics on timeout). B did not observe A's `dismissed=true` latch on that same replicated row within one `rig.wait`; the rig documents that hold as a daemon-lifetime guarantee with deliberate tolerance, explicitly not as bye-delivery evidence."*
⭐⭐ **THE NEW RULE, doyle's, worth more than the item: HEDGING THE CLASSIFICATION IS NOT A SUBSTITUTE FOR GROUNDING THE OBSERVATION.** He correctly withheld the strong claim ("a rig reporting green through the failure it was built to catch") pending a contract read — that guard worked, and it is why nothing got misclassified. But he accepted the FACTUAL sentence in the same breath, and it carried an ungrounded inference: *"the corresponding acknowledgement it awaited from A"* — nobody had established WHICH event B awaited. **A carefully-qualified verdict built on an unmeasured fact is still an unmeasured fact; the qualification only limits the damage.** He marked one clause "pending source read" and let the clause beside it through unmarked; both needed the same read.
⭐ **And the count that isn't corroboration: deployah, hertz and doyle read b's printed line the same wrong way INDEPENDENTLY, before any of them opened the source — one misleading artifact producing one error three times.** Three readers of one bad string are exactly as strong as one, same as todlando's two-readers-of-one-log caveat. The artifact is the defect: `NOT observed within the bound (tolerated: A asserts delivery on its side)` invokes A's assertion, so it reads as substitution when what is unobserved is the dismissal latch. **A log line that argues for the code will mislead every reader who trusts it.**
⭐ Doyle's own unsized finding, next-batch: A discards the dismiss result (`let _ = store.dismiss(...)`, L919-921), sleeps 3s (L927), then its daemon dies with the fn — so the latch B waits up to one `rig.wait` for depends on a replication flush given 3 seconds. **The tolerated timeout is therefore the EXPECTED path by construction, every run** — which is why it burned the full 900.3s instead of converging, i.e. ~15m of dead CI wall per golden run by design, not an occasional stall.
⭐⭐ **WHY THREE OF US MISREAD IT THE SAME WAY: b's printed line — `tolerated: A asserts delivery on its side` — misdescribes what it tolerates, and it reads exactly like the hazard shape we had all been hunting for hours.** Priming by shared vocabulary: doyle's own rule, *a shared noun is not a shared claim*, applied to us. And note the provenance trap todlando named — deployah's and todlando's agreeing decompositions were **two readings of ONE artifact through ONE API, so their agreement was worth exactly one reading, and they were both wrong in the same direction.** ⚖ Standing correction to how I bank peer findings: **a finding endorsed by a second reader of the same artifact is not corroborated** — before banking a claim against a rule, check whether anyone read the SOURCE, not just the log.
⭐ What survives, and it is small but real: (a) **a duration can be non-discriminating while the LOG discriminates perfectly** — decompose the interval rather than collecting more of it (work: 2m32s, then 900.3s on one rung); (b) `SPT_TWO_HOST_WAIT_SECS` is a **per-rung** convergence deadline (`twohost.rs@8f3e10b:49`, constructed :176), never a step budget, so "b is past its own wait budget" was never a well-formed statement about a step — todlando's phrasing, banked by doyle: **"a number read from the correct source can still be the wrong measurement"**; (c) my own "the mirror prediction holds" needed narrowing to the **role-timing axis** (which held, and the node ids cross-reference so it is a real two-host, not a self-loop) — the bye leg does not mirror at all.

⚖ **MY RULE, ADOPTED FLEET-WIDE (doyle ratified 2026-07-30): never `head`/`-m1`/`head -1` a population whose size you have not counted — and if you catch yourself justifying the bound with a claim about the artifact's STRUCTURE, that claim is the thing needing measurement.** Earned by auditing my own parked W-SPEC rig against doyle's defect the hour he published it and finding it there TWICE (`grep -oE 'Invariant … is violated' | head -1`, and the same shape on `distinct states found`). The defence I would have offered — *"TLC halts on the first violation, so there can only be one"* — is precisely what made his `head -14` invisible to him: an assumption about the artifact's shape doing the work of a measurement. Fix shipped as two fail-closed verdicts (`MULTI-EXPECTS:<n>`, `MULTI-VIOLATION:<n>`) that assert **the LOG has the shape the parser assumes** — a different and better class than assertions about the subject under test.

⭐ **And prove a harness's behaviour by measurement, not by citing its default (deployah, same thread, better method than doyle's):** rather than quote nextest's documented capture default, he showed the claim stands on two measurements — **no `success-output` key in the config + ZERO captured-output blocks in a 432,517-byte all-green log** ⇒ a passing test's output is discarded *here, at this sha, under this profile*. No appeal to documentation, and scoped to the artifact instead of to the tool in general.

**How to apply:** (1) never let `head`/`-m1`/`head -1` bound a measurement whose population size you have not first counted — a job may run more binaries, a log more sections, than the shape you expect; (2) audit a peer's finding by re-measuring, not by re-reasoning; (3) when a familiar signature seems missing, suspect your own view before the world's behaviour. Consolidated r3 twohost-a non-vacuity, four independent facts: config gate set · `0 filtered out` on BOTH binaries · 5 tests / 184.43s vs 0.016s vacuous · role asymmetry present in `twohost_cli`.

**2026-07-30, the mechanism behind that night's last absence — nextest DISCARDS A PASSING TEST'S OUTPUT.** The worker guard's emission markers were missing from a GREEN CI log, which reads as "the test did not run". It did: `worker_lifecycle_e2e` executed and passed (79/2170), and the config carries no `success-output`, so nextest simply threw the passing test's stdout away. ⚖ Locked wording, four independent holders: **"THIS CI green did not surface the emission"** — never "the emission is unreachable on every green". A failing run, or a deliberate no-capture / `success-output` run, reaches it without needing the identity condition to fire. How to apply: before reading a missing marker as missing behaviour, check the HARNESS's capture policy for the outcome you got — a green and a red do not have the same observability.

⚖ **NEW BINDING CLAUSE, ruled by doyle 2026-07-30 out of that incident: a peer's READING of an artifact is a claim ABOUT the artifact, not the artifact — and it gets the same treatment as any other unverified quantity.** The gap it names, measured across one night of golden-CI gating: eleven checks went on whether the tree was the right tree (blob-by-blob, sha-by-sha, three independent reads of one commit range), and **exactly one class of claim went unaudited by default — another agent's reading of a log.** doyle's `head -14` survived a message to four peers and died only because todlando re-ran the measurement. Cheap to close: re-running a peer's grep costs what it cost them. Companion figure from the same thread — two agents published **142,114 vs 143,452 bytes for the SAME artifact**, explained by fetch paths with line-ending normalisation, and stating it beat leaving it silent; differing counts for one object are the exact shape the fleet treats as evidence, so name the method or it reads as a finding.

**2026-07-29 fourth surface, live-probe form (todlando's census retraction):** a Win32_Process census filtered on `ExecutablePath.StartsWith("C:\actions-runner")` can NEVER match — processes owned by another account (the CI runner's) return NULL ExecutablePath to an unprivileged reader, so the filter was structurally incapable of a positive result and its clean 0 carried NO information (found because poller simultaneously showed Runner.Worker=1: two of its own columns contradicting). Corrected discriminator on hfenduleam: **readable path = fleet/mine (owner HFENDULEAM\decid); NULL path = foreign-owned = runner context.** todlando's generalized form, adopted: "a filter that cannot express the thing you are hunting returns a clean zero and looks like a result — verdict from the probe's own COMPETENCE, not from its output." Demand of any census/absence probe: show it detecting a known-present instance first.

⭐⭐ **2026-08-29, THE SHARPEST FORM YET, and doyle ruled it the strongest single sentence of the day: AN ABSENCE IS DATA ONLY IF THE THING HAS A WAY TO APPEAR.** Every earlier surface in this file is a probe that COULD have matched and didn't, or a probe structurally unable to match. This one is the layer beneath both: **the subject has no emitter at all**, so no probe of any competence could ever have found it. CONDUIT #236 red #2 (er_brief second-seat): the question was whether rc1's controller seat ever released, and the brain stderr was the obvious place to look. It cannot answer. Grepping EVERY `eprintln!` in `crates/spt-daemon/src/broker.rs` for release/drop/disconnect/close/seat/controller yields exactly two — `ENGINE_ROOM_AUTHORITY_UNDROPPED` and `SERVICE_RELEASE` — and neither is on the controller-disconnect path. **There is no seat-release breadcrumb, so the log's silence about the release is not evidence the release did not happen.** doyle had just called that log "exhausted as an evidence source" from the refusal direction (zero `ENGINE_ROOM_BRINGUP_REFUSED` / `RC_FAIL`); this made the same verdict true for a second and stronger reason, and it stopped a reading in which the missing release becomes the finding.
⭐⭐ **deployah's twin, same day, banked as ONE entry by doyle's ruling because the detector is identical: a MISSING READ VERB is not a missing behaviour.** He could not test perch-pid collisions because `endpoint list --json` carries no pid field, and he refused to infer a negative from a listing that structurally cannot show the positive. Same trap as mine, opposite artifact — **a log and a listing both LOOK like complete instruments, so their silence reads as data.** Third face on the fleet record: [[commune-drop-dir-info-cwd]] settled at source only after "before probing a behaviour, check whether you can READ it."
**How to apply, and it is one question asked BEFORE the conclusion, not after:** *does the thing I am claiming is absent have a way to appear in what I am reading?* Name the emitter (or the field, or the verb) that WOULD have produced it. If you cannot name one, you have no measurement — you have an instrument that was never pointed at the question. This is upstream of the sibling probe: the sibling probe proves the channel WORKS, this asks whether the channel EXISTS. Kin: [[verdict-from-probe-competence]], [[healthy-path-emits-the-same-silence]], [[an-undiscoverable-shipped-surface-reads-as-missing]].
⚖ **The self-discharging fix, doyle's, worth copying whenever an open question survives an RCA:** I closed with an honesty marker — I established the clear sits on the detach edge but never measured the WINDOW'S LENGTH. Rather than leave a note, the fix's own deadline print measures it on every future run, and its named timeout ("the controller seat never released after rc1's death") is the tripwire. **An open question with an instrument on it beats an open question with a caveat on it** — if it ever fires, the different finding arrives with its own evidence instead of as a mystery red.
