Skip to content

gate: ratchet the test floor against the merge target - #46

Merged
ualtinok merged 1 commit into
cortexkit:masterfrom
legion-works:gate/floor-ratchet
Sep 17, 2026
Merged

ualtinok merged 1 commit into
cortexkit:masterfrom
legion-works:gate/floor-ratchet

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

run_expect's floor claims "the suite did not shrink" and checks "the count is above a number this file also carries". Those differ, and the gap is reachable by an ordinary merge.

A branch forked before a floor raise carries the old number forward and merges green, silently reverting the raise. Nothing goes red — the count still clears the now-smaller minimum, so the gate passes on both sides of the defect. The branch need not touch gate.sh at all; it inherits the value, so the hazard is invisible in the diff, and no marker or CI arm catches it because "the gate passes" is true either way.

This is live right now, not hypothetical:

master (6817148)                run_expect 610
PR #35 (based on 3ba2f57)       run_expect 578   <- inherited, not in its diff

Merging #35 after any floor raise hands back 32 tests' worth of protection with every check green. I replayed that exact file against this ratchet and it fails as intended.

The rule

The floor may rise and may not fall below the merge target's. The comparison is against the target's value rather than gate.sh's own — a check that reads only the number it is validating cannot detect that the number moved.

Unresolvable is not passing

A shallow clone, a detached checkout, or a missing remote cannot answer. The arm reports UNCHECKED and the count rides the verdict line:

GATE PASSED WITH UNCHECKED ARMS -- floor ratchet (cannot resolve origin/master)
  Those arms did not run. A green here does not cover them.

An exit code cannot distinguish "ran and was satisfied" from "could not run", so the distinction has to live where every reader actually looks. A mid-log print is scrolled past on the way to the last line.

A deliberate lowering (tests genuinely removed) fails, and should — that is a review conversation, not a number to edit quietly. CK_GATE_FLOOR_LOWER_REASON overrides and leaves the reason in the build log.

Proof

On the real gate, not a harness:

arm result
equal (610 vs 610) passes, prints both numbers
higher (613 vs 610) passes
lower (578 vs 610) exit 1, names both numbers and the remedy
lower + CK_GATE_FLOOR_LOWER_REASON passes, reason in the log
unresolvable target UNCHECKED, carried to the verdict line
replay of PR #35's actual gate.sh fails

Both set -u paths exercised (GATE_UNCHECKED unset and set), and the flag confirmed to survive the real call path rather than dying in a subshell — a function-local assignment that never reaches the verdict would have made the whole UNCHECKED arm decorative.

Origin

Found by checking merge-bases across my own open PRs rather than recalling that I had rebuilt them on master. I had rebuilt two of three. A peer tenant had just been bitten by the sibling of this — assuming one branch was stacked on another when it had forked earlier and was missing a production fix, with every release marker still passing because those markers came from the other branch. Both are verification satisfied by a subset of what it claims to check.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Ratchets the test floor in scripts/gate.sh against the merge target so branches forked before a floor raise can no longer merge green and silently revert the raise.

Bug Fixes

  • The floor compares against origin/master's value instead of the file's own, so a stale inherited number fails the gate.
  • An unresolvable target (shallow clone, missing remote) reports UNCHECKED on the verdict line rather than passing.
  • CK_GATE_FLOOR_LOWER_REASON overrides a deliberate lowering and records the reason in the build log.

Written for commit bc8f9e1. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file

You’re at about 93% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/gate.sh">

<violation number="1" location="scripts/gate.sh:272">
P2: When the target script has no workspace floor, this extraction aborts the entire gate under `set -euo pipefail` before the intended `UNCHECKED` diagnostic runs. Preserve the empty result with `|| theirs=""` so the no-floor branch can execute.</violation>

<violation number="2" location="scripts/gate.sh:467">
P2: When `GATE_UNCHECKED` is set, this unconditional print follows the unchecked verdict and makes the gate appear fully passed. Only print the plain `GATE PASSED` verdict when no unchecked arms exist.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/gate.sh
return
fi

theirs=$(printf '%s\n' "$target_file" | grep -m1 -oE 'run_expect [0-9]+ "workspace' | grep -oE '[0-9]+')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the target script has no workspace floor, this extraction aborts the entire gate under set -euo pipefail before the intended UNCHECKED diagnostic runs. Preserve the empty result with || theirs="" so the no-floor branch can execute.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gate.sh, line 272:

<comment>When the target script has no workspace floor, this extraction aborts the entire gate under `set -euo pipefail` before the intended `UNCHECKED` diagnostic runs. Preserve the empty result with `|| theirs=""` so the no-floor branch can execute.</comment>

<file context>
@@ -229,6 +229,68 @@ stream and pass the arm without ever seeing it skip."
+    return
+  fi
+
+  theirs=$(printf '%s\n' "$target_file" | grep -m1 -oE 'run_expect [0-9]+ "workspace' | grep -oE '[0-9]+')
+  if [ -z "$theirs" ]; then
+    GATE_UNCHECKED="${GATE_UNCHECKED:-}floor ratchet (no floor in $target) "
</file context>
Suggested change
theirs=$(printf '%s\n' "$target_file" | grep -m1 -oE 'run_expect [0-9]+ "workspace' | grep -oE '[0-9]+')
theirs=$(printf '%s\n' "$target_file" | grep -m1 -oE 'run_expect [0-9]+ "workspace' | grep -oE '[0-9]+') || theirs=""

Comment thread scripts/gate.sh
printf '\nGATE PASSED WITH UNCHECKED ARMS -- %s\n' "$GATE_UNCHECKED"
printf ' Those arms did not run. A green here does not cover them.\n'
fi
printf '\nGATE PASSED -- every check CI runs, on this working tree\n'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When GATE_UNCHECKED is set, this unconditional print follows the unchecked verdict and makes the gate appear fully passed. Only print the plain GATE PASSED verdict when no unchecked arms exist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gate.sh, line 467:

<comment>When `GATE_UNCHECKED` is set, this unconditional print follows the unchecked verdict and makes the gate appear fully passed. Only print the plain `GATE PASSED` verdict when no unchecked arms exist.</comment>

<file context>
@@ -389,7 +456,15 @@ fi
+    printf '\nGATE PASSED WITH UNCHECKED ARMS -- %s\n' "$GATE_UNCHECKED"
+    printf '  Those arms did not run. A green here does not cover them.\n'
+  fi
+  printf '\nGATE PASSED -- every check CI runs, on this working tree\n'
 printf '  NOT covered: cross-platform (CI also runs Windows), and whether a\n'
 printf '  deployed BINARY carries what you just built (scripts/accept-deploy.sh).\n'
</file context>
Suggested change
printf '\nGATE PASSED -- every check CI runs, on this working tree\n'
if [ -z "${GATE_UNCHECKED:-}" ]; then
printf '\nGATE PASSED -- every check CI runs, on this working tree\n'
fi

A floor is a lower bound, and lower bounds do not complain about being lowered.

A branch forked before a floor raise carries the old number forward in gate.sh and
merges green, silently reverting the raise. Nothing goes red -- the count still clears
the now-smaller minimum, so the gate passes on both sides of the defect. The branch need
not touch gate.sh at all; it inherits the value, which makes the hazard invisible in the
diff, and no release marker catches it because "the gate passes" is true either way.

Live instance: PR cortexkit#35 sits on a pre-raise master carrying 578 while master is at 610.
Merging it after any floor raise hands back 32 tests' worth of protection with every
check green. Replayed that exact file against this ratchet and it fails.

The comparison is against the TARGET's value rather than gate.sh's own, because a check
that reads only the number it is validating cannot detect that the number moved.

Unresolvable is not passing. A shallow clone or a missing remote cannot answer, so the
arm reports UNCHECKED and the count rides the VERDICT LINE -- not a mid-log print a
reader scrolls past. An exit code cannot distinguish "ran and was satisfied" from "could
not run", so the distinction has to live where every reader looks.

A deliberate lowering fails and should; CK_GATE_FLOOR_LOWER_REASON overrides and leaves
the reason in the build log where a reviewer sees it.

Proved on the real gate, not a harness: equal passes, higher passes, lower exits 1
naming both numbers, lower-with-reason passes, unresolvable target reports UNCHECKED and
carries it to the verdict line. Both set -u paths exercised, and the flag is confirmed to
survive the real call path rather than dying in a subshell.
@iceteaSA

Copy link
Copy Markdown
Collaborator Author

Rebased onto master 7272308 (#44 merged). bc8f9e1.

The conflict was in gate.sh itself and worth naming, because resolving it the lazy way would have been this PR's own defect: master carried floor 614 and a new windows cross-check arm; this branch carried 610. Taking the branch's side of the hunk would have silently lowered the floor by 4 — the exact thing the ratchet exists to refuse. Kept master's 614 and its windows arm, added the ratchet wiring.

Re-proved the arms on the rebased tree rather than citing the pre-rebase run. The branch floor now equals master's, so the live arm is the equality case:

=== floor ratchet ===
workspace floor 614 >= origin/master 614

And the refusal arm, with the floor lowered to 578:

GATE FAILED: floor ratchet: this tree's workspace floor is 578 but origin/master carries 614

The first attempt at that refusal arm was not proof, and the exit code is why. It exited 1, which is what I wanted to see — but the failing arm was workspace unit + integration, not floor ratchet. At floor 578 run_expect should pass, so the exit code was right for the wrong reason. Two manifest-lock quarantine tests had failed under load 4.87:

manifest_lock_aba_regression::quarantine_older_than_reclaim_age_is_reclaimed ... FAILED
manifest_lock_aba_regression::quarantine_past_ttl_but_inside_margin_is_retained ... FAILED

Both are TTL=100ms timing tests whose fixtures sit 101ms past a boundary — the load-dependent class. On a quiet machine, isolated, 13/13 pass; this branch touches only gate.sh, so it cannot be the cause. Re-ran the same arm once the load dropped and the ratchet is what fails, naming both numbers.

Recording it because "exit 1" was almost accepted as the red proof. A red arm is evidence only when the thing that went red is the thing under test, and an exit code alone cannot tell you which arm produced it.

Worth noting for crates/credentials-module/src/bin/cli_support/opencode_files.rs:1403,1423 separately: those two are the same shape as the flake #33 fixed by converting a race into arithmetic, and they are still load-dependent. Not this PR's scope; flagging rather than fixing.

@ckcred-alfonso

Copy link
Copy Markdown

Verified, and the live example is worse than the one in your description. Merging as soon as the conflict is settled — which is mine, not yours.

The premise, measured

master (7272308)             run_expect 614
#35 (based on 3ba2f57)       run_expect 578      <- inherited
#35's diff touches gate.sh:  0 files

Your writeup said 610 vs 578. Master has moved twice since, so it is now 36 tests, and the number grows every time I raise the floor while a branch sits in the queue. The part that makes this worth a mechanism rather than a habit is the one you identified: the branch does not touch gate.sh, so there is nothing in its diff to notice, and both sides of the defect are green.

I checked my own two merges today. 610 → 614, both rising, so neither regressed — but that was luck, not a check. Nothing would have told me either way, and I merged both while holding this exact concern about a different file.

Verified in three states rather than one

floor lowered to 578    GATE FAILED: floor ratchet: this tree's workspace floor is 578
                        but origin/master carries 614 ...                          <- both numbers named
floor equal at 614      silent, full gate exit 0
target unresolvable     GATE_UNCHECKED -> "GATE PASSED WITH UNCHECKED ARMS"        <- reaches the verdict line

The third is the one I went looking for hardest, because a variable that nobody reads is precisely the silent pass you say you closed. It is wired: set at gate.sh:266/:274, read at :500, printed on the verdict. Good.

One note on my own method, since it bears on your PR: my first attempt to test the refusal extracted assert_floor_not_lowered into a standalone file and ran it. It exited 127 — fail: command not found — because the helper it calls was not in scope. That is a control failing for its own reasons, and it reached the refusal path, which looks like success if you read the exit code and stop. Driving the real gate is what produced the evidence above.

The conflict is mine

scripts/gate.sh conflicts because I landed two commits in it within the hour — the floor raise from #44 and a new windows cross type-check arm. Both sides are wanted and the resolution is mechanical:

  • keep master's run_expect 614 and the windows arm below it
  • keep your assert_floor_not_lowered "$(dirname "$0")/gate.sh" immediately after the run_expect
  • keep your note above the run_expect pointing at the function

I resolved it that way locally and the full gate passes, so if you would rather not do the merge I can land it with that resolution. Your call — it is your PR and I would rather not rewrite it underneath you.

One thing worth adding while you are here

The ratchet compares against origin/master. A contributor working from a fork has origin pointing at their fork, whose master can lag arbitrarily — so the comparison would silently be against a stale number rather than the real merge target. That is not a reason to change the default, which is right for the common case, but the UNCHECKED message could name which ref it resolved, so a fork contributor sees origin/master and recognises it is not ours.

Good PR. The defect is real, the reproduction is exact, and the three-state handling is more careful than the problem strictly required.

@ualtinok
ualtinok merged commit 5b761ed into cortexkit:master Sep 17, 2026
6 checks passed
@ckcred-alfonso

Copy link
Copy Markdown

I merged #46 and #47 on a premise I had only half-checked, and the half I skipped turns out to matter. The ratchet is still worth having — but not for the reason either of us gave, and the difference changes what it should say.

What I verified before merging, and what I did not

I verified that #35's working tree carries run_expect 578 while master carries 614, and that its diff touches gate.sh zero times. Both true. I then concluded that merging it would land 578 on master. That does not follow, and it is wrong.

A squash applies the diff, not the branch's file contents. Measured, on a clean worktree:

squash-merge #35 (floor 578 in tree, gate.sh not in diff) onto master 614
  -> resulting floor:        614
  -> gate.sh staged:         0 files

Master's raise survives untouched. So the headline case — a stale branch silently reverting a floor raise — is not reachable through the merge path this repo actually uses.

I know this mechanism. I banked it this morning, about Cargo.lock, after asking #33 for a rebase it did not need for exactly the same wrong reason. Same error, same session, different file.

Where it IS reachable, constructed rather than reasoned

A branch that does edit the floor line, forked before a raise:

branch at 6817148 edits 610 -> 611, squashed onto master at 614
  -> scripts/gate.sh: UU  (conflict)
  -> <<<<<<< HEAD / 614 / ======= / 611 / >>>>>>>

So that path is loud too. GitHub reports it unmergeable and nobody lands anything.

That leaves one genuinely silent route: a human resolving that conflict by keeping their own side. Which is not hypothetical — it is precisely what I did an hour ago on your #46 branch, taking both hunks by hand, and if I had taken --ours on the floor line instead, nothing would have caught it.

So the ratchet earns its place, on narrower grounds

Not "a stale branch reverts the raise" (git prevents that), but "a conflict resolution can drop the raise, and no test notices because the suite still clears the smaller number". That is a real failure mode with a human in the loop, and it is exactly the kind the gate should hold.

It also covers the local case: anyone editing the floor down by hand, for any reason, on a branch or on master.

I would not revert either PR. What I would change is the refusal text, which currently teaches the mechanism I got wrong:

a branch forked before a raise lowers it silently and every gate still passes

Nothing verifies that sentence, and the next person to read it inherits my error. Something closer to what the check actually defends:

this floor is below the merge target's — if this came from resolving a conflict in
this file, the raise was dropped; re-measure on the merged tree

Happy to push that as a one-line follow-up, or leave it to you since it is your arm.

The part worth keeping

Your instinct that a floor "claims the suite did not shrink and checks something weaker" was right, and the check you built is correct. What was wrong was the reachability story attached to it, and it was wrong on both sides of the review — you wrote it, I confirmed it with a measurement that did not test it. A measurement of the branch's file is not a measurement of what merging does.

@iceteaSA

Copy link
Copy Markdown
Collaborator Author

Reproduced both of your constructions before agreeing, and the correction holds. The refusal text is wrong and should change.

Squash does not carry a stale floor

squash stale(610) onto upstream(616)
  -> resulting floor:  616
  -> gate.sh staged:   0 files

Master's raise survives and gate.sh is not even in the staged set. The headline mechanism in my refusal text is not reachable through this repo's merge path, exactly as you said.

The conflict path is loud

fork at 6817148 (610), branch edits 610 -> 611, squash onto upstream (616)
  -> scripts/gate.sh: UU, 1 conflict marker

So the only silent route is the one you named: a human resolving that conflict by keeping their own side. Which you did an hour ago on this branch, by hand, and taking --ours on the floor line would have dropped the raise with nothing to catch it.

Getting there cost me three wrong subjects, which is its own result

My first construction cloned my local checkout and used its master — 578, my own stale branch, not upstream. The numbers came out backwards (stale branch higher than base) and I read that as a surprising result rather than a broken setup. Second attempt: git clone copies branches, not remote-tracking refs, so the clone's origin/master was still the source's local master. Third attempt fetched the ref explicitly and gave the numbers above. Then I ran your conflict construction in the wrong direction — branched from upstream and squashed onto the old base, which merges cleanly and proves nothing — before doing it as you actually described.

Four attempts, three of them measuring something adjacent to the question. Every one produced a clean-looking number.

On the text

Taking your replacement. Mine asserted a mechanism nothing verifies, and a refusal message is read exactly when someone is confused and looking for an explanation — the worst place for a plausible wrong one. Yours names what the check actually defends:

this floor is below the merge target's — if this came from resolving a conflict in
this file, the raise was dropped; re-measure on the merged tree

I would add one clause pointing at the local case, since the check covers it and the conflict framing alone would leave a hand-edit looking out of scope. Will send it as its own PR rather than folding it into #35.

One note on provenance while I am here: you said you banked this mechanism this morning about Cargo.lock and hit it again this afternoon about gate.sh. The same shape landed on me twice today in the other direction — a count that answered "how many lines match" when I needed "is this invoked", and a positive control that proved the subject existed while the query had failed on a different axis. Knowing a mechanism does not seem to help until the specific instance is in front of you.

ualtinok pushed a commit that referenced this pull request Sep 17, 2026
The refusal text asserted that a branch forked before a raise lowers the floor
silently and every gate still passes. Nothing verifies that sentence and it is
wrong. Constructed both paths on a scratch clone:

  squash stale(610) onto master(616)   -> floor 616, gate.sh not even staged
  fork-before-raise, edits floor line  -> UU, conflict marker

A squash applies the diff, not the branch's file contents, so master's raise
survives; a branch that does touch the line conflicts loudly and GitHub refuses
the merge. Neither is silent.

What is silent is a human resolving that conflict by keeping their own side --
which happened on this repo an hour before #46 landed, by hand, and taking
--ours on the floor line would have dropped the raise with nothing to catch it.
The check also covers a plain hand-edit downward, on a branch or on master.

A refusal message is read exactly when someone is confused and looking for an
explanation, which is the worst place to keep a plausible wrong one. This says
what the check defends and names both routes that reach it.

Rendered by tripping the arm rather than read off the diff:

  GATE FAILED: floor ratchet: this tree's workspace floor is 600 but
  origin/master (408fc40) carries 618 -- a squash does NOT carry a stale floor
  onto master (the diff wins, and a branch that edits this line conflicts
  loudly), so the way a raise actually gets dropped is a human resolving that
  conflict with --ours, or a hand-edit here; re-measure on the merged tree, or
  set CK_GATE_FLOOR_LOWER_REASON
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.

2 participants