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
11 changes: 11 additions & 0 deletions .claude/commands/review-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ Check new code against the mandatory patterns from `CLAUDE.md`:
- **No raw collections as public API** β€” functions returning `dict`/`list` for structured data should return a typed model instead.
- **Model with the right paradigm for clarity** β€” choose the tool that makes the domain intent obvious at the call site: pure functions for stateless transforms, magic methods for natural domain operations (`current + delta` via `__add__` instead of manual field construction), OOP for encapsulating state and invariants. Flag code that uses a weaker paradigm when a stronger one would eliminate boilerplate and make the intent self-evident.

**14. Guards that defend against developer errors instead of runtime input**
Classify every `if/else` and `try/except` by **what it guards against**. A guard is only legitimate when the value it checks can be wrong for reasons **outside our code's control**. This is the sibling of item #12 (`assert` forbidden outside tests) β€” same philosophy: internal invariants belong in unit tests, not hot-path branches.
- **Decision heuristic:** "Can this value only be wrong if another part of OUR code is wrong? β†’ delete the guard, add a unit test, let it raise. Can it be wrong from input or environment outside our control? β†’ keep the guard and handle it gracefully."
- **Keep** guards on real runtime/external conditions: user/API request bodies, network/HTTP responses, filesystem, DB availability, third-party payloads, parsing external/untrusted data, concurrency races. Validating a request field from a caller SHOULD be guarded and return a clear error.
- **Flag** defensive branches on trusted internal values β€” an internal constant, an `Enum` member, a dict key that exists by construction, a field our own code sets, a registry entry. These add dead branches to the hot path and hide the bug. Prefer letting the operation raise naturally (`KeyError`/`AttributeError`/`TypeError`) per Python **EAFP** (Easier to Ask Forgiveness than Permission), and cover the invariant with a unit test β€” unit tests are the sanctioned place to assert internal invariants. A swallowed or re-wrapped developer error violates PEP 20 (Zen of Python): "Errors should never pass silently" and "Explicit is better than implicit" β€” it slips past the test suite.
- **Real examples from this repo:**
- An internal table-name registry lookup on an internal constant should be `table = _TABLE[name]` (raises `KeyError`; add a test asserting every collection constant is registered) β€” NOT `if name not in _TABLE: raise ValueError("register it")`.
- `db_adapter.py` normalizes query rows with `if "id" not in r and "_id" in r: r["id"] = r["_id"]` (repeated at ~L136/L147/L187). The `IDatabase.query()` contract (`interface.py`) guarantees every row has `_id`, and both backends honor it (`firestore.py` L191, `memory.py` L350), so the `and "_id" in r` re-check defends against a backend breaking its own contract β€” a developer error. Prefer `r["id"] = r["_id"]` (raises `KeyError` if the contract is broken) and cover the "`query()` returns `_id`" invariant with a per-backend unit test.
- `memory.py` `_matches_filter` (L394–L419) is a `match operator:` with no `case _:` default, so an unsupported operator silently matches nothing β€” while `firestore.py` `_apply_filter` (L298) raises `ValueError` for the same input. Operators are always hardcoded literals in our own query code (e.g. `[("status", "==", "running")]`), so a bad operator is a developer error; the memory backend lets it pass silently (violating PEP 20) and diverges from Firestore. Add a `case _: raise ValueError(...)` so both backends fail the same way, and unit-test the unsupported-operator path.
- Flag "just in case" guards on trusted internal inputs as **CHANGES REQUIRED** (or a nit if trivial).

## What NOT to flag

- Minor inefficiencies (extra dict copy, redundant log line)
Expand Down