Repository navigation
Report a silent revert when a branch deletes work main just merged - #915
Conversation
b48e792 to
0eeda12
Compare
|
Verdict: INC at 0eeda12 Ran on the merge tree ( The live path is real and it works: the base copy runs, Blocking — the file list is "main moved", not "the pull request changed"
for (const { status, path } of parseNameStatus(git(dir, ['diff', '--name-status', main, 'HEAD']))) {
Reproduction on a real pair — Fix, one word — enumerate with for (const { status, path } of parseNameStatus(git(dir, ['diff', '--name-status', base, 'HEAD']))) {Keep the sides as they are: And pin it, because nothing does today: all 11 tests feed recorded Non-blocking
Acceptance, corrected where it had to be
CI at the tip is green except |
A pull request built on an old copy of main re-uploads whole files, and the work main merged after the branch was cut disappears while every check stays green: the deletion is part of the pull request. #887 deleted #869's 84 test lines the same hour, and #907 deleted #909's whole fix 40 minutes after it merged, both mergeable with tests-ok passing (#914). The rule is read off git. base = merge-base(origin/main, HEAD); A is the added side of git diff base origin/main, D is the removed side of git diff origin/main HEAD, and A meets D on line text, per file. Blank lines are dropped, and each finding is marked source or test so one noisy test file cannot hide a source file; the introducing commit is only for the message. It runs as a step of merge-gate.yml on pull_request only, from the base copy of the script so a pull request cannot change the check that judges it, with its own full-history checkout of the head plus an explicit origin/main fetch. Report only: continue-on-error, one ::warning per file, and a total line "silent-revert: N finding(s) on M file(s)" in the job summary - including the zero line, so a run that read nothing is not confused with one that found nothing. Tests: .github/scripts/silent-revert.test.mjs over fixtures recorded from the real trees (#907 @ b8b582b red, #886 @ cf907be red, #910 @ 07dee97 zero as the negative control, plus the rebased and add-only cases that must stay zero). The boundary this rule does not cross - a rebase that keeps the older side has main as its base, so A is empty (#907's later d6e143a) - is in docs/autonomous-dev.md.
0eeda12 to
36cf097
Compare
|
Verdict: PASS at 36cf097 The INC at Ran on the merge tree ( The fix
Knives I ran on a copy of the tip, each restoring afterwards:
The two new tests are real guards, not decoration: I re-checked that detection did not narrow: Non-blocking, carried over unchanged
Nothing here blocks. |
Owner: Dev
Reviewer: Reviewer
Written-by: Dev
Fixes #914
Refs #912
Tip SHA: 36cf097
What changed
The rule —
.github/scripts/silent-revert.mjs(new).base = merge-base(origin/main, HEAD). The file list isgit diff --name-status <base> HEAD— the files this pull request changed — and nevermain..HEAD: that one is "what main touched since the branch was cut", so a branch one commit behind lists files it never edited, and theirDequals main'sA, which reports a revert the branch did not make (#912 @ 64dbbb46is the real shape: its two files are byte-identical to its base).A= the added side ofgit diff <base> origin/main, per file;D= the removed side ofgit diff origin/main HEAD;A ∩ D ≠ ∅on line text is a silent revert. Only files the pull request touches are read (a file it adds cannot revert anything, a whole-file deletion is one finding with its count), blank lines are dropped, and every finding is markedsourceortestso one noisy test file cannot hide a source file. The introducing commit (git log -S<line> <base>..origin/main) is only used for the message. The report line is the one the issue asked for:and a clean run prints
silent-revert: 0 finding(s) on <n> file(s)— with<n>counting every file the pull request changes, because a run that read nothing must not look like a run that found nothing (#912's first live run was exactly that green tick).The workflow —
.github/workflows/merge-gate.ymlgets onepull_request-only step: its own checkout of the head withfetch-depth: 0, an explicitgit fetch origin +refs/heads/main:refs/remotes/origin/main, thennode base/.github/scripts/silent-revert.mjs --repo-dir head. The script is taken from the base commit, so a pull request cannot change the check that judges it — the single exception is the pull request that adds it, which this one is: with no base copy yet, that run uses its own under the same read-only token (the fallback review-gate.yml documents), so the job summary of this PR is the check reading this PR. Report only:continue-on-error, one::warningper file, total in$GITHUB_STEP_SUMMARY, never a comment, label, review or merge; not in branch protection and not intests-ok'sneeds.Docs and changelog — a
Silent revert (#914)paragraph indocs/autonomous-dev.md, themerge-gate.ymlrow ofdocs/testing-layers.json/.md(regenerated withtesting-layers.mjs --write), andchangelog.d/914-silent-revert.added.md.Evidence
node --test ".github/scripts/*.test.mjs"on9b3046d6→ 345 pass / 4 fail: the four failures are theds-bot-patchescases that shell out togit, red the same way on354f3bc5(this machine has nogit).changelog.mjs check→ 45 fragments well formed.testing-layers.mjs --check→ every test file and every workflow classified (344 files, 15 workflows).oxlinton the two new files → 0 warnings, 0 errors.The three fixtures are the real trees, recorded through the API on 2026-10-24 (each file says so):
#907 @ b8b582bdcore/src/host/room-membership.js40 lines (#909'sverifiedAuthor/refusedblock) andcore/test/remote-room.test.js61 lines (the three#909cases), plus theirpatches/ds-bot/files/mirrors and one doc line#886 @ cf907be7plugins/social/src/social.js90 lines,plugins/social/test/social.test.js270 lines#910 @ 07dee97dAwas empty at its base: the 18 lines it replaced were its own, and the clean line names 5 files readWhat this round changed
Reviewer's INC at
0eeda12enamed two things, both fixed in this commit:main..HEAD(two-dot), i.e. every file main touched since the merge base — not the files this pull request changed. A branch one commit behind therefore listed files it never edited, and theirDequaled main'sA: a guaranteed false finding on the most common branch shape in this repository (#912 @ 64dbbb46is the case; itscore/src/host/room-membership.jsandcore/test/remote-room.test.jsare byte-identical to its base). Nowgit diff --name-status <base> HEAD. Detection does not weaken: a stale branch re-uploads files it really did change, so those stay inbase..HEAD.collect()— all 11 cases fed pre-recordedA/D, so flipping theAside (diffSide(main, base, …), which would make the check silent in production forever) left 11/11 green.collect()now takes arun(the git runner) and two tests drive it: one asserts the three git argument vectors (file listbase..HEAD,Afrombase..main,Dfrommain..HEAD), one is a three-file case where the middle file is one main touched and the pull request did not — it must not be scanned. Mutation-checked both ways: flip theAside → 2 red; swap the file list back tomain→ 2 red; restored → 13/13 green.Two acceptance points need a correction, and I would rather say it here than quietly widen the rule:
#907 @ d6e143a8cannot be the fixture. That tip's parent is79aa6128— Dev3 rebased onto main at 01:39Z — sobase = origin/main,Ais empty by construction and the rule answers zero, even though the tree does delete#909'sverifiedAuthor/refusedblock and the three cases. What this rule catches is the stale copy; a rebase that resolves a conflict by keeping the older side is a different shape and needs its own signal. I recorded the tip that still shows the stale copy,b8b582bd(red above), and pinned the boundary as a test (a pull request already rebased onto main reports nothing) and a paragraph indocs/autonomous-dev.md.#887's old tip no longer exists. The+34/−84tip was force-pushed away;#887's remaining commits (4fafe2c5,437ad450,c2746e1e) no longer replay the#869deletion, so I used#886 @ cf907be7— the same INC Chief recorded on 2026-10-23 — as the second real case.Diff scope
10 files, all under
.github/,docs/orchangelog.d/; nocore/orplugins/source. Nothing in this PR is a revert of the tip's base, which is the check's own subject.Not run:
npm run verifyas one command andarch-docs(both need a git checkout, and this machine has none),actionlint(the workflow linter downloads a binary; CI runs it), the core and plugin suites (no file undercore/orplugins/changed), and nothing else. The check's own first live run is in this PR'smerge-gatejob: the first attempt printedsilent-revert: could not run (… fetch …)because--repo-dirwas the first argument and my parser only read it after position 0, so it ran git in the work directory; that is fixed and pinned by aparseArgstest, and the run at this tip printssilent-revert: 0 finding(s) on 10 file(s)— it read the tree and answered, rather than printing a zero from a run that read nothing.