Artifact 512c74682fcb13ef8fcb2b932b80f342cf343451d2b3dd5db995e0cc9cf21b65:
- File
docs/running.md
— part of check-in
[bbe731e042]
at
2026-08-17 16:40:19
on branch trunk
— deploy: Dockerfile, compose stack and a run book
quarry had no packaging at all while notify already had a working Dockerfile and compose. The image ships zit WITH quarry, deliberately: 0001 §2 makes the zit CLI the entire store interface, so zit's behaviour IS quarry's, and today alone three zit builds changed what quarry does — frames got faster, a card fix changed which artifacts parse, stat -R enabled bare-store serving. ZIT_REF is a build argument and /etc/quarry-build records the pair, so a container can answer which zit it carries.
compose follows notify's shape, including the constraint its own file documents: dagnats binds loopback in standalone mode, so everything shares its network namespace. quarry joins it too — its embedded NATS uses DontListen and binds nothing, so there is no port to collide with.
docs/running.md is the by-hand path, which is what this was actually developed against: the launchd-signed dagnats, notify's bootstrap and workflows, the topic-vs-subscriber addressing, and the two credentials that close the write and push gates.
Unverified, and said so in the doc: no container runtime is installed here, so the images have never been built. The two build recipes inside them ARE verified natively — zit compiles from a clean clone with zig build --release=fast, quarry builds fully static with CGO_ENABLED=0.
Also lands the publish-throughput measurement as an opt-in test. It is what disproved the hypothesis that synchronous publish was the ingest bottleneck (62k msg/s sync), which redirected the search to zit frames and quarry's folding. (user: dmestas, size: 5884) [annotate] [blame] [check-ins using]
# Running quarry Two ways: the compose stack, or by hand. The hand-rolled path is what this was developed against and is what the compose file encodes, so it is written down first — a run book that stays true even if the images are never built. ## What the stack is | piece | role | state | |---|---|---| | **zit** | the store. quarry shells out to it for every read and write (0001 §2) | the `.zit` files themselves | | **quarry** | the forge: indexes, UI, API, sync fronting | derived indexes, embedded NATS, OLTP database | | **notify** | notifications: workflows, subscribers, topics, the inbox quarry embeds | SQLite | | **dagnats** | the workflow engine notify runs on: embedded NATS + JetStream + an HTTP bridge | JetStream data | `zit` is not a runtime dependency in the ordinary sense — it is quarry's only store interface, so **its version is part of quarry's behaviour**. On 2026-08-16 three zit builds changed what quarry does: `frames` got 31% faster, a card-parsing fix changed which artifacts parse at all (and until it landed, sqlite's 794 tickets were invisible), and a new `stat -R` let quarry serve a bare store. Pin the pair; do not let a deploy resolve zit from whatever is on PATH. ## By hand ### dagnats Runs under launchd on this machine as `com.dmestas.dagnats`, binding NATS on 4222 and its bridge on 8080, with data in `~/Library/Application Support/dagnats`. ```sh launchctl kickstart -k gui/$(id -u)/com.dmestas.dagnats ``` A freshly-built Go binary replacing it must be **ad-hoc signed** or launchd kills it with `OS_REASON_CODESIGNING` and the service silently fails to come back: ```sh go build -o ~/go/bin/dagnats ./cmd/dagnats codesign -s - -f ~/go/bin/dagnats launchctl kickstart -k gui/$(id -u)/com.dmestas.dagnats ``` In standalone mode dagnats binds **loopback**, which is why anything talking to it shares its network namespace under compose rather than reaching it by service name. ### notify ```sh notify bootstrap -dev -db notify.db -org quarry -env dev # prints apiKey once notify serve -dev -db notify.db -addr 127.0.0.1:3000 \ -nats-url nats://127.0.0.1:4222 -bridge-url http://127.0.0.1:8080 curl -s localhost:3000/readyz # {"status":"ok","bridge":true,"db":true} ``` `-dev` derives a fixed, publicly-known master key. Outside development set `NOTIFY_MASTER_KEY_HEX`. Quarry triggers two workflows by name, so they must exist, each with an `in_app` step or nothing reaches an inbox: ```sh K=<apiKey> for wf in quarry-activity quarry-mention; do curl -s -X POST localhost:3000/v1/workflows -H "Authorization: ApiKey $K" \ -H 'Content-Type: application/json' -d "{ \"name\":\"$wf\",\"triggerIdentifier\":\"$wf\", \"steps\":[{\"stepId\":\"S1\",\"type\":\"in_app\", \"controlValues\":{\"subject\":\"{{payload.summary}}\", \"body\":\"{{payload.repo}} — by {{payload.actor}}\"}}]}" done ``` **Activity is addressed to a topic, mentions to a person.** A repository's activity goes to the topic `repo-<name>` (quarry sanitizes its own `repo:<name>` convention, because notify accepts only lowercase letters, digits, hyphens and underscores in a key and rejects the whole trigger otherwise). A topic with no members notifies nobody, so subscribing is a required step, not a nicety: ```sh curl -s -X POST localhost:3000/v1/subscribers -H "Authorization: ApiKey $K" \ -H 'Content-Type: application/json' -d '{"subscriberId":"you"}' curl -s -X POST localhost:3000/v1/topics -H "Authorization: ApiKey $K" \ -H 'Content-Type: application/json' -d '{"key":"repo-synthetic","name":"synthetic activity"}' curl -s -X POST localhost:3000/v1/topics/repo-synthetic/subscribers \ -H "Authorization: ApiKey $K" -H 'Content-Type: application/json' \ -d '{"subscriberId":"you"}' ``` ### quarry ```sh quarry serve --addr 127.0.0.1:8950 --data ./data \ --notify http://127.0.0.1:3000 --notify-key "$K" \ --repo quarry=$HOME/quarry ``` `--data` is not optional in practice: it defaults to `.quarry` beside the **first** `--repo`, which writes derived state into whichever repository happens to be listed first. Always pass it. Gates are open until they are closed, and closing them is two separate credentials on purpose — accounts are the forge identity (argon2id), the sync secret is the RFC §6.3 value the protocol fixes: ```sh quarry account add --user you --admin --data ./data # closes web/API writes quarry user add --repo name=$HOME/quarry --user you --data ./data # closes push ``` The account gate is **global**: the first account closes writes on every repository in the fleet, not just one. ## With compose ```sh export NOTIFY_SRC=../notify # a notify checkout; it builds dagnats too export ZIT_REF=<commit> # pin the store implementation docker compose up --build ``` Then bootstrap notify once (`docker compose run --rm notify bootstrap …`), put the key in `QUARRY_NOTIFY_KEY`, and create the workflows and topics as above. `docker run <image> cat /etc/quarry-build` reports which quarry and which zit an image carries, without starting it. > **Unverified.** `Dockerfile` and `compose.yml` have not been built — > no container runtime is installed on the development machine. The two > build recipes inside them *are* verified natively: zit compiles from a > clean clone of the GitHub mirror with `zig build --release=fast`, and > quarry builds fully static with `CGO_ENABLED=0` (its SQLite is > WASM-based and Pikchr runs on wazero, so nothing needs a C toolchain). > What remains untested is the image assembly and the compose wiring. ## Ports | port | what | |---|---| | 4222 | dagnats NATS | | 8080 | dagnats HTTP bridge (and its console — unauthenticated, keep it on loopback) | | 3000 | notify API | | 8950 | quarry | quarry's own embedded NATS binds nothing (`DontListen`), so it never collides.