# atproto-devtool
Last verified: 2026-04-19
Single-crate Rust 2024 binary providing developer tooling for the atproto
ecosystem. Today the only shipped subcommand is `test labeler`, which runs a
conformance suite against an atproto labeler.
## Tech stack
- Rust 2024, single crate (binary + library). Library target exists so
integration tests under `tests/*.rs` can reach pipeline internals via
`atproto_devtool::commands::test::labeler::...`.
- Async runtime: tokio (rt + macros + time + net only — not full).
- HTTP: reqwest with rustls. WebSockets: tokio-tungstenite with
rustls-tls-native-roots.
- CLI: clap derive. Diagnostics: miette (fancy) + thiserror.
- Crypto: k256 + p256 (ECDSA), sha2, multibase. `getrandom` is a direct
dep so we can reach `OsRng` for JWT `jti` nonces and the sentinel
run-id (the transitive `rand_core` inside `elliptic-curve` is built
without the `getrandom` feature).
- JWTs: hand-rolled compact JWS in `src/common/jwt.rs` (ES256 and ES256K
only). We do NOT depend on a JWT library.
- atproto types: atrium-api (not atrium-xrpc-client — we use reqwest directly
through our own seams so every network hop is mockable).
- Testing: insta snapshots, assert_cmd for CLI end-to-end.
## Commands
- `cargo build` / `cargo run -- test labeler <target>`
- `cargo test` — unit + integration + snapshot tests.
- `cargo insta review` — review snapshot diffs after test runs.
- `cargo fmt` / `cargo clippy -- -D warnings` — both must be clean.
- `cargo read <crate>` / `cargo read --api <crate>` / `cargo read --docs <crate>`
— preferred over browsing docs.rs.
## Project structure
- `src/main.rs` — thin tokio bootstrap over `cli::run`.
- `src/lib.rs` — re-exports everything under the same tree so integration
tests in `tests/` can reach pipeline internals.
- `src/cli.rs` — clap root, tracing subscriber wiring (`--verbose` toggles
`DEBUG`), `NO_COLOR`/`--no-color` handling.
- `src/commands/test/labeler/` — the `test labeler` subcommand. See
`src/commands/test/labeler/CLAUDE.md` for pipeline architecture.
- `src/common/identity.rs` — DID/handle/multikey/PLC primitives plus
signing-key types shared across stages. See `src/common/CLAUDE.md`.
- `src/common/jwt.rs` — compact JWS encoder/decoder (ES256 / ES256K) used
by the report stage to mint self-mint service-auth tokens. See
`src/common/CLAUDE.md`.
- `src/common/diagnostics.rs` — small helpers for building `NamedSource` and
spans against JSON payloads.
- `tests/` — one integration binary per stage (`labeler_identity.rs`,
`labeler_http.rs`, `labeler_subscription.rs`, `labeler_report.rs`,
`labeler_endtoend.rs`, `labeler_cli.rs`) plus shared fakes in
`tests/common/mod.rs` and fake smoke tests in `tests/common_fakes.rs`.
- `tests/fixtures/` — JSON + binary CBOR fixtures per stage. The
`tests/snapshots/` tree holds insta snapshots.
- `docs/implementation-plans/2026-04-13-test-labeler/` — the initial phased
implementation plan for the four-stage pipeline. The report stage was
added later under `docs/implementation-plans/2026-04-17-labeler-report-stage/`.
Both trees are historical and useful for grepping rationale on individual
decisions.
## Conventions
- No `mod.rs`. Use the `foo.rs` + `foo/` sibling-file layout.
- `#[expect(...)]` over `#[allow(...)]` for every lint suppression.
- Imports at module top only (the one exception is `cfg()`-gated items).
- `uninlined_format_args = "deny"` is on in `Cargo.toml`. Always use
`format!("{x}")`, never `format!("{}", x)`.
- All code comments end with a period. Sentence case for headings.
- Every `thiserror::Error` that is rendered to users must also derive
`miette::Diagnostic` with a stable `code = "..."` string. Diagnostic codes
appear in snapshot tests — changing one is a breaking change.
- Never use `#[serde(flatten)]` or `#[serde(untagged)]`; write explicit structs
and/or custom visitors.
- Newtypes everywhere: `Did`, `Curve`, `AnyVerifyingKey`, etc. Don't let raw
strings leak across stage boundaries.
## Testing pattern
Every network-touching stage goes through an injected trait object
(`HttpClient`, `DnsResolver`, `RawHttpTee`, `WebSocketClient`,
`CreateReportTee`, `PdsXrpcClient`). Production wires in `Real*`
implementations; tests wire in fakes from `tests/common/mod.rs`. Do not
add network calls that bypass these seams — they make stages untestable
and break snapshot determinism.
Integration tests pin per-check output via insta snapshots. When a check's
rendered text, diagnostic code, or order changes, `cargo insta review` is the
expected workflow. Treat every `.snap` file under `tests/snapshots/` as part
of the public contract for the CLI.
## Boundaries
- Safe to edit: `src/`, `tests/`, `docs/`.
- The `docs/implementation-plans/` tree is historical — update it only when
the plan genuinely needs revising, not as part of regular code changes.
- `Cargo.lock` is committed; update it through `cargo` only.