Skip to content

fix(cli): pull verifies what it wrote, and says when the bytes came back different - #91

Merged
xizhuomengcontin merged 1 commit into
mainfrom
fix/pull-verifies
Sep 16, 2026
Merged

xizhuomengcontin merged 1 commit into
mainfrom
fix/pull-verifies

Conversation

@xizhuomengcontin

@xizhuomengcontin xizhuomengcontin commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Orca-Code-Review — push 1

Severity Count
P0 0
P1 0
P2 0
P3 0

✅ no blocking findings

Two halves of the same question: is the run I just pulled the run that was pushed?

pull never checked

Spec §6 — "Readers SHOULD verify it and MUST report, not repair, a mismatch." verifyIntegrity
exists and replay calls it. pull wrote a run to disk and said pull.done.

The zip's own CRC catches a flipped bit. It cannot catch an archive that is internally consistent
and whose events simply are not the ones its manifest attests to — which is the shape a storage
bug on the far side takes. That landed silently, and the first sign would be a replay that
diverged for a reason nobody could name.

Now verified against the root in the manifest that arrived with it, on the staged copy, before
the swap
— what fails verification must not be what replaced a run that --force merely asked
to update:

$ orca pull <run> --gateway <serving a well-formed archive with a mismatched root>
error pull.failed
  run_11aa80b1fadd: events.jsonl does not match the integrity root in its own manifest
  (manifest 4d106ed7b608973e…, file ec9d07756d0b9f03…). Nothing was replaced. Pull it
  again; if it persists the copy on the gateway is damaged.

$ ls .orca/runs | wc -l
0

The round trip nobody could explain

Push a run, pull it back, and the bytes differ. A gateway is free to store the trace however it
likes, and this one re-serialises it — Go's encoding/json sorts a map's keys and escapes <,
> and & — then recomputes the root over its own bytes.

Both copies are internally consistent. Neither is wrong. What is lost is the one thing a digest is
for: it no longer tells you the two are the same run, so a real corruption and a benign re-encoding
look identical.

Nothing said so. Now, when a local run of the same id is there to compare against:

$ orca pull <run> --force        # over the run it was pushed from
warn pull.reserialized run=run_11aa80b1fadd local=5a2576662583e2c0… pulled=4d106ed7b608973e…
     why="same events, different bytes — the gateway stores its own serialisation and
          recomputes the root over it"
     effect="the two copies can no longer be compared by digest, though nothing was lost"
info pull.done run=run_11aa80b1fadd files=3 dir=…

and pull.differs, loudly, when the events themselves are not the same.

What this deliberately does not do

It does not canonicalise what the writer puts on disk. Making the two roots equal means matching
Go's escaping of <>& byte for byte — no canonicalisation standard does that (RFC 8785 sorts keys
and leaves escaping minimal), so it would be adopting one encoder's quirk as the format's own.
That is a decision about the spec, not a bug fix.

It would also cut against the grain of the repo: nothing here re-serialises an event. scrub
rewrites events.jsonl by substituting text inside the original lines rather than parsing and
re-emitting, and #35 put it plainly — "the only correct thing to send is the bytes already on
disk."
The comparison added here sorts keys deeply at read time and writes nothing.

Happy to follow up with the canonical-writer change if that is the call; it is small, and the
byte accounting is already worked out (key order plus 9 escapes, +45 bytes on the run I measured).

Verification

Three cases, end to end against the live gateway and a local stand-in:

case result
pull into a fresh directory quiet, as before
pull over the local run it was pushed from pull.reserialized, both roots named
archive whose root does not match its events refused, destination untouched

Both tests fail without the change — expected undefined to be defined for the report, and the
corrupt pull silently succeeding for the other.

Suite: 10 failed / 2416 passed / 40 skipped — the same 10 pre-existing Windows platform failures
as main, two more passing tests. prettier --check, tsc --build, scripts/conformance.mjs
(61 events, 0 failures) and scripts/fidelity.mjs --check (1 recording, 0 regressions) all clean.

🤖 Generated with Claude Code

…ack different

Spec §6: "Readers SHOULD verify it and MUST report, not repair, a mismatch."
`verifyIntegrity` existed and `replay` called it. `pull` wrote a run to disk
and said `pull.done`.

So a download that arrived altered landed silently, and the first sign of it
would be a replay that diverged for a reason nobody could name. The zip's own
CRC catches a flipped bit; it cannot catch an archive that is internally
consistent and whose events simply are not the ones its manifest attests to,
which is the shape a storage bug on the far side takes.

`pull` now verifies the staged copy against the root in its own manifest —
before the swap, never after, because what fails verification must not be what
replaced a run `--force` merely asked to update. Verified end to end against a
gateway serving a well-formed archive with a mismatched root: the pull is
refused and the destination is untouched.

The second half is the round-trip nobody could explain. Push a run, pull it
back, and the bytes differ: a gateway is free to store the trace however it
likes, and this one re-serialises it — Go's `encoding/json` sorts a map's keys
and escapes `<`, `>` and `&` — then recomputes the root over its own bytes.
Both copies are internally consistent and neither is wrong. What is lost is the
one thing a digest is for: it no longer tells you the two are the same run.

Nothing said so. Now, when a local run of the same id is there to compare
against, pull reports which it is:

  warn pull.reserialized run=… local=5a25766625… pulled=4d106ed7b6…
       why="same events, different bytes — the gateway stores its own
            serialisation and recomputes the root over it"
       effect="the two copies can no longer be compared by digest, though
               nothing was lost"

and `pull.differs`, loudly, when the events themselves are not the same. The
comparison sorts keys deeply at read time and touches no file: canonicalising
what this writer puts on disk would mean adopting one JSON encoder's escaping
rules as the format's own, which is a decision about the spec rather than a bug
fix, and nothing else in this repo re-serialises an event — `scrub` rewrites
`events.jsonl` by substituting text in the original lines for exactly that
reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@orcacode-review orcacode-review 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.

🐳 OrcaCode Review

✅ No findings — nothing to flag in this PR. Great work!

OrcaCode Review — Route Smarter. Ship Safer. Spend Less.
Engine-reported: 545 calls · 49.5M tokens · 99% cached

❤️ Share · Install OrcaCode Review

Free on GitHub — the review runs on your own OrcaRouter key. If it helped, a shout-out goes a long way.

Share: X · Reddit · LinkedIn
Follow: X · Discord · LinkedIn · OrcaRouter

@xizhuomengcontin
xizhuomengcontin merged commit 7bdef90 into main Sep 16, 2026
6 checks passed
@xizhuomengcontin
xizhuomengcontin deleted the fix/pull-verifies branch September 16, 2026 06:07
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.

1 participant