diff --git a/DevelopmentGuide.md b/DevelopmentGuide.md index 587e306..e60c359 100644 --- a/DevelopmentGuide.md +++ b/DevelopmentGuide.md @@ -22,11 +22,21 @@ file elsewhere. ## Run the app ```sh -uv run uvicorn quartermaster.main:app --reload +uv run uvicorn quartermaster.main:app \ + --log-config src/quartermaster/logconfig.json \ + --reload ``` Open http://127.0.0.1:8000. +The `--log-config` flag emits structured JSON to stdout (Loki-ready +when deployed). Omit it if you'd rather read logs in uvicorn's default +human-readable format during local dev; production must use the config. + +Health probe (unauthenticated): `curl http://127.0.0.1:8000/healthz` +returns `{"status":"ok"}` on a healthy DB, 503 with the exception +class name in `detail` otherwise. + ## Run tests ```sh @@ -34,7 +44,7 @@ uv run pytest ``` Tests use an in-memory SQLite engine; no separate migration step -needed. Full suite runs in under 4 seconds. +needed. Full suite runs in under 6 seconds. ## Developing against a throwaway DB @@ -59,30 +69,33 @@ rm -rf /tmp/qm-dev.db /tmp/qm-dev-backups ``` src/quartermaster/ - main.py FastAPI app factory - routes.py Budget configuration handlers - routes_month.py Monthly view handlers + lifecycle transitions - service.py Budget service: entries, target, zero-amount, groups - month_service.py Month service: snapshot, deviation, lifecycle, groups - groups.py Group enum, labels, section->group mapping - models.py SQLAlchemy models, Section enum, MonthState enum - db.py Engine, session, PRAGMA foreign_keys=ON - config.py DB URL resolution + main.py FastAPI app factory + routes.py Budget configuration handlers + routes_month.py Monthly view handlers + lifecycle transitions + routes_health.py GET /healthz (separate router, unauthenticated) + service.py Budget service: entries, target, zero-amount, groups + month_service.py Month service: snapshot, deviation, lifecycle, groups, postings + groups.py Group enum, labels, section->group mapping + models.py SQLAlchemy models, Section enum, MonthState enum + db.py Engine, session, PRAGMA foreign_keys=ON + config.py DB URL resolution + logging_config.py Loads LOG_CONFIG from logconfig.json; AccessLogFilter + logconfig.json dictConfig source-of-truth for JSON stdout logs templates/ - base.html Barlow Condensed imports + favicon - index.html Budget config page - month.html Month view page - month_create.html Create-flow for a missing month + base.html Barlow Condensed imports + favicon + index.html Budget config page + month.html Month view page + month_create.html Create-flow for a missing month partials/ - section.html Budget section card - target_card.html Budget debt target card - budget_zero.html Budget Zero Amount widget (with logo) - budget_group_totals.html OOB swap for all four budget group totals - month_section.html Month section card with inline HTMX edits - month_target.html Month debt target card - month_zero.html Month Zero Amount widget (with logo) - month_group_totals.html OOB swap for all four month group totals - month_nav.html prev / title / next / state / lifecycle button + section.html Budget section card + target_card.html Budget debt target card + budget_zero.html Budget Zero Amount widget (with logo) + budget_group_totals.html OOB swap for all four budget group totals + month_section.html Month section card with inline HTMX edits + month_target.html Month debt target card + month_zero.html Month Zero Amount widget (with logo) + month_group_totals.html OOB swap for all four month group totals + month_nav.html prev / title / next / state / lifecycle button static/ app.css Full stylesheet (Barlow Condensed + ledger layout) brand/ Optimised logo assets @@ -92,19 +105,24 @@ alembic/ scripts/ backup-db.sh sqlite3.Connection.backup wrapper tests/ - test_service.py Budget service - test_routes.py Budget routes - test_month_service.py Month snapshot + CRUD - test_month_routes.py Month routes - test_zero_amount.py Zero-amount math + widgets - test_groups.py Group mapping + subtotals + default-open - test_notes.py Notes field + snapshot copy - test_month_lifecycle.py Transitions + close gate + edit locking - test_backup_script.py Backup script shell behaviour -CLAUDE.md DB safety rule, repo-level instructions + test_service.py Budget service + test_routes.py Budget routes + test_month_service.py Month snapshot + CRUD + test_month_routes.py Month routes + test_zero_amount.py Zero-amount math + widgets + test_groups.py Group mapping + subtotals + default-open + test_notes.py Notes field + snapshot copy + test_month_lifecycle.py Transitions + close gate + edit locking + test_postings.py Posting CRUD + derived applied + test_template_edit_isolation.py Template edit doesn't leak into snapshots + test_backup_script.py Backup script shell behaviour + test_logging.py JSON formatter, access filter, dictConfig, seed events + test_health.py /healthz 200 success + 503 on DB failure +CLAUDE.md DB safety rule, repo-level instructions docs/ - wiki/ Cloned wiki (gitignored) - mockups/ Design history + mockup HTML + superpowers/ Specs and plans (per-session design notes) + wiki/ Cloned wiki (gitignored) + mockups/ Design history + mockup HTML ``` ## Conventions @@ -124,8 +142,10 @@ chore/- tooling, deps, docs Each commit is one logical change. Typical PRs in this repo have 3-6 commits grouped by layer (data model, service, routes, templates, -tests, docs). Commit subjects under 72 chars, body under 80, trailers -include `Refs #` and the Claude co-author line. +tests, docs), or more for TDD-driven feature work where each task +gets its own failing-test / implementation / commit triple. Commit +subjects use conventional-commits prefixes (`feat:`, `fix:`, `chore:`, +`test:`, `docs:`, `refactor:`), under 72 chars. Body under 80. ### Merge flow @@ -138,6 +158,9 @@ PRs merge via the Forgejo API (or the web UI merge button), NOT 4. Close the tracking issue manually via the API (the `Closes #N` keyword is unreliable on this Forgejo instance). +Exception: purely local direct-to-main merges (rare) rebase onto +origin/main first to keep linear history. + ### Adding a schema change 1. Edit `src/quartermaster/models.py`. @@ -161,6 +184,21 @@ affects the HTML, also: 4. Claude cannot run a real browser from the CLI; the human eyeball is the final check before merge. +### Adding a structured log event + +1. Pick a namespaced module logger if one doesn't exist in the file + you're touching: `logger = logging.getLogger("quartermaster.")` + placed after all imports. +2. Use `logger.info` (or `.warning`/`.error`) with `extra={"event": "...", + ...additional_fields}`. The `event` name should be a concise + snake_case verb_noun. +3. Place the log after the commit / refresh in service-layer functions, + before `return`, so it only fires on durable success. +4. Write a test using `caplog` that asserts a record with the expected + `event` attribute is emitted. See `tests/test_logging.py` for the + pattern. +5. Document the new event in the table in [Operations](Operations). + ### Design workflow UI experiments live under `docs/mockups/` as self-contained HTML files