Skip to content

Refuse a record that is removed or renamed after it lands (#69) - #127

Merged
iderex merged 1 commit into
mainfrom
records/a-landed-record-may-not-be-removed
Aug 12, 2026
Merged

Refuse a record that is removed or renamed after it lands (#69)#127
iderex merged 1 commit into
mainfrom
records/a-landed-record-may-not-be-removed

Conversation

@iderex

@iderex iderex commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #69

What this changes

Two refusals in the deterministic pull-request check, in removal.go, next to
the answer rule they complete.

A record present at the base of the range and absent at the head is refused. The
message names what record 0004 does allow, because somebody meeting this has
already decided the directory is finished with and needs the other repair rather
than a no.

A record that moved is refused separately, and the refusal names both ends of
the move. The repair is a choice this check may not make: keep the slug, or
write a new experiment with its own question and let the old record say where
the work went. Which of the two a change is comes from what git reported, so the
diff is now read with --find-renames rather than with whatever diff.renames
happens to be on the machine that runs it.

ParseFiles now carries the pairing git prints for a rename. Losing it is what
separates a move from a removal beside an addition, and a refusal that names
only where a record used to be sends the reader looking for where it went.

What failure it prevents

The decision that records are permanent has two halves and only one of them was
refused. An answer already on the branch may be added to and not altered. A
record that is deleted was refused nowhere, because the runner walks a tree and
a record that is gone is not a record it can meet, so the rule this board's
central claim rests on held against editing and not against deleting.

The failure is ordinary rather than malicious. An experiment answered no, its
prototype went under record 0004, and what is left looks like an empty directory
somebody forgot to tidy. Or a slug is renamed to something better months later,
and every pointer at the old name stops resolving, including a promotion section
a reader is following from another board.

What was run

At e02c06e32d34605bfd414a4bafc48624e6ef227d.

$ go build ./... && go vet ./... && gofmt -l .
(no output from any of the three)

$ go test -count=1 ./...
ok  	github.com/Flowfin/lab/cmd/lab	12.661s
ok  	github.com/Flowfin/lab/cmd/pullrequest	4.548s
?   	github.com/Flowfin/lab/experiments/reading-a-tree-of-records	[no test files]
ok  	github.com/Flowfin/lab/internal/check	4.358s
ok  	github.com/Flowfin/lab/internal/hardware	4.299s
ok  	github.com/Flowfin/lab/internal/invariants	4.724s
ok  	github.com/Flowfin/lab/internal/prose	4.554s
ok  	github.com/Flowfin/lab/internal/pullrequest	4.701s

Each guard was deleted and the suite watched. Unwiring the rule from Judge:

--- FAIL: TestJudge/a_record_already_on_the_branch_that_this_change_removes
        refusal expected and not produced: record-already-landed-was-removed
--- FAIL: TestJudge/an_experiment_directory_already_on_the_branch_that_this_change_renames
        refusal expected and not produced: experiment-already-landed-was-renamed
--- FAIL: TestARemovedRecordNamesWhatMayBeRemovedInstead
--- FAIL: TestARenameNamesBothEndsOfTheMove
--- FAIL: TestAMoveNothingReportedAsOneIsRefusedAsARemoval

Removing the boundary, so that a record with no version at the base is judged:

--- FAIL: TestJudge/a_record_this_change_removes_that_was_never_on_the_branch_it_lands_on
        refusal produced that no case expected: record-already-landed-was-removed

Dropping the rename pairing in ParseFiles reddened TestParseFiles and
nothing else. That is the gap this change closes rather than a result: each half
of the package is proved on its own and neither saw the field being dropped
between them. TestARenamedRecordSurvivesTheJoinFromTheDiff was added after the
deletion showed it, and with the pairing dropped again it says:

--- FAIL: TestARenamedRecordSurvivesTheJoinFromTheDiff
    the verdict is [record-already-landed-was-removed], want exactly experiment-already-landed-was-renamed

Every edit above was reverted and the suite quoted at the top was run afterwards.

The check was also run as the workflow runs it, through cmd/pullrequest and
real git, against two commits built on top of eb88aadfb4b5f506f8da2b0912e49d866d9a3aba,
one removing the landed record and one renaming its directory. Neither is
pushed and no branch points at either.

$ GITHUB_EVENT_PATH=... go run ./cmd/pullrequest    # head b1f089f58d4a9178fcc1e69eafc3c31c139232cb
examined the pull request
  records: 1, 1 of them already on the branch this lands on
refused: experiments/reading-a-tree-of-records/EXPERIMENT.md: it was on the branch this lands on and is not at the head of this range, so an experiment that happened stops having happened. Record 0004 removes the code and keeps the record, which gains the line naming the commit that removed it (record-already-landed-was-removed)
1 refusal(s), 0 note(s), 0 rule(s) not judged
exit=1

$ GITHUB_EVENT_PATH=... go run ./cmd/pullrequest    # head 48998d5828ef462899f6dcf2a88ed57ef921e232
examined the pull request
  records: 2, 1 of them already on the branch this lands on
refused: experiments/reading-a-tree-of-records/EXPERIMENT.md: it was on the branch this lands on and this change moves it to experiments/reading-a-tree/EXPERIMENT.md, so every pointer at the old path stops resolving. Keep the slug, or leave the record where it is and let it say where the work went (experiment-already-landed-was-renamed)
1 refusal(s), 0 note(s), 0 rule(s) not judged
exit=1

The means is Go, in the package that already holds both ends of the range and
already refuses a rewritten answer. It carries the three rules: each property is
refusable through the Refusal the package already has, each has a case that
trips it and no other, and the numbers above come from commands. It adds no
language, runtime or dependency this tree does not carry.

This change has no second reader. Nobody but me has read it, and the evidence
above stands in place of that rather than beside it.

What this does not do

It does not tell a rename from a removal on its own. That separation is git's
similarity judgement, and a move made together with a rewrite of the record is
refused as a removal, which is red for the right reason and names the wrong
repair. Written at the rule and covered by a case.

It does not stop history being rewritten on the branch itself. A force push that
removes the commit a record arrived in is refused by the ruleset, which refuses a
non-fast-forward push and carries no bypass actors. Named at the rule so a green
run here is not read as covering it.

It judges paths and presence, never intent. A removal argued for in the body and
a removal nobody noticed are the same change to this rule, and the reason a
record is being taken out is what the review is for.

The decision that records are permanent says a record is not deleted and
is not rewritten to hide what it said. Only the second half was refused.
A change that removed an EXPERIMENT.md, or the directory it sits in,
passed every check here, because the runner walks the tree and a record
that is gone is not a record it can meet. So the rule this board's
central claim rests on held against editing and not against deleting,
and deleting is the cheaper way to make an experiment stop having
existed.

Two refusals in the deterministic pull-request check, next to the answer
rule they complete, because telling a removal from a record that was
never there needs the version at the base of the range and this check
already holds both ends of it.

A record present at the base and absent at the head is refused, and the
message names what record 0004 does allow: the code goes, the record
stays, and it gains the line naming the commit that removed the code.

A move is refused separately and the message names both ends of it,
because the repair is a choice the check may not make between keeping
the slug and writing a new experiment that says where the work went.
Which of the two a change is comes from what git reported, so the diff
is read with --find-renames rather than with whatever diff.renames is
set to on the machine that runs it, and where a move is not reported as
one it is refused as a removal, which is stated at the rule.

Two boundaries are written at the rule. A record created and removed
inside one branch is not covered, because nothing about it reached the
branch the change lands on. History being rewritten on the branch itself
is the ruleset's refusal rather than this one, and saying so is what
keeps a green run from being read as covering it.

ParseFiles now carries the pairing git prints for a rename. Deleting it
left every case green, because the judgement is proved against values
and the parser against what git prints, and neither half saw the field
being dropped between them. The case that closes that join was added
after the deletion showed the gap.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex
iderex merged commit 5235c37 into main Aug 12, 2026
24 checks passed
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.

Refuse a record that is removed or renamed after it lands

1 participant