architecture #
zds is a Zig implementation of an AT Protocol Personal Data Server.
The server owns account state, repo state, sessions, OAuth authorization,
service auth, blobs, sync output, and identity operations. It does not implement
an appview. Appview requests are forwarded through the generic atproto-proxy
path: to the service the proxy header names, or to the configured appview for
app.bsky.* methods sent without one.
compatibility #
Start with the AT Protocol specifications and cross-check the reference PDS.
Document intentional differences. Experimental Spaces APIs track the upstream
alpha behind ZDS_PERMISSIONED_DATA and can change incompatibly.
Routing, /api, and /api/openapi.json use the same comptime endpoint inventory.
boundaries #
zatowns AT Protocol primitives: syntax, TIDs, DID/handle resolution, JWT helpers, DAG-CBOR, CAR, MST, repo verification, OAuth helpers, and streaming clients.httpzowns the HTTP/1.1 server mechanics: request parsing, response serialization, connection handling, upgrades, and server-side websocket plumbing. ZDS adapts AT Protocol handlers onto that server boundary.zdsowns PDS policy and persistence: accounts, repo writes, sessions, app passwords, passkeys, storage, server routes, sync production, account migration, and the local login/security surface.- Dependency fixes should happen at the owning layer. Since this project also
maintains
zat, ZDS should consume a fixedzatcommit rather than vendoring or patching a private copy. - Spaces HTTP-signature parsing and profile validation stay in
src/internal/space_signature.zig;zatsupplies DID-key decoding and JOSE verification. This is a narrow Spaces profile, not a general HTTP-signature API. - SQLite stores account, repo, commit, token, OAuth, blob metadata, and identity state, plus Spaces revocations, authority sequencing, and pending notifications.
- Password sessions are durable rows keyed by access and refresh JWT IDs. Refresh rotates the stored token family instead of trusting self-contained JWTs alone.
- Account-plane audit events store subject, actor, and optional controller DID fields. That mirrors Tranquil's delegation-ready model without exposing user delegation before the surrounding account controls exist.
- Invite codes follow the official PDS split between code metadata and recorded uses; account creation consumes a code while holding the store lock.
- Blob bytes live in the disk blobstore rooted at
ZDS_BLOBSTORE_PATH. - Passkey storage follows Tranquil's core shape: credential ID, public key, signature counter, friendly name, and timestamps. Passkeys are outside the official PDS surface ZDS tracks for compatibility.
repo write invariants #
Repo writes must match reference PDS behavior:
$typemust match the collection being written.- omitted rkeys are generated as TIDs.
- known
key: "tid"records reject explicit non-TID rkeys. - known
literal:selfrecords require rkeyself. - known records return
validationStatus: "valid". - unknown lexicons return
validationStatus: "unknown"unless a known schema is available and validation succeeds. - writes update the MST, write a signed commit block, emit sync events, and request relay crawls.
These rules are grounded in bluesky-social/atproto/packages/pds record
preparation and Tranquil's validation/conformance model.
sync #
subscribeRepos and sync read endpoints expose the local repo state. Relay
visibility depends on valid repo commits, coherent event frames, and crawl
requests. ZDS_CRAWLERS should include both https://bsky.network and
https://vsky.network for current deployment coverage.
auth and login #
OAuth follows the ATProto OAuth profile: PAR is required, redirect URIs are
validated against client metadata, permission-set includes must resolve, tokens
enforce granular repo/blob/rpc/account/identity/space scopes, DPoP-bound OAuth access
tokens must present matching proof headers on resource requests, and revocation
affects resource-server checks. Redirect matching is exact, with the RFC 8252
loopback exception the reference provider applies: a client whose metadata says
application_type: native and registers http://127.0.0.1/callback (or the
[::1] form) without a port matches that redirect on whatever port it bound at
request time. A registered port still has to match and localhost is not a
loopback host for this redirect exception. Separately, the virtual OAuth client
ID http://localhost supports the ATProto localhost development profile, with
loopback-IP redirect URIs and metadata supplied in its query string.
Password sessions and app passwords match the reference PDS account model.
Session JWTs are accepted only when their JTI is present in the active session
table, so revocation and refresh rotation can be enforced by storage. The
answers a client gets for an expired, revoked, or replayed token are in
account security.
App-password sessions are attributed to the app-password name, and revoking the
credential revokes those sessions too. The session schema already carries an
optional controller_did column for future delegated-account flows, following
Tranquil's controller-vs-subject split.
Passkeys are an additional OAuth login credential inspired by Tranquil, backed
by the local webauthn dependency and stored in SQLite. Discoverable passkey
login is supported so an account can be chosen by the authenticator during the
OAuth flow.
/account is the resident management surface. Its security section,
/account/manage, manages passkeys and app passwords with account-password
authentication, matching the official PDS boundary that app passwords cannot
mint or revoke other app passwords.
PDS-owned app.bsky.actor preferences are handled locally because the official
PDS implements those routes. Appview reads and writes remain behind the generic
atproto-proxy path.
account web surfaces #
Follow the reference PDS account manager's categories: Home, Account, Devices,
Apps, About. ZDS currently exposes Home, Account, Apps, About. Credentials
(passkeys and app passwords), email, and account status belong together under
/account/manage; /account/security, /security, and /passkeys redirect
there. App access uses /account/apps, with /account/sessions retained as an
alias. Do not add a separate security/settings dashboard for each feature.
The experimental /account/spaces data viewer remains reachable from Home but
is not an account-management navigation section. A future device-management
view should use the reference Devices category instead of introducing another
settings concept.
Reference: bluesky-social/atproto,
packages/oauth/oauth-provider-ui/src/routes/account/u/$accountId/route.tsx
(inspected 2026-09-05).