Skip to content
Open
Show file tree
Hide file tree
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
14 changes: 14 additions & 0 deletions .claude/rules/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
paths:
- "src/mavedb/models/**"
- "src/mavedb/lib/**"
- "src/mavedb/routers/**"
- "src/mavedb/worker/**"
- "src/mavedb/scripts/**"
---

# Database

- Write new queries in SQLAlchemy 2.0 `select()` style. Older code still uses `db.query()`; don't copy it.
- ValidTime tables never return only live rows by default. Filter with `Model.current` or `Model.live_at(...)`.
- Replace ValidTime rows with `supersede_with` or `supersede_live_where`, never a separate retire-then-insert, so the handoff has no gap.
13 changes: 13 additions & 0 deletions .claude/rules/migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- "alembic/**"
---

# Migrations

- Generate with `poetry run alembic revision --autogenerate -m "<what changes>"`, then read the result: autogenerate misses or mangles enum changes, server defaults, views and functions.
- Set `down_revision` to the current head so the chain stays linear. Check with `poetry run alembic heads` (one head).
- Never edit a revision that has run on staging or production. Write a new one, even for a fix.
- One logical change per revision. The message says what changes ("add variant_count to scoresets"), not "update model".
- Production tables are large (`variants`, `mapping_records`, `mapping_record_alleles`, `alleles`, `annotation_event` run to millions of rows). A full-table `UPDATE` holds its lock for minutes. Build indexes on these tables `CONCURRENTLY` inside `op.get_context().autocommit_block()`, as `a7c3e5f9b1d2_unique_allele_hgvs_expression.py` does.
- Data migrations with no clean upgrade/downgrade path go in `alembic/manual_migrations/`. An operator runs them by hand (see the README there); Ruff excludes the directory.
12 changes: 12 additions & 0 deletions .claude/rules/permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
paths:
- "src/mavedb/routers/**"
- "src/mavedb/lib/**"
---

# Permissions

- Import from `mavedb.lib.permissions`. Check the entity a route names with `assert_permission(user, entity, Action.X)`.
- When a read fans out from that entity to related ones (score set → calibrations, allele → score sets), get a `Principal` from the `get_principal` dependency and filter with its `Viewer` for that entity type. Some entities are stricter than their parent: for instance, reading a score set doesn't grant reading its calibrations. Never assume that permission on one entity implies permission on its related entities.
- `Principal` and `Viewer` are request-scoped. Never use one as a default argument.
- Read enforcement is moving into Postgres row-level security (#808). Until it lands, these application checks are the only enforcement.
15 changes: 15 additions & 0 deletions .claude/rules/routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
paths:
- "src/mavedb/routers/**"
- "src/mavedb/lib/authentication.py"
- "src/mavedb/lib/authorization.py"
---

# Routes

FastAPI runs `def` handlers and dependencies in a thread pool and `async def` ones on the event loop, so a synchronous database call inside `async def` stalls every request on that process (#881).

- New routes, dependencies and helpers are `def`.
- Use `async def` only for real awaits (ARQ enqueue, async HTTP), and run the synchronous database work in the thread pool.
- Start worker pipelines only through `enqueue_pipeline_for_score_set` (`lib/workflow/kickoff.py`).
- Route tests go in `tests/routers/`.
14 changes: 14 additions & 0 deletions .claude/rules/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
paths:
- "tests/**"
---

# Tests

- Mock only at system boundaries: external services, Redis/ARQ, S3, Slack. Don't mock internal helpers, validators or transforms.
- Assert on outcomes: return values, responses, database state, and calls at system boundaries. Don't assert on internal calls, call counts or log and progress message strings; tests should survive a refactor.
- Unit tests cover edge cases and error paths with external services mocked. Integration tests cover happy paths end to end against the real test database.
- `session.refresh()` before asserting on a modified ORM object. Include negative assertions where they matter (the record that must not exist).
- Name tests `test_<function>_<condition>_<expected_outcome>` when you write or modify them; don't rename untouched tests.
- Use `@pytest.mark.parametrize` with `ids` instead of near-identical copies.
- Put a fixture in the most specific `conftest.py` that needs it, and lift it to the nearest common one when it's shared.
23 changes: 23 additions & 0 deletions .claude/rules/worker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
paths:
- "src/mavedb/worker/**"
- "tests/worker/**"
---

# Worker

Read `src/mavedb/worker/README.md` first; `best_practices.md`, `job_decorators.md`, `job_managers.md` and `pipeline_management.md` beside it cover job structure, decorators and pipelines.

## Rules

- Job signature: `async def job(ctx, job_id, job_manager) -> JobExecutionOutcome`. Register jobs in `worker/jobs/registry.py`.
- Job code never commits. The management decorators and `update_progress()` own commits.
- Start pipelines only through `enqueue_pipeline_for_score_set` (`lib/workflow/kickoff.py`).
- `max_jobs = 2` is deliberate: psycopg2 is synchronous and blocks the worker's event loop.

## Testing jobs

- Assert on `JobExecutionOutcome.status` and `.data` in every job test, plus database state for the objects the job changes. For external-service jobs, assert the boundary calls (ClinGen CAR/LDH, UniProt, gnomAD/Athena, ClinVar, S3).
- Let `update_progress()` run unpatched. It commits as a checkpoint in production, and tests should exercise that.
- Use `TransactionSpy` (`tests/helpers/transaction_spy.py`) only in manager and decorator tests, where commit and rollback are the contract. In job tests, check database state instead.
- `mock_database_flush_failure` and `mock_database_rollback_failure` are for infrastructure error-recovery paths that are hard to trigger otherwise.
32 changes: 32 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"env": {
"AWS_PROFILE": "mavedb-agent-readonly"
},
"permissions": {
"deny": ["Bash(* --no-verify*)", "Read(~/.aws/**)", "Read(~/.pgpass)", "Read(~/.ssh/**)"],
"ask": [
"Edit(**/.claude/settings*.json)",
"Bash(git push *--force*)",
"Bash(git push * -f*)",
"Bash(git push * main*)",
"Bash(git push * mavedb-main*)",
"Bash(git push * mavedb-dev*)",
"Bash(git push * release-*)",
"Bash(git -C * push *--force*)",
"Bash(git -C * push * -f*)",
"Bash(git -C * push * main*)",
"Bash(git -C * push * mavedb-main*)",
"Bash(git -C * push * mavedb-dev*)",
"Bash(git -C * push * release-*)",
"Bash(aws *--profile*)",
"Bash(*AWS_PROFILE=*)",
"Bash(eb *)",
"Bash(*MODE=prodapi*)",
"Bash(poetry add *)",
"Bash(poetry -C * add *)",
"Bash(npm install *)",
"Bash(npm --prefix * install *)",
"Bash(*pip install *)"
]
}
}
5 changes: 5 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Formatting-only commits, skipped by `git blame` (GitHub reads this file automatically).
# Locally: git config blame.ignoreRevsFile .git-blame-ignore-revs

# style: apply Ruff 0.17.1 formatting
dcf4e382d2e0bea48bd6c094141727275cbf262f
165 changes: 0 additions & 165 deletions .github/instructions/api.instructions.md

This file was deleted.

Loading
Loading