Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +12 to +15
Comment on lines +14 to +15
```

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.