···11+# labelz
22+33+a live AT Protocol labeler, built on [zat](https://tangled.org/zat.dev/zat).
44+55+this exists to pressure-test zat as an SDK — jetstream consumption, CBOR encoding,
66+secp256k1 signing, and XRPC serving all running against real network traffic.
77+88+## what it does
99+1010+connects to the bluesky jetstream firehose, watches for posts matching keyword rules,
1111+signs labels with secp256k1, stores them in SQLite, and serves them over the standard
1212+AT Protocol labeler endpoints:
1313+1414+- `com.atproto.label.subscribeLabels` — WebSocket event stream
1515+- `com.atproto.label.queryLabels` — HTTP query
1616+1717+the labeler is registered as a proper AT Protocol identity with a DID, PDS account,
1818+and labeler declaration record.
1919+2020+**live instance:** [labelz.fly.dev](https://labelz.fly.dev/health) · [DID document](https://plc.directory/did:plc:ugigqebt4sm3n4xpmqvslcyo) · [PDS](https://labelz-pds.nate-8fe.workers.dev/status)
2121+2222+## running
2323+2424+```
2525+LABELZ_DID=did:plc:... LABELZ_SECRET_KEY=<64-hex-chars> zig build run
2626+```
2727+2828+<details>
2929+<summary>environment variables</summary>
3030+3131+| variable | required | description |
3232+|----------|----------|-------------|
3333+| `LABELZ_DID` | yes | labeler DID (src field on emitted labels) |
3434+| `LABELZ_SECRET_KEY` | yes | secp256k1 secret key, 64 hex chars (32 bytes) |
3535+| `LABELZ_PORT` | no | server port (default: 4100) |
3636+| `LABELZ_DB` | no | SQLite database path (default: `/data/labelz.db`) |
3737+3838+</details>
3939+4040+<details>
4141+<summary>architecture</summary>
4242+4343+```
4444+jetstream ──→ keyword match ──→ sign (secp256k1) ──→ store (SQLite)
4545+ │
4646+ ┌──────────┴──────────┐
4747+ │ │
4848+ subscribeLabels (WS) queryLabels (HTTP)
4949+```
5050+5151+source files:
5252+5353+- `main.zig` — config, jetstream consumer, keyword matching
5454+- `label.zig` — label type, CBOR encoding, signing
5555+- `store.zig` — SQLite storage
5656+- `server.zig` — XRPC server (WebSocket + HTTP)
5757+- `identity.zig` — PLC identity operations (unused at runtime, used for setup)
5858+5959+</details>
6060+6161+<details>
6262+<summary>identity setup</summary>
6363+6464+the labeler needs a full AT Protocol identity: a DID registered at plc.directory,
6565+a PDS hosting the account, and a labeler declaration record. this was done once
6666+using [pds.js](https://tangled.org/chadtmiller.com/pds.js) on cloudflare workers
6767+and [goat](https://github.com/bluesky-social/indigo/tree/main/cmd/goat) for PLC operations.
6868+6969+the DID document includes both a repo signing key (P-256, used by the PDS) and a
7070+label signing key (secp256k1, used by labelz), plus service endpoints for both
7171+the PDS and the labeler.
7272+7373+see the `justfile` for the operational recipes (`keygen`, `update-did`, `declare`).
7474+7575+</details>
7676+7777+<details>
7878+<summary>deployment</summary>
7979+8080+runs on fly.io with a persistent SQLite volume. the Dockerfile does a multi-stage
8181+build: debian bookworm with zig 0.15.2 for compilation, slim runtime image with
8282+just libsqlite3.
8383+8484+```
8585+fly deploy
8686+```
8787+8888+</details>
8989+9090+## dependencies
9191+9292+- [zat](https://tangled.org/zat.dev/zat) — AT Protocol primitives (jetstream, CBOR, crypto, XRPC)
9393+- [websocket.zig](https://github.com/zzstoatzz/websocket.zig) — WebSocket client/server
9494+- system libsqlite3