From 097e5b815bd778dfcbf2fc053c512cb06877b8ec Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Sat, 5 Sep 2026 15:37:33 -0400 Subject: [PATCH] docs(auth-types): name the credential the repo write routes take `getSession`'s doc and `docs/architecture.md` both said the `com.atproto.repo.*` write surface was open, and `AccountMismatch` offered the caller an operator credential. Those routes take an agent token, and every account-lifecycle mutation is self-service. Co-Authored-By: Claude Opus 5 (1M context) Change-Id: I12db5bfb8097c3d09350d616ccf6770d7e7fbbc9 --- crates/didbot-reconcile/src/lib.rs | 17 ++++++++++------- crates/didbot-serve/src/error.rs | 16 +++++++--------- crates/didbot-serve/src/wire.rs | 8 ++++---- docs/architecture.md | 23 +++++++++-------------- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/crates/didbot-reconcile/src/lib.rs b/crates/didbot-reconcile/src/lib.rs index 761c1d0c..c7ebbc29 100644 --- a/crates/didbot-reconcile/src/lib.rs +++ b/crates/didbot-reconcile/src/lib.rs @@ -322,13 +322,16 @@ where /// which turns a failing list call into [`ViewError::Unreadable`] rather /// than a zone that looks empty, is necessary and is not sufficient. /// -/// **`Route53Dns::resync` does not meet that bar today.** It hydrates: it -/// inserts what it finds, discards a collision with what is already cached, -/// and removes nothing. Wired here it would leave a reconciler that reads a -/// zone forever and reports it in sync — blind to a record deleted at the -/// provider's console and blind to one repointed there, which are the two -/// drifts this crate exists to repair. Anything wiring a remote provider up -/// to a reconciler owes it an authoritative read first; see +/// **`Route53Dns::resync` meets that bar for the types a zone view reads.** +/// Within the names its `accepts` covers, it drops a name the zone no longer +/// holds and takes the zone's value for one repointed at the provider's +/// console — the two drifts this crate exists to repair — and a read that +/// fails partway through leaves the previous bookkeeping standing rather +/// than a half-read zone that looks emptied. Its `TXT` hydration is the one +/// place it adds without removing, and [`ZoneReconciler`] excludes `TXT` by +/// construction, so the pair stays consistent. +/// +/// A remote provider wired here owes the same guarantee; see /// `plan/onboarding.md`. pub struct ProviderView

{ provider: Arc

, diff --git a/crates/didbot-serve/src/error.rs b/crates/didbot-serve/src/error.rs index 546c1b7e..1657a11c 100644 --- a/crates/didbot-serve/src/error.rs +++ b/crates/didbot-serve/src/error.rs @@ -106,24 +106,22 @@ impl ApiError { ) } - /// The authenticated agent is not the account this admin route names, - /// and no operator credential was presented either. + /// The authenticated agent is not the account this admin route names. /// /// `AccountMismatch`, its own name for the same reason [`Self::repo_mismatch`] /// has one: an agent token is real and unexpired, and what failed is - /// *whose* account this is. `deleteAgent` and `setAgentPinned` accept an - /// agent's own credential for acting on itself and an operator credential - /// for acting on any account — see `plan/auth-types.md`'s "the credential - /// that unlocked it, again" — and this is what a self-service credential - /// gets for naming somebody else's account. + /// *whose* account this is. Every account-lifecycle mutation is + /// self-service — an agent's own credential, checked against the account + /// the request names, is the whole of what authorizes them; see + /// `plan/auth-types.md`. This is what that credential gets for naming + /// somebody else's account. pub fn account_mismatch(authenticated: &str, target: &str) -> Self { Self::new( StatusCode::FORBIDDEN, "AccountMismatch", format!( "`{authenticated}` may not act on `{target}`'s account; an agent's own \ - credential only authorizes acting on itself. An operator credential may \ - act on any account." + credential only authorizes acting on itself" ), ) } diff --git a/crates/didbot-serve/src/wire.rs b/crates/didbot-serve/src/wire.rs index d225021a..68be9ae6 100644 --- a/crates/didbot-serve/src/wire.rs +++ b/crates/didbot-serve/src/wire.rs @@ -156,10 +156,10 @@ pub struct SessionResponse { /// Response of `com.atproto.server.getSession`. /// -/// The one route today that actually requires a legacy session's access -/// token, checked against `Authorization`: every `com.atproto.repo.*` write -/// route stays open for now, so this is where "the session that was just -/// created answers for the account it names" is checked end to end. +/// The one route that requires a legacy session's access token, checked +/// against `Authorization`. `com.atproto.repo.*`'s writes take an agent +/// token instead, so this is where "the session that was just created +/// answers for the account it names" is checked end to end. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GetSessionResponse { diff --git a/docs/architecture.md b/docs/architecture.md index 61712db5..4c681543 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,21 +1,16 @@ # The shape of the system -Four pictures of where things run and what talks to what. This page is the -intended shape, not a report on what is finished: the repository layer and the -sync surface are built, and the OAuth authorization server, the policy engine -and both dashboards are not. Where a diagram shows something unbuilt it is -drawing a decision, and the decision lives in the epic under `plan/` that owns -it. +Four pictures of where things run and what talks to what. This page draws the +intended shape. Each picture is a decision, and the decision lives in the epic +under `plan/` that owns it; [plan/README.md](../plan/README.md) is the register +of what each epic has reached. -What has moved: this server has a credential and a session lifecycle where it -had none. [auth-types](../plan/auth-types.md) built legacy sessions +Every route declares which credential it requires, and +`crates/didbot-serve/src/auth.rs` holds that table: public reads, +`LegacySession` for session management itself (`com.atproto.server.{create,refresh,delete,get}Session`, app passwords hashed -with Argon2id) and a small, explicit table of which credential each route -requires — public reads, `LegacySession` for session management itself, and -public for now on the `com.atproto.repo.*` write surface, which is that -epic's own open item: a client writes an agent's own records by calling those -routes directly with no session to present, so switching them to require one -is real follow-up work rather than a flag to flip. +with Argon2id), and an agent token on the `com.atproto.repo.*` write surface. +[auth-types](../plan/auth-types.md) is the epic that owns it. [The trust model](trust-model.md) says what the system can and cannot prove. -- 2.51.2