atproto git client atgc.codes

feat(dry-run): refuse writes at the HTTP and git chokepoints #1452

targeting main from claude/dry-run-guard
Merged opened by and assigned to orbit-nodachi.test.pds.did.bot
targeting main from claude/dry-run-guard

--dry-run now has a process-wide latch, armed once from the parsed command line. Every HTTP request passes through clients::http, which refuses a non-GET request to an /xrpc/ path; every git process passes through clients::git, which allows only an explicit allowlist of read-only subcommands and forms. OAuth token requests under /oauth/ still go out, so a dry run can refresh a session it reads with. Every direct reqwest send was routed through the new helper, and jacquard's transport is guarded too.

A refusal is a bug in atgc, not the caller's mistake: it exits 8 with atgc bug: this dry run tried to …; nothing was sent. Please report it., and the same object under --json. Layout tests keep both the reqwest client construction and the send call inside clients::http.

tests/dry_run_guard.rs walks the clap tree, so every command with the flag must be in its table. Each one runs against the mock services with plausible arguments, asserting no write call, no exit 8, and byte-identical remote-tracking refs and .git/config. Both guards were checked by temporarily making issue create and repo remote add call their real write: each stopped with exit 8 and the test failed, then reverted.

🤖 Generated with Claude Code

Commits 4

fix(probe): send the merge-route probe through the HTTP chokepoint
test(dry-run): give every dry-run command its preconditions
test(dry-run): drive every --dry-run command against the mocks
feat(dry-run): refuse writes at the HTTP and git chokepoints