From d374d75f5d0b40f055a71f02d25494d0cef94e26 Mon Sep 17 00:00:00 2001 From: Ryan Morash Date: Wed, 20 May 2026 22:33:46 -0400 Subject: [PATCH] Add CLAUDE.md agent guide Surfaces the load-bearing invariants from docs/architecture.md (no asyncio, pure modules stay pure, frozen dataclasses, card ID redaction, fail-secure safety guards) so they bind without requiring a full read of the architecture doc. Includes uv-prefixed command list and a pointer to the architecture doc as authoritative. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1fd81e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,41 @@ +# door-sync — agent guide + +CiviCRM → UniFi Access reconciliation daemon. Runs on a Raspberry Pi under systemd. + +**Status: pre-implementation.** Module skeleton exists in `src/door_sync/` but reconciler/safety/clients are not yet written. Architecture is locked; see `docs/architecture.md` before adding code. + +## Commands + +```bash +uv sync # install +uv run pytest # tests +uv run mypy src tests # type check (strict) +uv run ruff check . # lint +uv run door-sync --once # one reconcile cycle, exit +uv run door-sync --dry-run # compute + log diff; no UniFi writes +``` + +All tooling goes through `uv run` — the venv is managed by uv, not pip. + +## Architecture + +`docs/architecture.md` is authoritative for module layout, data contracts, and the pure/impure boundary. Read it before designing changes that cross module boundaries. + +## Hard rules (from architecture doc — do not violate without a human in the loop) + +- **No asyncio.** Sync `httpx` only. Do not "modernize" to async; the design rejects it deliberately (architecture.md §3). +- **Pure modules stay pure.** `reconciler.py`, `safety.py`, `tier_mapping.py` take dataclasses, return dataclasses. No logging, no config lookups, no HTTP, no exceptions on data issues — return a sentinel instead (architecture.md §5). +- **Frozen dataclasses.** All domain models in `models.py` are `@dataclass(frozen=True)`. Never mutate; construct a new instance. +- **Strict layering.** Nothing imports `orchestrator` except `scheduler` and (future) `webhook`. See dependency table in architecture.md §4. +- **Card ID redaction.** Logs show last-4 only. Never log a full card ID at any level (architecture.md §11). +- **Dry-run is sacred.** Dry-run flips a flag inside `UnifiClient` that turns writes into no-ops. Pure modules behave identically in dry-run and live — do not branch on dry-run in pure code. +- **Fail-secure on safety guards.** Any guard firing means zero writes that cycle. No partial application. + +## Testing + +- Pure-module tests use plain dataclass construction — no mocks, no HTTP fixtures. +- Idempotency canary: `compute_diff` immediately after `unifi.apply()` must yield all-empty diff sets. Include this test for the reconciler (architecture.md §8). + +## Config + +Two-file split: secrets in env (`.env` dev, `/etc/door-sync/env` prod, mode 0400), everything else in TOML (`config.toml` dev, `/etc/door-sync/config.toml` prod). Schema is not yet implemented.