CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Classify every src file under FCIS

Adds a `// pattern: <classification>` comment to every src file with
runtime behaviour, per the ed3d-house-style FCIS skill. Distribution:
10 Functional Core, 8 Imperative Shell, 20 Mixed (each with a one-line
reason). The three barrel/re-export files (lib.rs, common.rs,
common/oauth.rs) are exempt under the skill's rules.

These are descriptive comments only — no behaviour change. They make
the existing architecture (narrow trait seams, pure helpers, stage
orchestration) legible to future readers without having to infer the
boundary from imports.

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

+142
+2
src/cli.rs
··· 1 1 //! Root clap parser and dispatch entry point. 2 2 3 + // pattern: Imperative Shell 4 + 3 5 use clap::Parser; 4 6 use miette::Result; 5 7 use std::process::ExitCode;
+2
src/commands.rs
··· 1 1 //! Top-level subcommand dispatch. 2 2 3 + // pattern: Imperative Shell 4 + 3 5 use clap::Subcommand; 4 6 use miette::Report; 5 7 use std::process::ExitCode;
+2
src/commands/test.rs
··· 1 1 //! `atproto-devtool test ...` subcommand tree. 2 2 3 + // pattern: Imperative Shell 4 + 3 5 use clap::Subcommand; 4 6 use miette::Report; 5 7 use std::process::ExitCode;
+2
src/commands/test/labeler.rs
··· 1 1 //! `atproto-devtool test labeler <target>` command. 2 2 3 + // pattern: Imperative Shell 4 + 3 5 pub mod pipeline; 4 6 pub mod target; 5 7
+8
src/commands/test/labeler/pipeline.rs
··· 1 1 //! Target parsing and pipeline driver skeleton for labeler conformance checks. 2 2 3 + // pattern: Mixed (unavoidable) 4 + // 5 + // Stage orchestration: drives every stage's `run` (which itself does I/O 6 + // through trait seams) and threads facts forward. Splitting orchestration 7 + // from per-stage gating logic would not improve testability — the 8 + // pipeline is already exercised end-to-end by `tests/labeler_*` with 9 + // fakes wired in. 10 + 3 11 use std::time::Duration; 4 12 5 13 use url::Url;
+2
src/commands/test/labeler/pipeline/create_report.rs
··· 4 4 //! The `sentinel` submodule builds the pollution-avoidance reason string 5 5 //! that every committed report body carries. 6 6 7 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 8 + 7 9 use std::borrow::Cow; 8 10 use std::sync::Arc; 9 11 use std::time::{Duration, SystemTime, UNIX_EPOCH};
+5
src/commands/test/labeler/pipeline/create_report/did_doc_server.rs
··· 9 9 //! Shuts down on drop: the RAII handle aborts the background task and 10 10 //! closes the listener. 11 11 12 + // pattern: Imperative Shell 13 + // 14 + // Pure-listener loop: binds a TCP socket and serves a fixed JSON 15 + // payload. No business logic; the DID document is built upstream. 16 + 12 17 use std::net::SocketAddr; 13 18 use std::sync::Arc; 14 19
+2
src/commands/test/labeler/pipeline/create_report/pollution.rs
··· 23 23 //! (pointing at the reporter's own DID) to exercise the simplest working 24 24 //! shape. This makes the test deterministic for round-trip debugging. 25 25 26 + // pattern: Functional Core 27 + 26 28 use serde_json::{Value, json}; 27 29 28 30 use crate::common::identity::Did;
+6
src/commands/test/labeler/pipeline/create_report/self_mint.rs
··· 2 2 //! server, and a reference curve. Exposes a single method for signing 3 3 //! atproto service-auth JWTs with that identity. 4 4 5 + // pattern: Mixed (unavoidable) 6 + // 7 + // Owns a signing key (pure crypto), a `DidDocServer` (imperative TCP), 8 + // and a JWT minting helper (pure). The mix mirrors the lifecycle: the 9 + // signer must keep the server alive while it's signing tokens. 10 + 5 11 use std::net::SocketAddr; 6 12 use std::time::Duration; 7 13
+5
src/commands/test/labeler/pipeline/create_report/sentinel.rs
··· 14 14 //! within a single `test labeler` invocation so operators can trace a group 15 15 //! of test reports back to one run. 16 16 17 + // pattern: Functional Core 18 + // 19 + // `build` and `format_rfc3339_utc` take time as a parameter; the 20 + // `SystemTime` import is for the type only. 21 + 17 22 use std::time::{SystemTime, UNIX_EPOCH}; 18 23 19 24 /// Prefix used so operators can grep their moderation queue for
+2
src/commands/test/labeler/pipeline/crypto.rs
··· 5 5 //! (deterministic canonical encoding per RFC 8949) and supports key rotation via 6 6 //! the did:plc audit log. 7 7 8 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 9 + 8 10 use std::borrow::Cow; 9 11 use std::collections::BTreeMap; 10 12
+2
src/commands/test/labeler/pipeline/http.rs
··· 3 3 //! Performs `com.atproto.label.queryLabels` requests against the labeler endpoint, 4 4 //! verifies schema conformance, and exercises pagination. 5 5 6 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 7 + 6 8 use std::borrow::Cow; 7 9 use std::sync::Arc; 8 10
+2
src/commands/test/labeler/pipeline/identity.rs
··· 3 3 //! Performs DID document resolution and labeler record validation, 4 4 //! emitting a series of named checks for each identity-layer requirement. 5 5 6 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 7 + 6 8 use std::borrow::Cow; 7 9 use std::sync::Arc; 8 10
+2
src/commands/test/labeler/pipeline/subscription.rs
··· 4 4 //! using a two-connection strategy: backfill with cursor=0, and live-tail if backfill 5 5 //! did not complete within the budget. 6 6 7 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 8 + 7 9 use std::sync::Arc; 8 10 use std::time::Duration; 9 11
+2
src/commands/test/labeler/target.rs
··· 1 + // pattern: Functional Core 2 + 1 3 use std::fmt; 2 4 3 5 use miette::Diagnostic;
+2
src/commands/test/oauth.rs
··· 1 1 //! OAuth conformance tests. 2 2 3 + // pattern: Imperative Shell 4 + 3 5 use std::process::ExitCode; 4 6 5 7 use clap::Subcommand;
+2
src/commands/test/oauth/client.rs
··· 6 6 //! spins up an in-process fake authorization server and observes the 7 7 //! client driving end-to-end OAuth flows. 8 8 9 + // pattern: Imperative Shell 10 + 9 11 pub mod fake_as; 10 12 pub mod pipeline; 11 13 pub mod target;
+7
src/commands/test/oauth/client/fake_as.rs
··· 1 1 //! In-process fake atproto OAuth authorization server for interactive mode. 2 2 3 + // pattern: Imperative Shell 4 + // 5 + // Owns the axum server lifecycle (bind, spawn, shutdown) and exposes 6 + // a `ServerHandle` through which tests address the AS by URL. Routing 7 + // and per-flow logic live in `endpoints` and `identity` (Mixed and 8 + // Functional Core respectively). 9 + 3 10 pub mod endpoints; 4 11 pub mod identity; 5 12 pub mod request_log;
+7
src/commands/test/oauth/client/fake_as/endpoints.rs
··· 1 + // pattern: Mixed (unavoidable) 2 + // 3 + // Axum handlers (Imperative Shell — accept the request, log it, return 4 + // the response) wrap the per-`FlowScript` decision logic (pure: which 5 + // status, which body, which header). The mix is deliberate: each handler 6 + // is short enough that splitting would obscure the request/response shape. 7 + 1 8 use std::collections::{HashMap, HashSet, VecDeque}; 2 9 use std::sync::{Arc, Mutex}; 3 10
+2
src/commands/test/oauth/client/fake_as/identity.rs
··· 1 + // pattern: Functional Core 2 + 1 3 use k256::ecdsa::SigningKey as K256SigningKey; 2 4 use serde_json::json; 3 5 use url::Url;
+6
src/commands/test/oauth/client/fake_as/request_log.rs
··· 1 + // pattern: Functional Core 2 + // 3 + // In-memory append-only log container. The `Mutex` provides thread-safe 4 + // state, not external I/O — timestamps are supplied by the caller, not 5 + // observed from `SystemTime` here. 6 + 1 7 use std::sync::Mutex; 2 8 3 9 /// Append-only log of inbound requests. Cloneable `Arc` handle; locks
+2
src/commands/test/oauth/client/pipeline.rs
··· 1 1 //! OAuth client conformance test pipeline and target parsing. 2 2 3 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 4 + 3 5 use super::target::OauthClientTarget; 4 6 5 7 pub mod discovery;
+7
src/commands/test/oauth/client/pipeline/discovery.rs
··· 3 3 //! This stage resolves the client metadata document for HTTPS targets or 4 4 //! synthesizes implicit metadata for loopback development clients. 5 5 6 + // pattern: Mixed (unavoidable) 7 + // 8 + // `run` is the imperative shell (does the HTTP fetch); the bulk of the 9 + // logic lives in pure helpers `evaluate_https_metadata_response` and 10 + // `evaluate_loopback_metadata`, which is why this stage has unit tests 11 + // at the bottom of the file alongside its integration coverage. 12 + 6 13 use std::borrow::Cow; 7 14 use std::sync::Arc; 8 15
+2
src/commands/test/oauth/client/pipeline/interactive.rs
··· 1 1 //! OAuth client interactive stage — fake AS server, RP-driven flow, conformance checks. 2 2 3 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 4 + 3 5 pub mod dpop_edges; 4 6 pub mod iss_sub_verification; 5 7 pub mod scope_variations;
+2
src/commands/test/oauth/client/pipeline/interactive/dpop_edges.rs
··· 2 2 //! 3 3 //! Verifies AC7.1–AC7.6: nonce rotation, refresh rotation, replay rejection, and compliance violations. 4 4 5 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 6 + 5 7 use std::borrow::Cow; 6 8 use std::fmt; 7 9 use std::sync::Arc;
+2
src/commands/test/oauth/client/pipeline/interactive/iss_sub_verification.rs
··· 18 18 //! behaviour on all other endpoints, a cooperating RP can reach the 19 19 //! final verification step before the broken-AS injection kicks in. 20 20 21 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 22 + 21 23 use crate::commands::test::oauth::client::fake_as::{ServerHandle, endpoints::FlowScript}; 22 24 use crate::common::oauth::relying_party::{AuthorizeOutcome, ParRequest, RelyingParty, RpError}; 23 25 use crate::common::report::CheckResult;
+2
src/commands/test/oauth/client/pipeline/interactive/scope_variations.rs
··· 2 2 //! 3 3 //! Verifies AC6.1–AC6.6: scope grant behaviors, user denial, refresh scoping, and mandatory fields. 4 4 5 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 6 + 5 7 use std::borrow::Cow; 6 8 use std::fmt; 7 9 use std::sync::Arc;
+2
src/commands/test/oauth/client/pipeline/jwks.rs
··· 3 3 //! Fetches and validates the client's JWKS (JSON Web Key Set) for confidential 4 4 //! clients using either an inline document or an external URI. 5 5 6 + // pattern: Mixed (unavoidable: stage orchestration interleaves trait-object I/O with check-result construction) 7 + 6 8 use async_trait::async_trait; 7 9 use miette::{Diagnostic, LabeledSpan, NamedSource}; 8 10 use reqwest::Client as ReqwestClient;
+6
src/commands/test/oauth/client/pipeline/metadata.rs
··· 4 4 //! every spec-derived static property of an atproto OAuth client metadata 5 5 //! document. 6 6 7 + // pattern: Functional Core 8 + // 9 + // `metadata::run` is synchronous — it consumes `DiscoveryFacts` (already 10 + // gathered by the discovery shell) and produces check results plus 11 + // downstream facts. No I/O. 12 + 7 13 use miette::{Diagnostic, NamedSource, SourceSpan}; 8 14 use serde::Deserialize; 9 15 use std::borrow::Cow;
+2
src/commands/test/oauth/client/target.rs
··· 1 + // pattern: Functional Core 2 + 1 3 use std::fmt; 2 4 3 5 use miette::{Diagnostic, NamedSource, SourceSpan};
+9
src/common/diagnostics.rs
··· 1 1 //! Shared miette configuration and `NamedSource` helpers. 2 2 3 + // pattern: Mixed (unavoidable) 4 + // 5 + // `install_miette_handler` mutates global process state via 6 + // `miette::set_hook` (a side effect, called once from `cli::run`); the 7 + // other helpers (`named_source_from_bytes`, `pretty_json_for_display`, 8 + // `span_at_line_column`, `span_for_quoted_literal`) are pure and could 9 + // live in a sibling file, but the module is small enough that splitting 10 + // would obscure the shared diagnostic concern. 11 + 3 12 use std::sync::Arc; 4 13 5 14 use miette::{GraphicalTheme, MietteHandlerOpts, NamedSource, SourceSpan};
+11
src/common/identity.rs
··· 3 3 //! This module provides a narrow interface over HTTP and DNS resolution, 4 4 //! allowing callers to swap real network I/O with recorded fixtures in tests. 5 5 6 + // pattern: Mixed (unavoidable) 7 + // 8 + // Pure: `parse_multikey`, `encode_multikey`, the `AnyVerifyingKey` / 9 + // `AnySigningKey` / `AnySignature` newtypes and their crypto methods, 10 + // `is_local_labeler_hostname`, all `IdentityError` construction. 11 + // Imperative shell: `RealHttpClient`, `RealDnsResolver`, `resolve_handle`, 12 + // `resolve_did`, `plc_history_for_fragment`. Splitting would force every 13 + // downstream stage to import from two modules and would not improve 14 + // testability — the I/O surface is already behind narrow trait seams 15 + // (`HttpClient`, `DnsResolver`). 16 + 6 17 use async_trait::async_trait; 7 18 use k256::ecdsa::signature::hazmat::PrehashVerifier; 8 19 use serde::{Deserialize, Serialize};
+2
src/common/jwt.rs
··· 7 7 //! Only ES256 and ES256K are supported (RFC 7518 §3.4); raw r||s signature 8 8 //! encoding, unpadded base64url segments, UTF-8 JSON payloads. 9 9 10 + // pattern: Functional Core 11 + 10 12 use base64::Engine; 11 13 use base64::engine::general_purpose::URL_SAFE_NO_PAD; 12 14 use serde::{Deserialize, Serialize};
+6
src/common/oauth/clock.rs
··· 1 + // pattern: Imperative Shell 2 + // 3 + // `RealClock::now_unix_seconds` reads `SystemTime::now()`, the canonical 4 + // non-deterministic side effect. The trait itself is the seam tests use 5 + // to inject `FakeClock`. 6 + 1 7 use std::time::{Duration, SystemTime, UNIX_EPOCH}; 2 8 3 9 /// A clock source. `RealClock` uses `SystemTime::now()`; tests inject
+2
src/common/oauth/jws.rs
··· 4 4 //! test-friendly JWS signing and JWKS parsing. Diagnostic codes in the 5 5 //! `oauth_client::jws::*` namespace enable stable error reporting downstream. 6 6 7 + // pattern: Functional Core 8 + 7 9 use std::sync::Arc; 8 10 9 11 use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey};
+9
src/common/oauth/relying_party.rs
··· 26 26 //! discover_as, and `http_no_redirect` for `do_authorize_inner`, which 27 27 //! manually inspects the first 3xx response. 28 28 29 + // pattern: Mixed (unavoidable) 30 + // 31 + // Pure: `parse_as_descriptor`, `verify_redirect_iss`, `extract_auth_response`, 32 + // `new_pkce`, `new_jti`, `sign_dpop`, `sign_private_key_jwt`. Imperative 33 + // shell: every `do_*` flow method (PAR / authorize / token / refresh) 34 + // drives reqwest. The mix is deliberate — see the module-level docs above 35 + // for why the RP holds its own reqwest clients rather than going through 36 + // the project's `HttpClient` seam. 37 + 29 38 use std::collections::HashMap; 30 39 use std::sync::{Arc, Mutex}; 31 40 use std::time::Duration;
+2
src/common/report.rs
··· 1 1 //! Report aggregation and rendering for the labeler conformance suite. 2 2 3 + // pattern: Functional Core 4 + 3 5 use std::borrow::Cow; 4 6 use std::fmt; 5 7 use std::io;
+2
src/main.rs
··· 1 1 //! `atproto-devtool` binary entry point. 2 2 3 + // pattern: Imperative Shell 4 + 3 5 use miette::Result; 4 6 use std::process::ExitCode; 5 7