- Connect an MCP client — Claude Desktop, Cursor, or a remote MCP runtime.
- Connect an external app via API token — mint a scoped token from the UI and use it from curl / scripts / third-party integrations.
- Mail sidecar spec — the JSON envelope external mail-fetch processes emit.
- Mail-mbsync recipe — the Docker-compose IMAP → suchi setup.
/tmp/suchi-* so
runs don’t collide. Kill the server at the end of each recipe
(kill $PID) before starting the next one.
The recipes below use small text-like “PDFs” (a
%PDF-1.7 ...
header + %%EOF). They exercise the ingest pipeline shape but
don’t trigger real OCR — for that you need qpdf, pdftotext, and
ocrmypdf installed on the box (the Docker full image ships
them all).Recipe 0 — Bootstrap an instance
Every other recipe starts here. Boot with a fresh data dir, grab the setup token from the log, create the admin.Recipe 1 — Upload API + dedup state machine
ExercisesPOST /api/documents/, the three dedup branches (fresh /
alive-collision / trashed-collision), and soft-delete/restore.
Recipe 2 — Per-user dedup keyspace
Verifies that two users hold the same bytes as two independent docs (seephase-2(dedup) commit).
Recipe 3 — Post-ingest pipeline + /api/tasks/
Uses a real PDF to exercise qpdf → pdf-inspector → ocrmypdf routing and the durable outbox.Missing binaries (qpdf / ocrmypdf) log
skip.no_binary and the
pipeline continues. The pdftotext-backed pdf-inspector fallback is
available on any box with poppler-utils installed.Recipe 4 — fs-watch + sidecar
Bootstrap the instance without fs-watch first, then restart with it enabled so the owner-email resolves.Recipe 5 — bundle import (dry-run first, then full)
PUBLIC_URL + DATA_DIR are the only knobs; the
subcommand doesn’t start a server, it just reads the same DB the
running server would.
Recipe 6 — suchi gc (unreferenced-blob reclamation)
Recipe 7 — Poking at the durable outbox
Thejobs table IS the truth of what suchi is doing. Every ingest
producer writes a post-ingest job in the same tx as the doc row.
Retries, backoff, dead-letters all live there.
Recipe 8 — /metrics scrape
suchi_http_requests_total{route,status}— is the mux catching what you expect?suchi_http_request_duration_secondshistogram — slow handler regression detector.suchi_jobs_pending/suchi_jobs_running— outbox backlog.suchi_jobs_dead_total{kind}— has anything given up permanently?
Recipe 9 — Clean shutdown + restart
Everything above is deliberately verbose so a new contributor can copy any single recipe and see something happen. Once you’re comfortable, the same recipes compose: bootstrap once, then loop through 1 → 4 → 6 against a single running instance to touch every Phase-2 path in one session.