DEV Community

Mahiro Hirakawa
Mahiro Hirakawa

Posted on

What "undo" actually means when the target is a real repo, not a fixture

Falsifier, stated before anything else

Here's the claim this post makes, and here's exactly how to prove it wrong.

Claim: a tool we built (gx) can take a real, public git repository, make a
change to it through two different storage adapters, prove the change
happened, undo it, and verify both the change and the undo (offline, in a
separate process), and the whole four-stage walk finishes in well under 30
seconds.

To falsify it: clone github.com/stevemao/left-pad at commit 2fca615
(11 tracked files, 44,229 bytes). Run the four-stage walk against it on the
git substrate, then again on the fs substrate. If any stage fails, if the
wall-clock for either substrate's four stages exceeds 30 seconds, if the
git walk changes the worktree bytes of README.md (it should only move
HEAD; see below), or if undo does not restore the exact pre-change
sha256 / HEAD, this post is wrong. We ran it three times. The numbers
are in the table further down.

One thing this falsifier doesn't cover, up front: the exact binary we used
(a local target/debug/gx build) is not confirmed to match what's
currently checked out in the public repo we point to below. We didn't verify
that before writing this, and we're saying so rather than letting the gap
sit quietly. More on what we didn't check in the limits section.

The tool under test is open source: https://github.com/TraceFold/tracefold

What we actually did

We're not examining left-pad for problems, and this isn't a report about
left-pad. Nothing is wrong with it. That's rather the point: we picked it
because it's small, public, and mildly famous (the 2016 npm unpublish
incident), so a reader can recognize it as a real repo in about three
seconds instead of squinting at a synthetic fixture. What follows is a
demonstration of our own tool, run against a real repo, not a report on the
repo itself.

Until now, the four-stage walk (break, prove, restore, verify) had only been
exercised against a fixture: a small in-process mock server built for the
demo command itself. That's a fine way to test the walk's logic, but it
doesn't tell you whether the same walk holds up against something with real
git history, a real working tree, and file bytes nobody synthesized for the
occasion. So we cloned left-pad, pointed the tool at it through two
different adapters, and ran the walk for real.

The four stages, actual output

Below is run 1 of 3 against left-pad @ 2fca615, translated from the
Japanese-language run log into English narration; the timings, hashes, and
commit IDs are unchanged. The other two runs are not reproduced in full
here; their numbers are in the table further down and match this format.

=== gx 4-stage demo - run 1 - repo=left-pad ===
REPO=left-pad COMMIT=2fca615 BRANCH=master TRACKED_FILES=11 BYTES=44229 TARGET_FILE=README.md

--- substrate=git ---
1 break (commit) [1.247s policy=git-permit-default=Allow]: history got a new
  commit HEAD 2fca615->ef062db. worktree bytes unchanged (index/HEAD only;
  the file was left untouched; git status='M  README.md'). status=OK
2 prove (receipt) [0.362s]: commit receipt checked against merkle
  checkpoint -> valid
3 restore (undo) [0.490s]: history moved HEAD back to 2fca615 (matches
  original), git status clean=yes. status=OK
4 verify (offline) [0.535s]: both commit and undo receipts re-checked in a
  separate process, no network -> commit=valid undo=valid
T_git_four_stages=2.662

--- substrate=fs ---
1 break (commit) [1.172s policy=fs-permit-default=Allow]: fs substrate has
  no concept of a repo. the working file itself was rewritten,
  sha256 662703d6->5491b8f2. status=OK
2 prove (receipt) [0.377s]: commit receipt checked against merkle
  checkpoint -> valid
3 restore (undo) [0.384s]: file sha256 restored to the pre-change value,
  662703d6. status=OK
4 verify (offline) [0.483s]: both commit and undo receipts re-checked in a
  separate process, no network -> commit=valid undo=valid
T_fs_four_stages=2.441

T_total_incl_clone=6.057
FAILURES=0
P4_RESULT=PASS
Enter fullscreen mode Exit fullscreen mode

That "worktree bytes unchanged... the file was left untouched" line in stage
1 of the git walk is not a throwaway detail. It's the actual point of
running this against a real repo instead of a fixture.

Why the git stage doesn't say "the file changed"

We route the same operation through two different storage adapters on
purpose, because they don't do the same thing to the target, and saying so
correctly turned out to be harder than making the undo work.

On the git substrate, "committing a change" moves HEAD and writes a new
blob into the object database. It does not touch the bytes sitting in the
working tree. git status shows M README.md (a diff against the new
HEAD), but if you open the file on disk, it reads exactly as it did
before. The repo's history changed. The file, as a set of bytes on
disk, did not.

On the fs substrate there's no repo concept at all, just a plain file, and
committing a change there does what it sounds like: the bytes on disk get
rewritten, verifiably (sha256 662703d6->5491b8f2 above).

We almost described both the same way, calling both stages "the file
changed," because from outside it feels like the same thing happened twice
(an edit went in, then came back out). That description is not accurate for
the git case, and a tool that narrates git history as a file rewrite is
misreporting what it just did, even when the receipt underneath is
perfectly correct. So the demo script carries two separate string
templates, one per substrate, and the git template never uses the word
"rewritten." We checked: across all three runs, that word appears only in
the fs-substrate lines, three times, once per run.

This is a small case of something we care about more broadly. "Nothing
happened yet" (unknown), "nothing is there" (absent), and "it happened and
now it's gone" (undone) are not the same claim, and "the record changed" is
not the same claim as "the artifact changed." Collapsing distinct states
into one, because both are easy to describe with the same word, is exactly
the kind of shortcut that makes a verification tool untrustworthy in the
one place it matters: telling you what actually happened.

Numbers across three independent runs

Each run is a fresh clone and a fresh signing key; nothing is reused
between runs.

run git 4-stage (s) fs 4-stage (s) total incl. clone (s) result
1 2.662 2.441 6.057 PASS
2 2.463 2.339 5.763 PASS
3 2.454 2.321 5.706 PASS
median 2.463 2.339 5.763 3/3 PASS

Worst single observation across all three runs was 6.057 seconds against a
30-second budget, a bit over a fifth of it. We're not claiming the budget
is tight. On this one repo, on this one machine, it wasn't close.

Try it in about 30 seconds

Get gx: git clone https://github.com/TraceFold/tracefold && cd tracefold && cargo build --release.

The script we used is not reproduced in full here. Structurally, one
substrate's walk is:

gx submit --substrate git --locator "$clone#master:README.md" \
  --intent goal.txt --context Evidence --actor-key "$key_id"
gx plan "$intent_id"
gx verify "$transformation_id"
gx commit "$transformation_id"                 # 1. break
gx log checkpoint --key "$key_file" --out head1.json
gx receipt verify "$commit_receipt" --checkpoint head1.json  # 2. prove
gx undo "$transformation_id"                    # 3. restore
gx log checkpoint --key "$key_file" --out head2.json
gx receipt verify "$commit_receipt" --offline --checkpoint head1.json  # 4. verify
gx receipt verify "$undo_receipt" --offline --checkpoint head2.json
Enter fullscreen mode Exit fullscreen mode

Swap --substrate git --locator "$clone#branch:path" for
--substrate fs --locator "$path" to run the same walk against a plain
file instead of a git history.

What this doesn't cover

We ran the checks that matter for the claim above and stopped there. Here's
what's still open, listed plainly rather than folded into the passing
numbers:

  • Only two storage adapters, out of more that exist. This demo used fs and git. There's a third adapter (mcp) that a different verb (gx wrap) routes through, and we have not run that verb against anything in this pipe. It's untested: not passing, not failing, just not run yet.
  • No refusal path was exercised. Every policy decision in every run was Allow (fs-permit-default / git-permit-default). We haven't shown what the tool does, or says, when a change is refused or escalated instead of admitted.
  • One machine, one OS. Everything above ran on a single WSL2 instance (Ubuntu 24.04). No Windows-native run, no macOS run, no CI container. The 30-second number is a fact about that one environment, not a portability claim.
  • The binary's exact provenance wasn't pinned. We used a locally built binary and did not re-confirm which source commit it was built from before this specific run, nor whether it matches what the public repo currently has checked in. That's a gap in our own process, not something we're asserting is fine.
  • One target repo. left-pad is 11 files. We also ran the same four stages once against a much larger public repo (a few thousand files) as part of an earlier check, and it passed too, but that run isn't in this demo and we're not folding its numbers into the table above. Different test, reported separately, not conflated here.

None of the above is a defect we found in something else. It's a list of
things we haven't run yet, kept separate from the things we have, so the
passing numbers above don't get read as covering more ground than they do.


Raw run logs and the demo script are kept alongside this post's source
material; the run 1 excerpt and the three-run table above are the complete
numeric record for this post.

Top comments (0)