|
| 1 | +# door-sync — agent guide |
| 2 | + |
| 3 | +CiviCRM → UniFi Access reconciliation daemon. Runs on a Raspberry Pi under systemd. |
| 4 | + |
| 5 | +**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. |
| 6 | + |
| 7 | +## Commands |
| 8 | + |
| 9 | +```bash |
| 10 | +uv sync # install |
| 11 | +uv run pytest # tests |
| 12 | +uv run mypy src tests # type check (strict) |
| 13 | +uv run ruff check . # lint |
| 14 | +uv run door-sync --once # one reconcile cycle, exit |
| 15 | +uv run door-sync --dry-run # compute + log diff; no UniFi writes |
| 16 | +``` |
| 17 | + |
| 18 | +All tooling goes through `uv run` — the venv is managed by uv, not pip. |
| 19 | + |
| 20 | +## Architecture |
| 21 | + |
| 22 | +`docs/architecture.md` is authoritative for module layout, data contracts, and the pure/impure boundary. Read it before designing changes that cross module boundaries. |
| 23 | + |
| 24 | +## Hard rules (from architecture doc — do not violate without a human in the loop) |
| 25 | + |
| 26 | +- **No asyncio.** Sync `httpx` only. Do not "modernize" to async; the design rejects it deliberately (architecture.md §3). |
| 27 | +- **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). |
| 28 | +- **Frozen dataclasses.** All domain models in `models.py` are `@dataclass(frozen=True)`. Never mutate; construct a new instance. |
| 29 | +- **Strict layering.** Nothing imports `orchestrator` except `scheduler` and (future) `webhook`. See dependency table in architecture.md §4. |
| 30 | +- **Card ID redaction.** Logs show last-4 only. Never log a full card ID at any level (architecture.md §11). |
| 31 | +- **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. |
| 32 | +- **Fail-secure on safety guards.** Any guard firing means zero writes that cycle. No partial application. |
| 33 | + |
| 34 | +## Testing |
| 35 | + |
| 36 | +- Pure-module tests use plain dataclass construction — no mocks, no HTTP fixtures. |
| 37 | +- Idempotency canary: `compute_diff` immediately after `unifi.apply()` must yield all-empty diff sets. Include this test for the reconciler (architecture.md §8). |
| 38 | + |
| 39 | +## Config |
| 40 | + |
| 41 | +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. |
0 commit comments