Undo reported "nothing left to undo" after a successful undo #72

Merged
steve merged 2 commits from fix/undo-reports-noop into main 2026-07-21 12:25:35 +00:00
2 Commits
Author SHA1 Message Date
steveandClaude Opus 4.8 8a0f367804 Make the Go/SQL tally parity a tested contract, not a hand-maintained one
Build image / build-and-push (push) Successful in 5s
Gadfly's point stands: countRevisions reproduces in Go what ListChangeSets does
in SQL, and nothing was holding the two together. Its sibling finding is the
same problem seen from the test side — the test compared only TOTALS, which
would agree even if the two groupings had diverged completely.

Both ends now name the contract, and the test compares the full per-(entity, op)
breakdown row for row, across two reverts of different shapes so there is more
than one row to get wrong. That turns "someone will remember to keep these in
step" into something CI notices.

The duplication itself stays. A change set that has just been written has no
rows to GROUP BY yet, so the alternative to counting in Go is a second round
trip to count what we are already holding.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 08:25:00 -04:00
steveandClaude Opus 4.8 42246fd6b7 Undo reported "nothing left to undo" after a successful undo
Build image / build-and-push (push) Successful in 9s
Gadfly review (reusable) / review (pull_request) Canceled after 5m38s
Adversarial Review (Gadfly) / review (pull_request) Canceled after 5m38s
Found by running the real thing: the agent replaced a bed of garlic with
cucumbers, Undo restored the garlic correctly — and then said "Nothing left to
undo — this was already reversed."

The revert response carries the change set commitScope just inserted, and that
row has no per-op tally: Counts is populated by the history LIST query, not by
the insert. describeUndo treated a zero tally as "nothing happened", so a real
undo with eight revisions behind it reported itself as a no-op. Telling someone
an action did nothing when it just worked is about the worst thing a safety
feature can say, because the obvious next move is to do it again.

Fixed on both sides. The client now treats a NULL change set as the no-op
signal, which is what the server actually means by it — an empty tally is not
the same thing and must not be read as one. And RevertChangeSet now fills in the
counts, so the revert response and the list read describe the same change set
the same way, and a partial revert can say "2 of 3" from either.

The unit test missed it because the fixture I wrote populated counts, so it was
testing my assumption about the response rather than its actual shape. The new
tests pin the real thing: the service asserts a revert's counts match the fill
it undid AND match what the list read reports, and the client asserts an empty
tally still reads as "Undone."

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 08:19:15 -04:00