# development This note records codebase conventions that are easy to lose while moving quickly. ## source layout - `src/main.zig` is process setup. Keep parsing and setup helpers in `core` or `internal` so `main` stays readable. - `src/core` is small shared PDS infrastructure: config, logging, syntax, XRPC helpers, mail, and repo helpers. - `src/http` owns routing and HTTP response shape. Endpoint behavior belongs in the protocol modules rather than in the router. - `src/atproto` owns the AT Protocol XRPC surface: server, repo, sync, OAuth, identity, preferences, and generic proxying. - `src/storage` owns SQLite persistence, repo mutation, event sequencing, and blob metadata. Runtime code reads record bodies from `repo_blocks`, not from cached JSON columns. - `src/internal` is ZDS-local reusable glue. Good candidates are CLI parsing, sharded synchronization, passkey adapters, and narrow encoding adapters. Protocol primitives still belong in `zat` once they are general enough. - `bench` and `tools` contain 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 `zat` for atproto primitives: syntax, TIDs, DID and handle resolution, JWT helpers, DAG-CBOR, CAR, MST, repo verification, OAuth helpers, JSON helpers, and streaming clients. - Use `httpz` for the PDS HTTP/1.1 server boundary: accept loops, request parsing, response writing, upgrades, and server-side websocket integration. - Use `webauthn` for browser credential ceremony verification. ZDS owns the account policy, challenge storage, and UI that surround it. - Use `zqlite` for 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-proxy` path. 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: ```sh 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.