development #
This note records codebase conventions that are easy to lose while moving quickly.
source layout #
src/main.zigis process setup. Keep parsing and setup helpers incoreorinternalsomainstays readable.src/coreis small shared PDS infrastructure: config, logging, syntax, XRPC helpers, mail, and repo helpers.src/httpowns routing and HTTP response shape. Endpoint behavior belongs in the protocol modules rather than in the router.src/atprotoowns the AT Protocol XRPC surface: server, repo, sync, OAuth, identity, preferences, and generic proxying.src/storageowns SQLite persistence, repo mutation, event sequencing, and blob metadata. Runtime code reads record bodies fromrepo_blocks, not from cached JSON columns.src/internalis ZDS-local reusable glue. Good candidates are CLI parsing, sharded synchronization, passkey adapters, and narrow encoding adapters. Protocol primitives still belong inzatonce they are general enough.benchandtoolscontain repeatable local probes. Prefer Just targets over one-off command lines.
dependency boundary #
Treat ZDS as an application and zat as the sibling library we maintain for
reusable AT Protocol code. ZDS should depend on zat; it should not copy,
vendor, or fork zat to make local package graph problems disappear.
- Use
zatfor atproto primitives: syntax, TIDs, DID and handle resolution, JWT helpers, DAG-CBOR, CAR, MST, repo verification, OAuth helpers, JSON helpers, and streaming clients. - Use
httpzfor the PDS HTTP/1.1 server boundary: accept loops, request parsing, response writing, upgrades, and server-side websocket integration. - Use
webauthnfor browser credential ceremony verification. ZDS owns the account policy, challenge storage, and UI that surround it. - Use
zqlitefor SQLite access. ZDS owns the schema and transaction policy.
When ZDS needs a generally useful primitive that zat does not expose yet,
build the smallest local version under src/internal, use it from ZDS, and
document why it is likely upstream material. Once the shape is clear, upstream
it into zat and repin ZDS to the new commit or release.
When dependency packages disagree, fix the package that owns the disagreement.
Because we maintain zat, dependency alignment problems in zat should be
fixed in the zat repo and consumed here by pinning the resulting commit. Do
not vendor zat into ZDS. Do not vendor httpz either; consume it directly
from Tangled and send focused fixes upstream when its HTTP or websocket behavior
needs to change.
Keep the websocket graph singular. httpz and zat should both use the
canonical first-party zzstoatzz.io/websocket.zig package so ZDS does not pull
two websocket implementations into one build.
Vendoring is only acceptable as a temporary local debugging move. Before a
change is considered ready, remove vendor/, express dependencies in
build.zig.zon, and verify the project from a clean package graph.
For generated JSON, prefer typed structs plus std.json.Stringify.valueAlloc
or a narrow helper over hand-written object strings. std.json.fmt is still
useful at stream boundaries, but new response code should avoid assembling JSON
objects with format strings when a structured value is straightforward.
lessons from history #
- Browser behavior wins over assumptions. If a client reports a failure, check the actual request, response, and browser console before deploying a fix.
- Appview behavior belongs behind the generic
atproto-proxypath. Do not add local Bluesky appview route implementations. - Unknown record schemas may be accepted, but must not be reported as
validationStatus: "valid"without validation. - OAuth client assertions are JOSE signatures. Accept standard raw ECDSA JWS signatures for OAuth without weakening stricter repo-signature verification.
- Passkey support should stay close to Tranquil's WebAuthn model: account-owned credential rows plus challenge rows, with browser prompts opened directly from user gestures.
- Account-plane security state should stay durable. Password sessions are not just signed JWTs; storage tracks active access/refresh JTIs, refresh rotation, revocation state, auth method, and optional future controller DID.
- When adding account-management behavior, record subject DID, actor DID, and optional controller DID separately. That keeps the path toward Tranquil-style delegated accounts explicit instead of embedding policy in ad hoc token strings.
- Storage shape follows the official PDS SQLite model where practical: record index rows identify current CIDs, and canonical record content is stored as DAG-CBOR blocks.
- Benchmark tables should only compare equivalent units of work. Put adjacent probes in a separate section rather than mixing them into one matrix.
local state #
Local SQLite files under dev/ are ignored workspace state. Do not commit
database files, WAL files, .env, .zig-cache, zig-out, or package cache
directories.
commands #
The root justfile is the stable task surface:
just test
just smoke
just invite https://pds.zat.dev
just plc-repair did:plc:...
just bench all
just docker-publish-current
Run zig zen before committing.