docs: enhance AGENTS.md files with Go linting, DI patterns, barrel re-export, TS best practices [guardrail-change]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,57 @@ async def create_dsr_request(
|
||||
- `pip-audit` in CI.
|
||||
- Async-first: prefer `httpx.AsyncClient`, `asyncpg`/`SQLAlchemy 2.x async`.
|
||||
|
||||
## mypy configuration
|
||||
|
||||
`backend-compliance/mypy.ini` is the mypy config. Strict mode is on globally; per-module overrides exist only for legacy files that have not been cleaned up yet.
|
||||
|
||||
- New modules added to `compliance/services/` or `compliance/repositories/` **must** pass `mypy --strict`.
|
||||
- To type-check a new module: `cd backend-compliance && mypy compliance/your_new_module.py`
|
||||
- When you fully type a legacy file, **remove its loose-override block** from `mypy.ini` as part of the same PR.
|
||||
|
||||
## Dependency injection
|
||||
|
||||
Services and repositories are wired via FastAPI `Depends`. Never instantiate a service or repository directly inside a handler.
|
||||
|
||||
```python
|
||||
# dependencies.py
|
||||
def get_my_service(db: AsyncSession = Depends(get_db)) -> MyService:
|
||||
return MyService(MyRepository(db))
|
||||
|
||||
# router
|
||||
@router.get("/items", response_model=list[ItemRead])
|
||||
async def list_items(svc: MyService = Depends(get_my_service)) -> list[ItemRead]:
|
||||
return await svc.list()
|
||||
```
|
||||
|
||||
- Services take repositories in `__init__`; repositories take `Session` or `AsyncSession`.
|
||||
|
||||
## Structured logging
|
||||
|
||||
```python
|
||||
import structlog
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# Always bind context before logging:
|
||||
logger.bind(tenant_id=str(tid), action="create_dsfa").info("dsfa created")
|
||||
```
|
||||
|
||||
- Audit-relevant actions must use the audit logger with a `legal_basis` field.
|
||||
- Never log secrets, PII, or full request bodies.
|
||||
|
||||
## Barrel re-export pattern
|
||||
|
||||
When an oversized file (e.g. `schemas.py`, `models.py`) is split into a sub-package, the original stays as a **thin re-exporter** so existing consumer imports keep working:
|
||||
|
||||
```python
|
||||
# compliance/schemas.py (barrel — DO NOT ADD NEW CODE HERE)
|
||||
from .schemas.ai import * # noqa: F401, F403
|
||||
from .schemas.consent import * # noqa: F401, F403
|
||||
```
|
||||
|
||||
- New code imports from the specific module (e.g. `from compliance.schemas.ai import AIRiskRead`), not the barrel.
|
||||
- `from module import *` is only permitted in barrel files.
|
||||
|
||||
## Errors & logging
|
||||
|
||||
- Domain errors inherit from a single `DomainError` base per service.
|
||||
@@ -91,4 +142,7 @@ async def create_dsr_request(
|
||||
- Change a public route's path/method/status/schema without simultaneous dashboard fix.
|
||||
- Catch `Exception` broadly — catch the specific domain or library error.
|
||||
- Put business logic in a router or in a Pydantic validator.
|
||||
- `from module import *` in new code — only in barrel re-exporters.
|
||||
- `raise HTTPException` inside the service layer — raise domain exceptions; map them in the router.
|
||||
- Use `model_validate` on untrusted external data without an explicit schema boundary.
|
||||
- Create a new file >500 lines. Period.
|
||||
|
||||
Reference in New Issue
Block a user