A three-source conformance checker for AT Protocol Personal Data Servers (spec + lexicons + reference behavior). Pre-alpha.

CAR-output sync reads need CAR-decode assertions #3

Closed katsuricata.com
Closed opened by and assigned to katsuricata.com

sync.getRepo, sync.getCheckout, sync.getBlocks, sync.getRecord, and sync.getBlob return CAR/binary bodies, not JSON; output-shape assertions don't apply, so the generator currently skips them.

They need CAR-decode assertions instead: parse the response with @atproto/repo (readCar), assert the roots rule (first root is the commit CID), and for getRepo run verifyRepoCar (commit signature, MST structure, rev). getBlob should additionally assert the CSP/nosniff headers per spec.

This is likely a new generated variant kind (binary/car output) rather than hand-written checks, so the checks regenerate on pin bumps.

Done when: the five endpoints have generated checks that pass against the oracle.

Done. All five are generated checks now and green against the oracle (commit 0db7612 on main).

Went with a new generated variant kind, like the issue guessed. There's a runCarContract runner next to runContract, and the generator emits a CAR/blob-decode contract when outputKind says the endpoint returns bytes instead of JSON. So they regenerate on pin bumps with everything else.

The per-endpoint split:

  • getRepo and getCheckout decode with readCar, check the first root is the current commit, and run verifyRepoCar (commit sig, MST structure, rev).
  • getRecord is the same roots check, no full verify since it's a slice.
  • getBlocks is the odd one out -- it returns a rootless CAR (roots is empty), so a blanket "first root is the commit" rule would have failed it. It just asserts the returned blocks cover the requested CIDs.
  • getBlob checks the bytes round-trip exactly, plus nosniff and the sandboxing CSP as a must (that was the call we made).

Two things that took a bit of digging. getBlob needed a permanent blob: a fresh uploadBlob stays quarantined and 404s until a record references it, so the runner writes a record pointing at the blob first. That's also why getBlob is mutate while the four CAR reads stay readonly. And I added @atproto/lex-data as a direct dep for parseCid, it was already in the tree transitively.

Full e2e passes, 54 checks, no failures. The one spec warn is the pre-existing www-authenticate thing.

Sign up or Sign in to add to the discussion.