Skip to content

docs(measurement): record why the host lock, not an announce protocol - #2175

Merged
justinchuby merged 1 commit into
mainfrom
squad/leon-lock-rationale
Aug 26, 2026
Merged

justinchuby merged 1 commit into
mainfrom
squad/leon-lock-rationale

Conversation

@justinchuby

Copy link
Copy Markdown
Owner

Documentation only. Records, next to the rule it justifies, why saturating benchmarks hold the host lock instead of coordinating by announcement.

The lock has been mandatory since #1806 (73c76458c) and .github/skills/measurement-discipline/SKILL.md already carries the rule and the mechanics. What it does not carry is the argument, which lives in issue threads and agent messages — so it gets re-litigated roughly once a week, always from scratch, usually by someone who has just had a good day with announce-before/announce-after. That is a reasonable position to arrive at from one day of evidence, which is exactly why the counter-evidence should be in the repo rather than in a scrollback.

The three arguments, each from a measured failure

Announcement is pairwise; the host is not. On 2026-08-25 one agent correctly yielded the box to a second, and a third then negotiated with the first for a host that had already been given away. Nobody defected and everybody was polite. Pairwise etiquette has nowhere to put a third party; a lock has one holder and every non-holder reads the same answer.

An announcement describes an edge; a lock covers the interval. Both false "host free" claims that day were state assertions that outlived their measurement — one sent from a reading that was 74 minutes stale, 14 minutes after the hung process it missed had started. Three test processes ran 75/61/51 minutes against a ~7-minute baseline and were found only because somebody went looking. One false claim came from the agent who proposed the announce discipline and one from the agent policing it, which is the tell that this is a property of the protocol and not of carelessness. It is the same defect as ps-based liveness one layer up, and the reason the outer harness holds the lock rather than each bench child.

A per-run efficiency guard is self-protective, not preventive. Per-run rusage (utime+stime)/wall is an excellent instrument — it took an A/A null from 52% to 0.04–0.56% on this box — and it belongs in every harness. But it tells you when somebody contaminated your run and says nothing about you contaminating theirs, so it does not compose across agents: if everyone adopts it and nobody locks, every run is correctly labelled and half are discarded. It is also blind to SMT-sibling contention and to steady external load, both of which hold efficiency near 1.0 while moving the number.

The resulting framing, which is the part worth remembering: the lock decides whether you may start; the guard decides whether to believe the reps you got. Neither substitutes for the other, and the guards are supplementary — a clean efficiency trace with no lock is not a defensible measurement.

Also written down: /proc/loadavg is the wrong instrument for admission control in both directions. A deliberately-bounded 4-of-32-CPU protocol shows runnable ≈4–5 and trips a -le 3 gate while being a good citizen; a single-threaded 100% CPU hog shows ≈1 and passes.

Validation

Documentation only, no behaviour change. scripts/ort_ab/test_gate_conformance.py 97/97 green; nothing in scripts/ or .github/workflows/ reads this file, so there is no conformance cell to update.

No --admin, no ruleset bypass; merging on required checks only via scripts/merge_when_green.sh.

The lock has been mandatory since #1806 (`73c76458c`), but the *argument*
for it lived in issue threads and agent messages, so it gets re-litigated
roughly once a week -- usually by someone who has just had a good day with
announce-before/announce-after, and always from scratch. Writing it down
next to the rule makes the next round short.

Three arguments, each from a measured failure rather than a preference:

  * Announcement is pairwise and the host is not. On 2026-08-25 one agent
    correctly yielded the box to a second and a third then negotiated with
    the first for a host already given away. Nobody defected; the protocol
    simply has nowhere to put a third party.

  * An announcement describes an edge, a lock covers the interval. Both
    false "host free" claims that day were assertions that outlived their
    measurement -- one from a reading 74 minutes stale, sent 14 minutes
    after the hung process it missed had started. One came from the agent
    who proposed the discipline and one from the agent policing it, which
    is the tell that it is not carelessness. Same defect as `ps`-based
    liveness, one layer up, and the reason the *outer harness* holds the
    lock rather than each bench child.

  * A per-run efficiency guard is self-protective, not preventive. It is an
    excellent instrument -- it took an A/A null from 52% to 0.04-0.56% here
    -- and it tells you when somebody contaminated *your* run. It says
    nothing about you contaminating *theirs*, so it does not compose across
    agents, and it is blind to SMT-sibling contention and steady external
    load, both of which hold efficiency near 1.0 while moving the number.

So the framing is: the lock decides whether you may **start**, the guard
decides whether to **believe** the reps you got, and the guards are
supplementary -- a clean efficiency trace with no lock is not a defensible
measurement. Also records why `/proc/loadavg` is the wrong instrument for
admission control in either direction: a bounded 4-of-32-CPU protocol trips
a `-le 3` gate while being a good citizen, and a single-threaded 100% hog
passes it.

Documentation only. No behaviour change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 26, 2026
…2179)

`merge_when_green.sh` is the gate every pull request in this lane goes
through:
it waits for the required contexts named by the repository ruleset, and
merges
only when all of them are `SUCCESS`. That is one terminal state, and it
is not
enough.

## The failure

A docs-only pull request never reaches it. The `Detect change scope` job
skips
the heavy lanes when the diff touches no code, so both required contexts
conclude **SKIPPED**, `mergeStateStatus` is `UNSTABLE`, and the watcher
polls
until it times out at exit 3. The pull request is mergeable, correct,
and
reviewable, and the tool will never merge it.

PR #2175 is sitting on exactly this right now.

This is the failure mode the script exists to prevent, pointed the other
way.
A guard with no recovery path does not stop its operator; it routes them
around
it — and the way around this one is `gh pr merge --admin`, which is the
single
thing the standing policy forbids. A tool that can only be obeyed by
being
bypassed has already lost.

## The change

SKIPPED becomes a third outcome, rather than being folded into either of
the
other two.

| condition | result |
|---|---|
| all required SUCCESS | merge (unchanged) |
| any required FAILURE | exit 2 (unchanged) |
| some required SKIPPED, rest SUCCESS | **exit 7**, no merge, names the
contexts |
| same, with `--allow-skipped`, state CLEAN/UNSTABLE/HAS_HOOKS | merge |
| same, with `--allow-skipped`, state BLOCKED/DIRTY/BEHIND/DRAFT | exit
6, still refuses |
| same, with `--allow-skipped`, state UNKNOWN or absent | keeps polling,
then exit 3 |
| a skip alongside anything absent/queued/failed | unchanged refusal |
| a skip alongside a failure **under the same name** | exit 2 |

Exit 7's message names the skipped contexts, points at the flag, and
says in
the same breath **not** to reach for `--admin`.

`--allow-skipped` accepts skips and only skips. The skipped set must be
*exactly* the not-green set, so one skipped lane cannot excuse an
absent,
queued or failed sibling — and, per review, that is enforced per **run**
and
not per folded row, so a required name that skipped *once* and failed
*once*
is a failure no matter which order GitHub lists them in.

And even with the flag, GitHub's own `mergeStateStatus` must be one of
the
states that accept a merge. That is an **allowlist**: refusing only
`BLOCKED`
would mean `UNKNOWN` — GitHub saying it has not computed mergeability
yet —
counted as consent, as would the field vanishing in an API change.
Not-yet-known
is a third outcome there too, and it waits. The caller may decide that a
skip is
acceptable for their diff; they may not overrule the repository, and
they may
not accept on its behalf before it has spoken.

## Why a flag, and not just treating SKIPPED as green

Because whether a skip is acceptable is a property of the diff, not of
the
check. `Detect change scope` skipping the Rust lanes is correct
information
about a docs change and a **serious** signal about a code one — a
misconfigured path filter that silently stops testing the thing you
changed
looks identical from here. The tool cannot tell those apart; the person
who
wrote the diff can. `--allow-skipped` is that person asserting it, at
the call
site, where the evidence is.

Folding SKIPPED into SUCCESS would make every future path-filter
regression
merge silently. That is a worse trade than one flag.

## Testing

31 new cells (41 → **72**, all passing). Sixteen came with the original
push,
of which four are negative controls:
absent, queued, failed and `BLOCKED` inputs each still refuse *with the
flag
set*. Two more pin that the flag does not alter the all-green path — it
must
not turn a green merge into a reported skip.

Ten mutants, **eight caught**. **The two survivors are equivalent by
construction** and are declared so rather than quietly dropped:
`green + nskipped == want` and `notgreen == skipped` are the same
predicate
while the upstream `rows != want` guard admits exactly one verdict row
per
required name, so no input can separate them. Both clauses are kept —
the
guard is what makes them equivalent and is not a law of the file — and
the
comment says all of this out loud instead of letting the pair be
mistaken for
coverage.

`bash -n` and `shellcheck` clean on both files.

## Follow-up

Once this lands, #2175 (`measurement-discipline`: why a lock beats an
announce
protocol) merges with `--allow-skipped` instead of timing out.


## Review round

An adversarial review found **two MUSTs, both reproduced by execution**,
and
both are fixed in `2857220ac`:

1. **A skipped run masked a failed one under the same name.** The
pessimistic
fold reported the array-order-first bad entry, which was free while the
only
   question was green-or-not and stopped being free once SKIPPED became
   mergeable. Worse: with *no* flag that input exited **7** and advised
`--allow-skipped`, so the gate was routing its operator into merging
over a
failure. Every pre-existing negative control gave its bad conclusion its
own
required name, so none of them ever reached the fold — they asserted the
   verdict, not the mechanism.
2. **The merge-state guard was a denylist that failed open** on
`UNKNOWN`,
`DIRTY`, `BEHIND` and a missing field. Now an allowlist with a distinct
   not-yet-known outcome.

Both are pinned by new mutants (10 total, 8 caught) and by cells that
fail if
either fix is reverted.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby merged commit bacbef7 into main Aug 26, 2026
12 of 13 checks passed
@justinchuby
justinchuby deleted the squad/leon-lock-rationale branch August 26, 2026 06:35
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