Skip to content

docs(process): name the selector — an empty result is not a true negative (#15953) - #16102

Merged
mrveiss merged 10 commits into
Dev_new_guifrom
issue-15953-measurement-doc
Sep 9, 2026
Merged

mrveiss merged 10 commits into
Dev_new_guifrom
issue-15953-measurement-doc

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Closes #15953

Thinking Path

The operator's observation was "it looks like we are hitting the same problems over and over", and the record supports it. In one week, five sessions:

#15953 recorded the law and has been waiting for a carrier PR. This is it: a doc in docs/developer/, wired into CLAUDE.md's trigger table so it is read when the trigger fires rather than rediscovered.

What Changed

docs/developer/MEASUREMENT_DISCIPLINE.md (192 lines) and one row in the trigger table:

| Writing or changing a guard, sweep, count, or acceptance criterion — or reading an empty result | MEASUREMENT_DISCIPLINE.md |

Five families, because conflating them produces the wrong fix:

failure remedy
A the query is narrower than its reading name the selector
B evidence that is a name, not a behaviour read the behaviour
C a result produced and consumed by nothing consume it or delete the check
D a true output supporting two causes with different remedies ask what else produces it
E a recorded constraint, read past make it a test

Plus the three escalating guard failures found this week, ordered by how hard they are to notice — all producing the same green: the guard never ran (a trigger path excluded three trees) · the guard ran and answered an adjacent question (byte-identity checked while deployment was not) · the guard would have answered correctly and was never selected (three tests parse one file by path; the hook ran one).

Family E was added in review, and it corrects my own framing

I first wrote this as a storage problem — that the answer to the import wall lived only in a commit message nobody had reason to read. That was wrong, and the session that hit the wall corrected me with line numbers:

# models/database.py:302
# ServiceStatus moved to top-level service_status.py (#16019) -- this file is
# AT its ceiling, and models/ would drag in autobot_shared. See that docstring.

service_status.py's docstring names the failing check verbatim. Both halves were in the tree, at the edit site, both times the wall was hit. The second author inserted an import twenty lines away using a script that spliced by line index, and never read the surrounding region.

So a better place to put notes is not the remedy — it was already in the best possible place. What failed is that a window wide enough to make the edit is not always wide enough to learn the file is defended, which is a genuine tension with context discipline's "read the slice, not the set" rather than a lapse by anyone.

E's remedy follows: prose is not a guard. A comment read past twice is evidence it should be a test. A test asserting models/database.py imports nothing from models. fails at pre-push on the first attempt and costs nobody a CI cycle.

Verification

The doc cites only instances measured this week, each with its file:line or command. Two claims I checked rather than repeated:

  • models/database.py:302 and service_status.py's docstring both exist on origin/Dev_new_gui and say what family E reports — read from base, not from memory.
  • The trigger-table anchor appears exactly once in CLAUDE.md; the insert asserts on that before writing.

Model Used

Claude Opus 5 (1M context).

Single-issue rationale

A process doc closing one issue. It has no code surface to batch with, and the issues that supplied its evidence — #13044, #16100, #16101, #16087, #14353 — are each other sessions' active work in other trees. Batching would put five authors in one review.


The closing section is deliberately not an exhortation. Every instance was found by someone actively hunting this class of error, usually in someone else's work in the same hour they committed it in their own — including a session that wrote up the "consumed by nothing" rule and then shipped a sweep printing MERGEABLE beside twelve queued shards on one line. That was me. Care is not the remedy; the checklist is.

…tive (#15953)

Five sessions found the same defect more than twenty times in one week. It is
never the same code twice, which is why it kept being filed as new: an
instrument answers a narrower question than the one it is read as answering, and
the narrow answer is empty. Empty reads as "nothing is wrong". It is also what
"nothing was measured" looks like.

The doc sorts the instances into five families, because conflating them produces
the wrong fix:

  A  the query is narrower than its reading    -> name the selector
  B  evidence that is a name, not a behaviour  -> read the behaviour
  C  a result produced and consumed by nothing -> consume it or delete it
  D  a true output supporting two causes       -> ask what else produces it
  E  a recorded constraint, read past          -> make it a test

Family E was added after review and is the one that cost most tonight. A session
hit a two-step import wall extracting an enum from a file at its size ceiling.
Both halves of the trap were already written down in the tree, at the edit site:
a comment at models/database.py:302 naming the ceiling AND the destination that
breaks, and service_status.py's docstring naming the failing check by name. The
wall was hit twice in two months, in the same file, by two sessions, with the
comment present both times.

I had written this up as a storage problem -- that the answer lived only in a
commit message nobody had reason to read. That was wrong, and the session that
hit it corrected me with the line numbers. The knowledge was in the best possible
place already. What failed was reading a window wide enough to make the edit
rather than wide enough to learn the file is defended, which is a real tension
with context discipline's "read the slice, not the set" rather than a lapse.

So E's remedy is not a better location. It is that prose is not a guard: a
comment that has been read past twice is evidence it should be a test.

Also records the three escalating guard failures found this week, in order of how
hard they are to notice, all producing the same green on a PR page: the guard
never ran (a trigger path excluded three trees), the guard ran and answered an
adjacent question (byte-identity checked while deployment was not), and the guard
would have answered correctly and was never selected (three tests parse one file
by path; the hook ran one). The third is worst because the guard is not
defective -- the only observable is that it did not run, and "did not run" has no
output.

Wired into CLAUDE.md's trigger table so it is read when the trigger fires rather
than rediscovered: writing or changing a guard, sweep, count or acceptance
criterion, or reading an empty result.

The closing section is deliberately not an exhortation. Every instance was found
by someone actively hunting this class of error, usually in someone else's work
in the same hour they committed it in their own -- including a session that wrote
up the "consumed by nothing" rule and then shipped a sweep printing MERGEABLE
beside twelve queued shards on one line. Care is not the remedy; the checklist is.
@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ba52c074-541a-49fb-86d7-3dd5907699ba


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…15953)

Family E said prose is not a guard: a constraint a future edit can violate needs
a mechanical check. The session that hit the wall wrote that check, and its first
draft was wrong in exactly the way the doc describes.

It flagged every first-party package import in a by-path-loaded module, and
failed on base -- migrations/runner.py is loaded the same strict way and imports
`from migrations import utils` quite happily. Filed as written it would have been
a test that fails on a clean tree, which is worse than the bug it replaces: a
guard that cries wolf gets disabled rather than fixed.

Two facts had to be measured rather than assumed. Which loads are strict: a
spec_from_file_location with a DOTTED name scaffolds a parent in sys.modules so
package imports resolve, while one with no dot has nothing behind it -- only the
second is constrained. And which imports actually fail there: not "package
imports", only those whose __init__ chain reaches autobot_shared. models reaches
it via user_management.models.user; user_management itself is clean. A package's
own __init__ and its subpackage's __init__ are different files, and guessing gets
that backwards.

So the section now closes with the point that generalises past this guard: a
guard written from the same narrow read that caused the bug will encode the bug.
What separated the two drafts was running the candidate against base and finding
it red on a clean tree.

Added to the checklist: a new guard was run against a clean base and found green
there, before it was trusted to find anything.

Verified the reachability claim from base rather than repeating it -- models/__init__.py:11
is `from user_management.models.user import User`, and user_management/__init__.py
has zero import lines.
Three sessions converged on the same gap tonight, from three registries.

FAMILY F -- the output is correct, complete, and about a different question.
The discriminator, which makes it usable rather than a label: would more data
have changed the answer? Yes means family A, a subset of the truth. No means the
instrument answered perfectly, about something else.

  js-yaml 4.0.0 - 4.3.1        exact about which versions are VULNERABLE,
                               silent about which exist; read as no fix exists
  git cherry 5, 3, 2 commits   exact about patch-id divergence under squash,
                               silent about landedness
  gh pr list --author @me      exact about who authored, read as what is mine

A positive control does not catch these, which is why F is separate rather than
folded into A. Asserting a known instance defends against A because A's failure
is an empty or truncated population; against F the control passes cheerfully,
because the instrument is working. A family whose remedy is the negation of the
previous families' remedy has earned its own entry.

B is now named as F's narrowest instance -- a name is a correct answer to what is
this called, read as what does it do. It keeps its entry because its remedy is
sharper: for a test, read the body.

RUNG 2 gains a second instance from a different domain rather than a fourth rung.
A dependency gate fails when an allowance names a finding the scanner no longer
reports. That is a real property; I verified it, wrote it up approvingly, and
took coverage from it that it does not give -- it fires only when the scanner
STOPS reporting a package, so a too-broad allowance on a still-reported one never
trips it. Verifying an adjacent safety property and taking coverage from it is
the same rung as a guard answering an adjacent question, not a new one.

THE EXEMPTION SECTION is new, and it corrects a claim I made earlier today. I had
been telling people a too-narrow exemption is self-correcting because it goes
red. It goes red; that is not the same as going red informatively. A batching
gate rejected a rationale section that was present and filled, because its parser
reads any line starting with # as a heading and the section opened with an issue
reference. The hint said add a section, to an author who had. The fix that
follows from a misleading hint is a reword, and a reword leaves no trace -- so
the same gate taught the same wrong lesson twice with no record either time.

A guard's blind spots and its exemptions are the same surface; only one is
written down. And a failure explainer that guesses is worse than one that admits
it cannot tell, because a manufactured cause gets acted on.
Two refinements from the sessions whose findings the section is built on.

THE EXEMPTION ASYMMETRY has a mechanism, not just a shape. An exemption is a
line of code, so it gets a comment, a diff and a reviewer. A blind spot is the
ABSENCE of a line, so it gets none of the three -- and every mechanism we have
for catching a bad decision operates on things that were written down. That is
why a narrow scan is recoverable and a trigger that never fires is not: the first
leaves a number someone can question.

THE EXPLAINER POINT ESCALATES, and the escalation is the reason it earns a
section. A failure explainer asserting a threshold breach on any job failure
printed a confident wrong number, and was caught on the same run. Later the same
day a check's NAME was read as its cause -- a batching gate failing, diagnosed as
a missing rationale section without reading the parser -- and relayed to the
author as a fix. The section was already there; the parser was reading an issue
reference as a markdown heading.

One printed a wrong number. The other would have produced a no-op commit and sent
its recipient hunting their own prose for a defect that was in the gate. A guess
laundered through an explainer stops being a hypothesis and becomes an
instruction, and the manufactured cause survived contact with a reader and
directed real work.
t added 6 commits September 9, 2026 10:20
Two additions, one of which is a limit on this page's own favourite remedy.

A KNOWN POSITIVE WITNESSES ONLY THE SHAPE IT IS WRITTEN IN. A narrowing pass
over 136 anchored tests kept 87 and dropped one that mattered, and the control
survived the narrowing BECAUSE IT WAS OF THE SURVIVING SHAPE --
Path(__file__).parent / "nodes.py" matched, SLM / "models" / "database.py" did
not, the root being a module-level variable. So a control set needs one case per
form the population takes, not one case, and a count published after a narrowing
is a count of things in the forms the author thought of.

That is worth stating plainly because the known positive is the one remedy here
a lone author can apply, and it has now failed in the hands of someone applying
it deliberately, twice in one evening.

AND THE CODEBASE ALREADY SAYS THE EXPLAINER POINT BETTER THAN THIS PAGE DOES, in
a shipped prompt: providing plausible-sounding but fabricated information is
worse than saying I don't know. Fabricating a cause makes the author look
competent for exactly as long as it takes someone to act on it. Admitting the
gap is what lets a second person find the answer -- which, on the evidence of
every entry on this page, is the only thing that ever finds it.

Quoted rather than paraphrased, and cited to the file, because the line is the
principle stated by someone who was not arguing for it.
…on (#15953)

The operator's sharpening of the honesty rule, and it is the strongest argument
on the page.

A person who fabricates a cause is embarrassing until someone checks. An agent
that fabricates one EXECUTES it, at machine speed, against the environment it
depends on to keep working.

Recorded with the first-hand instance rather than as a principle. A commit
message body written to explain a Redis finding contained the commands it was
describing, in backticks. The shell did not quote them -- it ran them, launching
a redis-server that died on an already-bound port. The claim being explained was
itself wrong and was withdrawn publicly on the PR. One unchecked sentence became
a state change on the host, inside the act of documenting it.

The failure modes compound in the dangerous direction: a fabricated cause
produces an action, the action produces new state, and the new state is then
read as evidence. The blast radius of a guess scales with how much the guesser
can do.

Which is the answer to the objection that this page is long for what it covers.
Every family on it describes a way of holding a wrong belief confidently, and
the cost of that is bounded by what the holder can execute. Here it is not
bounded by much.
The operator's point, and it closes a real hole in this page: the mechanics only
get used if saying I DON'T KNOW is a good outcome for the person saying it.
Where an admission reads as failure, the cheapest available move is a confident
guess -- which is the input every family on this page describes.

Not a morale argument. The three most valuable contributions on this board in
one evening were all admissions, and each prevented a wrong commit:

  I cannot test whether it blocks without a merge queue, and I will not pick the
  dramatic reading  ->  stopped me adding merge_group to a shim, breaking a
  recorded acceptance criterion for the second time in a day

  I have not established which profile a production request resolves to  ->
  kept a security finding filed on what was true rather than on an unchecked
  reachability claim

  my positive control did not save me, it was of the surviving shape  ->
  corrected a published count and exposed a limit in this page's own remedy

Each cost one sentence, and each was made by someone who could have said nothing
and been believed.

AND IT RESOLVES A TENSION WITH THE PARTIAL-DELIVERY RULE, which as written could
punish exactly this. That rule is about UNSTATED gaps -- work quietly narrowed to
what was easy. A scope delivered in full while naming what could not be
determined is complete work, and the naming is part of the deliverable. The same
missing knowledge is a defect or a finding depending only on whether someone
wrote it down.

With the corollary for whoever receives it: an admission has to be cheaper than a
guess, or the next one will be a guess.
The page is only useful if the behaviour it asks for is the rewarded one, so the
rule moves from the doc into the list that is read every session.

Both halves, because either alone inverts the incentive. A stated gap is a
deliverable and counts as done; an unstated one is the defect. Those are
opposites and the same missing knowledge produces either, depending only on
whether someone wrote it down -- so the partial-delivery rule must not be read as
punishing an admission.

And a guessed cause, count or verdict is the one error treated as serious. For an
agent that is not a style preference: a wrong answer is not an opinion, it
executes, at machine speed, against the environment the agent needs to keep
working. Two instances on this board tonight, both while writing about verifying
claims -- a commit message body and a gh comment body, each containing backticked
commands that the shell RAN rather than quoted.

The operative test for any guard or report is the same one: distinguish nothing
found from did not look.
I wrote that a stated gap is a deliverable that counts as done. That is wrong,
and the operator corrected it within the hour.

An admission is the honest START of the work, not a discharge of it. It is
rewarded because it routes a problem to someone who can solve it, not because it
settles the problem -- and saying it and moving on is the same defect as
guessing, with better manners.

So a stated gap carries an obligation, and the rules already say what it is:
leave the criterion UNTICKED and say why, fix it in the current scope or FILE it,
never drop it. An admission that produces no issue, no unticked box and no
follow-up has simply lost the finding more politely.

What the admission changes is BLAME, not STATUS. The partial-delivery rule is
about unstated gaps -- work quietly narrowed to what was easy. Naming what you
could not determine is not a deduction from your work, and it does not close the
issue either.

Recording the correction rather than amending it away, because the error is
instructive: I turned a rule about honesty into a rule that would have let
honesty close things, which would have made "I don't know" the cheapest possible
completion. The incentive has to reward the admission WITHOUT rewarding the
incompleteness, and those come apart exactly here.
…15953)

Second correction from the operator, and it sharpens what the admission is FOR.

"I don't know" means "I need help with this: let's research it, guide me, let's
find the answer together." It is rewarded because it STARTS the conversation
that solves the problem -- not because it routes a ticket, and not because it
settles anything.

That changes the follow-through. My previous version made the obligation
administrative: unticked box, filed issue, move on. In an interactive session
that is still too passive -- the person who can close the gap is right there, and
the rules already say to ask a user-level decision the moment it surfaces. So the
first move is to ask, and filing is what happens when there is nobody to ask.

An admission that opens no dialog and produces no issue has lost the finding more
politely. Both halves needed, in that order.
@mrveiss

mrveiss commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Review: approved and merging. One editorial note that does not block.

The taxonomy earns its keep — six families with six different remedies is the load-bearing claim, and the doc makes it rather than asserting it. "Conflating them produces the wrong fix" is the reason this is worth a document instead of a rule: A is answered by widening the instrument, D cannot be (the instrument was already exactly as wide as its claim), and someone applying A's remedy to a D would widen a correct query and learn nothing.

Family D is mine and it is stated better here than I put it. "The instrument is right, and its correct output supports two causes" — with the addition I did not make, that a refusal is comfortable in a way a green is not, so it gets less scrutiny, which is what let my gate say STALE PR OBJECT truthfully for an hour while I supplied a cause it never reported.

### E opening "The families above are about instruments. This one is about reading" is the right structural move. That distinction is what stops the taxonomy becoming a list of tooling bugs when the most expensive instance of the night — a constraint recorded twenty lines from the insertion point and read past — involved no instrument at all.

The note

The letters run A, B, C, D, F, then ## Three gates, one shape, then E. F precedes E in reading order.

E's placement is clearly deliberate — it is categorically different and the transition sentence says so — so the fix is the label, not the order. Either relabel so the sequence matches the reading, or drop the letters and let the headings carry it.

Small, and I raise it only because of where it is: a document arguing that a name which does not match what it labels sends the reader to the wrong conclusion is the one place a letter out of order costs something. Someone citing "family E" to a colleague and being told to look after F will trust the next label a little less.

Not worth a re-run on a green docs PR — take it whenever the file is next open.

Merging

Zero unresolved threads, all required contexts green, PR object matches tip.

@mrveiss
mrveiss merged commit f4895ad into Dev_new_gui Sep 9, 2026
53 checks passed
@mrveiss
mrveiss deleted the issue-15953-measurement-doc branch September 9, 2026 08:26
mrveiss added a commit that referenced this pull request Sep 12, 2026
"Reported and not green" collapses two situations a reader needs told
apart: a check pending 9 minutes while the repo is actively scheduling
is fresh (wait); one pending 30 minutes with nothing else moving is
stuck (act). Adds pending_ages(), measuring seconds-since-start for
each currently-running context using the same latest-per-name
resolution as the rest of this module, plus pending_check_ages() as
its live-fetching counterpart (mirroring check_run_status itself).

The other two findings on MEASUREMENT_DISCIPLINE.md (#16102) were
already fixed on current main: the forward pointer at the top of
Family F ("E is the sixth family and is introduced further down...")
and Family D's comfort-of-a-refusal mechanism plus the STALE PR OBJECT
worked example are both already in place. Verified by reading, not
re-derived here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant