Expand description
HTTP surface and the development server.
This crate is the outside of the system: it turns provisioning into
[axum] routes, and turns what the provisioner does into a log a developer
can read while it happens.
The whole surface is built around one object-safe trait,
didbot_pds::Registry. The provisioner is generic over its DNS provider and its
account store; the HTTP layer must not be, or every handler signature
carries type parameters that no route cares about. Arc<dyn Registry> is
where that genericity stops.
§Running it
The didbot-pds binary in this crate wires a development stack –
loopback DNS, an in-memory store – and serves it, so that one command
produces a server for didbot-swarm to drive:
cargo run -p didbot-serve --bin didbot-pds§Routes
| Route | Purpose |
|---|---|
GET / | on the server’s own hostname, a page naming it, its zones and its claim; elsewhere the same 404 as any unserved path |
GET /health | liveness, and which zone this server mints under |
GET /.well-known/did.json | the DID document for the request’s Host |
GET /.well-known/atproto-did | the DID of the account whose handle is the request’s Host, 404 when nobody holds it |
POST /xrpc/bot.did.createAccount | create an account beneath the creator the credential names |
POST /xrpc/bot.did.createSession | a session as a hosted account, from a key-signed JWT or an OpenID Connect token |
POST /xrpc/bot.did.parkKey | hold a public key for minutes, for the operator to admit under a name |
GET /xrpc/bot.did.describeParkedKey | the key parked under a name |
POST /xrpc/bot.did.deleteAccount | erase one: data gone, identity kept |
POST /xrpc/bot.did.freezeAccount, unfreezeAccount | hang or lift the account’s own frozen lock |
POST /xrpc/bot.did.deactivateAccount, activateAccount | hang or lift the account’s own deactivated lock |
GET /xrpc/bot.did.listAccounts | list them |
POST /xrpc/bot.did.pollOperatorClaim | ask this server to look for its operator’s claim now — no credential, because the nudge confers nothing; see auth |
POST /xrpc/com.atproto.repo.createRecord | write a record to a repository |
POST /xrpc/com.atproto.repo.putRecord | write one at a key you choose |
POST /xrpc/com.atproto.repo.deleteRecord | remove one, or make sure it is gone |
POST /xrpc/com.atproto.repo.applyWrites | a batch of the three above |
GET /xrpc/com.atproto.repo.getRecord | one record by collection and key |
GET /xrpc/com.atproto.repo.listRecords | read a collection back, newest first |
GET /xrpc/com.atproto.repo.describeRepo | the account, its document and its collections |
GET /xrpc/bot.did.stats | accounts, records, bytes per collection, blobs by lifecycle state, the policy revision enforced |
GET /xrpc/com.atproto.server.describeServer | this server’s DID and its real zone-derived handle domains |
GET /xrpc/com.atproto.server.getServiceAuth | a service-auth token signed as the caller’s account |
GET /xrpc/com.atproto.server.getSession | the account the caller’s credential signs in as: its DID, handle and status |
GET /xrpc/com.atproto.identity.resolveHandle | the DID a handle points at, hosted here or not |
GET /xrpc/com.atproto.identity.resolveDid | a DID’s document, hosted here or not |
GET /xrpc/app.bsky.actor.getPreferences | the account’s preferences: none, since this server stores none |
POST /xrpc/app.bsky.actor.putPreferences | MethodNotImplemented, and never proxied |
any other /xrpc/ method, with atproto-proxy | the call, forwarded to the service that header names; see proxy |
any other app.bsky.* method, without it | the same, to [proxy] app_view when one is set |
Every repo above is an at-identifier: a DID or a handle this deployment
issued. Which credential each route needs, if any, is
auth::ROUTE_CREDENTIALS — the read surface stays Public, and every
com.atproto.repo.* write route takes the agent token createAccount
hands back; see that module’s own doc for the full taxonomy.
Failures are returned in atproto’s error shape, {"error":..,"message":..},
with the names the lexicons declare – RecordNotFound, RepoNotFound,
InvalidRequest, InvalidRecord, MethodNotImplemented. See ApiError
for the whole mapping, and for the two conditions that get a name no lexicon
declares.
Re-exports§
pub use blob_collect::collect_once;pub use blob_collect::BlobCollector;pub use blob_collect::DEFAULT_COLLECT_INTERVAL;pub use confinement_poll::ConfinementPoll;pub use confinement_poll::DEFAULT_CONFINEMENT_INTERVAL;pub use lookups::DeclaredLookups;pub use lookups::Reader as LookupReader;pub use operator_handle::OperatorHandle;pub use operator_poll::OperatorPoll;pub use operator_poll::OperatorRecords;pub use operator_poll::PollNudge;pub use operator_poll::PollSchedule;pub use operator_poll::MIN_POLL_INTERVAL;pub use policy_poll::list_records;pub use policy_poll::PolicyPoll;pub use policy_poll::PolicyPollError;pub use policy_poll::PollError;pub use stale_sweep::sweep_once;pub use stale_sweep::StaleSweeper;pub use stale_sweep::SweepSchedule;pub use stale_sweep::DEFAULT_SWEEP_INTERVAL;pub use stale_sweep::DEFAULT_SWEEP_LIMIT;pub use stale_sweep::DEFAULT_UNFINISHED_AGE;pub use tree_reload::TreeReloader;pub use tree_reload::DEFAULT_TREE_RELOAD_INTERVAL;
Modules§
- analytics 🔒
- The analytics dashboard: this deployment’s accounts, drawn as the tree they form.
- auth 🔒
- The credential taxonomy: what this server accepts, and what each route
requires. See
plan/auth-types.md. - blob_
collect - The timer that runs
didbot_pds::Provisioner::collect_blobs. - blobs 🔒
- The three blob methods, and the streaming that keeps them honest.
- confinement_
poll - The confinement poll: re-derives every inherited lock from the tree of principals on a schedule.
- dashboard 🔒
- The per-server homepage and operations dashboard: this server, reporting
on itself.
docs/web-surfaces.mdcalls this surface 3 of the project’s four websites, and draws the boundary this module keeps: it is served bydidbot-serve, about the deployment serving it, one per deployment rather than one for the whole project, and it owns no policy — policy is written from a separate, statically-hosted origin (surface 2) and only ever read here, never edited. - dashboard_
accounts 🔒 - The operations dashboard’s account view: every account this deployment holds, and one account in full.
- dashboard_
apps 🔒 - The operations dashboard’s per-app view: what each OAuth application holds on this server, and what it did.
- error 🔒
- The error body every failing route returns.
- foreign 🔒
- The deployment’s one reader for records in repositories it does not hold.
- handles
- Forward handle resolution: the DID a handle points at.
- health 🔒
- The periodic line that says what the deployment currently holds, and the
state behind
GET /health’s progress report. - home 🔒
GET /on the server’s own hostname: a page naming this server, its zones and who stands behind it.- landing 🔒
- Serving
didbot_brand::page: every page a browser lands on during a sign-in here, the operator’s (crate::dashboard) and an account’s (crate::routes::oauth), and every refusal it can meet on the way. - lookups
- Reading the records a write’s policies declared they read, before the write is judged.
- oauth
- OAuth 2.0 for atproto (
plan/oauth.md). - operator
- Signing in as the operator, over atproto OAuth, to reach this server’s operator controls.
- operator_
handle - The handle the operator goes by, for the homepage.
- operator_
poll - Drives
didbot_pds::OperatorRelation’s poll against a real operator repository, and reacts to what it finds. Seeplan/handshake.md. - policy_
poll - The policy poll: the operator’s
bot.did.policyandbot.did.policyBindingrecords, read on the operator poll’s tick and built into one policy set. - proxy 🔒
atproto-proxy: an XRPC call this server does not serve, forwarded to the service a DID document names.- rate_
limit - A minimal fixed-window rate limiter, shared by every anonymous route that does real work before it knows who is calling.
- refusal 🔒
- Refused OAuth and XRPC requests, as the journal says them.
- relay
com.atproto.sync.requestCrawlandnotifyOfUpdate, called outward.- replay
- The bounded “spent once” map both proof checks keep.
- routes 🔒
- The router: every route the development server answers.
- service_
auth 🔒 com.atproto.server.getServiceAuth, and the rules a proxied call shares with it; seecrate::proxy.- stale_
sweep - The timer that runs
didbot_pds::Provisioner::sweep_stale_bounded. - subscribe 🔒
com.atproto.sync.subscribeRepos: this server’s repositories, as atproto wants to hear about them.- subscribers
- How many streams this server holds open at once, and how many of them one caller may hold.
- surface 🔒
- What the two dashboards this server serves do the same way.
- tree
- The ownership tree over HTTP: creating an account beneath a creator, parking a key for the operator to admit, and a session from a proof.
- tree_
reload - The timer that re-reads
[tree]’s bounds from the configuration file. - wire 🔒
- The JSON shapes the XRPC routes read and write.
- write_
meter - Per-account write metering: one token bucket on write operations and one on request bytes, both keyed by the authenticated account.
Structs§
- Account
Summary - One entry in the
listAccountsresponse. - ApiError
- A failure rendered as an HTTP status plus an
ErrorBody. - Apply
Writes Request - Body of
com.atproto.repo.applyWrites. - Auth
State - Session storage, the e-stop latch and the operator credential, bundled so
a caller building
app_with_authhands them over together. - Blobs
View - The
blobsmember ofStatsResponse: this deployment’s blobs, by the state each one is in. - Certificate
Health - What
GET /healthreports about certificate renewal. - Connect
Info 🔒Service - The per-connection service
MakeServiceWithConnectInfohands back:inner, unchanged, withConnectInfo(addr)inserted into every request’s extensions before the request reaches it. - Create
Account Request - Body of
bot.did.createAccount. - Create
Record Request - Body of
com.atproto.repo.createRecord. - Create
Record Response - Response of
com.atproto.repo.createRecord. - Delete
Record Request - Body of
com.atproto.repo.deleteRecord. - Describe
Repo Query - Query of
com.atproto.repo.describeRepo. - Describe
Repo Response - Response of
com.atproto.repo.describeRepo. - Describe
Server Response - Response of
com.atproto.server.describeServer. - DidRequest
- Body of
bot.did.deleteAccount, and of the freeze and deactivate routes and their inverses. - Disclosure
- Per-route configuration for every
Credential::Disclosureroute: open (public) or closed (operator-only), one flag per route rather than one flag for all of them. - Disk
Health - What
GET /healthreports about the data volume. - Disk
Watermarks - The free space, as a percentage of the data volume, below which
GET /health’sdisk.alertand the journal raise an alert. The[capacity] disk_*_free_percentkeys set them. - Error
Body - An atproto XRPC error body: a machine-readable name and a human sentence.
- Exclusions
- Which message types one connection wants.
- Fragment
- Text for one slot of a pool’s naming scheme.
- Frame
- One numbered frame, already encoded.
- GetRecord
Query - Query of
com.atproto.repo.getRecord. - GetRecord
Response - Response of
com.atproto.repo.getRecord. - Health
Snapshot - What
GET /healthreports beyond liveness: whether the tick that logs this deployment’s own pulse is still advancing, and how recently. - Health
State - The tick’s pulse, shared with whoever answers
GET /health. - Health
Tick - A running health tick, stopped by
HealthTick::stopor by dropping it. - Lifecycle
Line - What this deployment is doing right now, for a reader with no credential.
- List
Records Query - Query of
com.atproto.repo.listRecords. - Make
Service 🔒With Connect Info - Turns an already-
Cloneservice —limited_make_service’s [ConcurrencyLimit]-wrapped router — into a [tower::Service]axum::serveandaxum_servercan both drive per accepted connection, the same way [tower::make::Shared] does, but also carrying a [ConnectInfo] extension for the connection’s peer address, whichcrate::rate_limit::caller_keyreads by default. - Mime
Pattern - A MIME pattern:
type/subtype, with either half (or both) as*. - Park
KeyRequest - Body of
bot.did.parkKey. - Parked
KeyQuery - Query of
bot.did.describeParkedKey. - Policy
Binding View - One
bot.did.policyBindingrecord in force, and whom it reaches. - Policy
Record View - One
bot.did.policyrecord in force. - Policy
Refusal View - One record a build would not honour, and why.
- Policy
View - The
policymember ofStatsResponse. - Proxy
Limits - The per-account bounds on proxied calls, and the cache of targets’ endpoints, for one deployment.
- Proxy
Settings - The bounds on proxied calls, and where an
app.bsky.*call with noatproto-proxygoes; seeProxyLimits. - PutRecord
Request - Body of
com.atproto.repo.putRecord. - Record
View - One entry in the
listRecordsresponse. - Replay
Bounds - How much the replay buffer holds. It evicts its oldest frame when a new one would take it past either bound, unless an open stream has not sent that frame yet. Those it keeps up to twice the bounds. See the module docs.
- Repos
- The producer: the sequence and the replay buffer consumers read from.
- Repos
Stream - One consumer’s stream: the replay, then whatever arrives live, both read from the replay buffer.
- Scope
Refused - The exact scope a ceiling refused, for
insufficient_scope. - Scope
Set - A space-separated set of
Scopes, as carried on ascopeparameter or a token response. - Stats
Response - Response of
bot.did.stats. - Stop
Handlers 🔒 - The handlers for the signals that stop this process, registered when this is constructed rather than when it is awaited.
- Stopping
- One observation of the stop signal for a whole run, installed once at its start and awaited by everything that stops on it.
- Strict
Transport Security - A
Strict-Transport-Securityheader; see[tls] hsts_max_age_secsand[tls] hsts_include_subdomainsindidbot-config. - Swap
- The compare-and-swap fields, as they arrive on the wire.
- Tally
View - How many blobs, and what they occupy.
- Xrpc
Body - A buffered request body, refused in the atproto shape.
- Zone
Certificate Health - One zone’s entry in
CertificateHealth::zones.
Enums§
- Account
Proof - What the account’s own DID document offers a stranger as proof of being
it, beside the
#atprotokey this server signs its commits with. - Action
- One action a
repo:scope’saction=query may name.account:has its own, incrate::AccountAction. - Action
Set - The set of actions a
repo:scope grants. - Credential
- A credential kind a route may declare it accepts.
- Disk
Alert - Which free-space watermark the data volume is below.
- Edge
State - Whether the edge from an account to its operator still stands.
- Frame
Kind - Which of the lexicon’s message types a frame is.
- Nsid
Pattern - An NSID pattern: exact, a recursive prefix wildcard, or the bare
*. - Repo
Write - One operation in a
com.atproto.repo.applyWritesbatch. - Scope
- One parsed scope atom.
- Scope
Parse Error - A failure parsing one scope atom or a whole scope string.
- Transition
- The three legacy
transition:*scopes. - Write
Result - One entry in the
applyWritesresults, one per write, in order.
Constants§
- APP_
BSKY_ METHODS - The
app.bsky.*methods this server serves itself rather than proxies, as the reference PDS does: the account’s preferences. - ATPROTO_
METHODS - Every
com.atproto.*method this router answers, in NSID order. - BOT_
DID_ METHODS - The
bot.did.*methods this server serves — its own account admin surface, alongside thecom.atproto.*oneATPROTO_METHODSnames. - DEFAULT_
HEALTH_ INTERVAL - How often the health line is written when a caller does not choose.
- DEFAULT_
LOG_ FILTER - Filter used when
RUST_LOGis unset. - DEFAULT_
REPLAY_ BYTES - How many encoded bytes the replay buffer holds, if a deployment does not choose.
- DEFAULT_
REPLAY_ FRAMES - How many frames the replay buffer holds, if a deployment does not choose.
- FUTURE_
CURSOR - The lexicon’s error for a cursor ahead of the stream.
- HEADER_
READ_ TIMEOUT - How long a connection has to finish sending its request headers.
- MAX_
CONCURRENT_ REQUESTS - How many requests this server answers at once before a new one waits for a slot to free up.
- MAX_
LIST_ LIMIT - The most records
listRecordswill return in one answer. - MAX_
REQUEST_ BODY - The largest request body this server will hold in memory.
- NEVER_
WROTE_ MIN_ IDLE - The shortest
idleForaneverWrotedelete takes, unless[operator] never_wrote_min_idle_secssets another. An agent’s account is created before its session makes its first write, and an hour is well past that. - OUTDATED_
CURSOR - The one
namethis server ever sends on an#infoframe. - ROUTE_
CREDENTIALS - Every
com.atproto.*,app.bsky.*andbot.did.*method this router serves, and the credential it declares. See the module doc for what each kind means. - SHUTDOWN_
GRACE - How long a shutdown waits for connections that are still open before it
stops anyway, unless the caller names its own bound;
didbot-pdsreads[server] drain_secs. - STREAM_
CLOSE_ GRACE - How long a shutdown waits, once serving has ended, for each
subscribeReposconnection to send what it holds and close. - TURN_
HEADER - The header a client may name the turn a request belongs to in.
- TURN_
HEADER_ MAX_ LENGTH - The longest turn identifier this server will put in a log line.
Traits§
- Certificate
Alerts - Reads back each zone’s certificate alert level.
Functions§
- app
- Builds the router.
- app_
with_ auth - Builds the router with every collaborator supplied explicitly.
- app_
with_ health - Builds the router with the health pulse wired to an existing producer.
- app_
with_ repos - Builds the router with
com.atproto.sync.subscribeReposattached too. - cross_
origin_ layer - The cross-origin policy on the public protocol surface.
- encode_
error - Encodes one error frame, which closes the connection after it.
- encode_
message - Encodes one message frame: the header, then the body.
- grace_
expired 🔒 - Resolves
SHUTDOWN_GRACEafterstartedfires, and never if it does not. - init_
tracing - Installs the tracing subscriber.
- installed_
shutdown_ signal - Resolves when this process is asked to stop:
SIGTERM, or Ctrl-C. The handlers are registered before this returns. - limited_
make_ 🔒service - What both listeners hand each accepted connection:
routerbehindMAX_CONCURRENT_REQUESTS, carrying the peer’s address as [ConnectInfo]. - make_
service_ 🔒limited_ to limited_make_serviceat a limit a test can overrun with two requests.- record_
uri - The
at://URI naming one record. - refuse_
skipped_ validation - The
validateflag, which is atproto’s own name for the three stances. - repo_
surface - The same mapping, as the
com.atproto.repo.*surface reports it. - serve_
router - Serves an already-built router on
listeneruntilSIGINTorSIGTERM, waiting at mostSHUTDOWN_GRACEon connections that will not close by themselves. - serve_
tls - Serves an already-built router on
listener, terminating TLS withconfig, untilSIGINTorSIGTERM, on the sameSHUTDOWN_GRACEserve_routergives the plain-HTTP path. - serve_
tls_ 🔒inner - serve_
tls_ until serve_tls, stopping whenstoppedresolves rather than on a signal observed from here on, and waitingdrainrather thanSHUTDOWN_GRACEfor requests in flight. A process whose startup runs after its handlers are installed passesStopping::wait, so a stop sent during that startup is not lost.- serve_
tls_ with_ header_ timeout serve_tls, with the header-read timeout named rather than taken fromHEADER_READ_TIMEOUT.
Type Aliases§
- Limited
Make 🔒Service - The service both listeners are driven by; see
limited_make_service.