chore: bootstrap trellis workspace and sync deployment settings

This commit is contained in:
qzl
2026-04-20 17:15:50 +08:00
parent 0842e04c39
commit eeed737949
82 changed files with 16522 additions and 11 deletions
@@ -0,0 +1,104 @@
# Database Guidelines
> Database patterns and conventions for this project.
---
## Overview
Current backend data layer is **SQLAlchemy Async ORM + Alembic**.
Evidence:
- Async engine/session: `backend/src/core/db/session.py`
- ORM base/mixins: `backend/src/core/db/base.py`
- Generic repository soft-delete behavior: `backend/src/core/db/base_repository.py`
- Migration entry: `backend/alembic/env.py`
- Migration files: `backend/alembic/versions/*.py`
Transaction boundary convention (actual implementation):
- **Repository** does query/flush only
- **Service** owns `commit()` / `rollback()`
Examples:
- `backend/src/v1/todo/repository.py` uses `flush()` and never commits
- `backend/src/v1/todo/service.py` commits/rolls back around repository calls
- `backend/src/v1/schedule_items/service.py` commits/rolls back in service methods
---
## Query Patterns
Observed query conventions:
1. Use SQLAlchemy expression API (`select`, `update`, `delete`) with async session.
2. Default to soft-delete-safe reads (`deleted_at IS NULL`) for models supporting soft delete.
3. Many repositories log and re-raise DB exceptions; service layer converts DB failures into stable API/domain errors.
Examples:
- `backend/src/core/db/base_repository.py`
- `_apply_soft_delete_filter(...)`
- `get_by_id(...)` and `get_one(...)` auto-apply `deleted_at IS NULL` when column exists
- `update_by_id(...)`/`soft_delete_by_id(...)` use `flush()` and do not own commit/rollback
- `backend/src/v1/todo/repository.py`
- `list_by_owner(...)` explicitly applies `.where(Todo.deleted_at.is_(None))`
- `backend/src/v1/schedule_items/repository.py`
- `list_by_date_range(...)` combines owner filter + soft-delete filter + time window
---
## Migrations
Alembic is the schema migration source of truth.
Evidence:
- `backend/alembic/alembic.ini`
- `backend/alembic/env.py`
- `backend/alembic/versions/20260226_0001_initial_schema.py`
Project command entry points:
- `./infra/scripts/dev-migrate.sh migrate`
- `./infra/scripts/dev-migrate.sh init-data`
- `./infra/scripts/dev-migrate.sh bootstrap`
The script executes runtime CLI with `uv` and `PYTHONPATH=backend/src`.
---
## Naming Conventions
Observed naming patterns in models and migrations:
- Table names: plural snake_case (`todos`, `profiles`, `schedule_items`, `friendships`)
- Examples in `backend/src/models/todos.py`, `backend/src/models/profile.py`, `backend/src/models/friendships.py`
- Common audit columns: `created_at`, `updated_at`, `deleted_at`
- Mixins in `backend/src/core/db/base.py`
- Key/index/constraint names in Alembic are explicit and prefixed by type:
- `ix_*` (index), e.g. `ix_llms_factory_id`
- `fk_*` (foreign key), e.g. `fk_profiles_id`
- `uq_*` (unique), e.g. `uq_automation_jobs_id_owner`
- `chk_*` (check), e.g. `chk_automation_job_schedule_type`
- Evidence in `backend/alembic/versions/20260226_0001_initial_schema.py`
---
## Anti-patterns / Forbidden Patterns
- Do not commit/rollback inside repository classes.
- Repositories should only `flush` and raise (`backend/src/v1/todo/repository.py`).
- Do not bypass soft-delete filtering for regular read paths when model has `deleted_at`.
- Base behavior exists in `backend/src/core/db/base_repository.py`.
- Do not introduce schema changes without Alembic migration.
- Current schema lifecycle is Alembic-based (`backend/alembic/env.py`, `backend/alembic/versions/*`).
---
## Uncertainties (documented, not invented)
- There is no repository-wide automated check that every query path includes soft-delete filtering; this is a convention enforced by base repository usage and review.
- Some repository method signatures still use broad `dict` input (`backend/src/v1/schedule_items/repository.py`), and a stricter typed policy is not fully codified in docs yet.
@@ -0,0 +1,112 @@
# Directory Structure
> How backend code is organized in this project.
---
## Overview
Backend code is centered in `backend/src` and follows a route-module layout under `v1/` with shared infrastructure in `core/`.
- **HTTP entry**: `backend/src/app.py` and `backend/src/v1/router.py`
- **Feature modules**: `backend/src/v1/<domain>/`
- **Cross-cutting infrastructure**: `backend/src/core/`
- **ORM models**: `backend/src/models/`
- **Shared/domain schemas**: `backend/src/schemas/`
- **Global/base service providers**: `backend/src/services/`
- **Migrations**: `backend/alembic/`
- **Tests**: `backend/tests/{unit,integration,e2e}`
---
## Directory Layout
```text
backend/
├── src/
│ ├── app.py
│ ├── core/
│ │ ├── config/
│ │ ├── db/
│ │ ├── http/
│ │ ├── logging/
│ │ └── taskiq/
│ ├── models/
│ ├── schemas/
│ ├── services/
│ └── v1/
│ ├── router.py
│ ├── auth/
│ ├── users/
│ ├── todo/
│ ├── schedule_items/
│ └── ...
├── alembic/
│ ├── env.py
│ └── versions/
└── tests/
├── unit/
├── integration/
└── e2e/
```
---
## Module Organization
Observed feature-module pattern (evidence from multiple `v1/*` modules):
1. `router.py` contains FastAPI routes and request/response wiring
2. `dependencies.py` contains DI providers
3. `service.py` contains business logic, authz checks, transaction boundaries
4. `repository.py` contains query/CRUD logic only
5. `schemas.py` contains API DTOs (Pydantic)
Real examples:
- `backend/src/v1/todo/{router.py,dependencies.py,service.py,repository.py,schemas.py}`
- `backend/src/v1/schedule_items/{router.py,dependencies.py,service.py,repository.py,schemas.py}`
- `backend/src/v1/friendships/{router.py,dependencies.py,service.py,repository.py,schemas.py}`
Cross-module composition examples:
- `backend/src/v1/router.py` includes all domain routers under `/api/v1`
- `backend/src/app.py` mounts the v1 router and global exception handlers
- `backend/src/v1/todo/dependencies.py` composes DB session + user dependency + repositories into `TodoService`
---
## Naming Conventions
Conventions observed in current codebase:
- Package and file names use **snake_case** (`schedule_items`, `inbox_messages`, `registration_bootstrap.py`)
- Route package names are domain nouns (`todo`, `users`, `auth`, `agent`)
- Common file names are stable across modules:
- `router.py`
- `service.py`
- `repository.py`
- `schemas.py`
- `dependencies.py`
- Logger names usually follow module path semantics, e.g.:
- `get_logger("v1.todo.service")`
- `get_logger("v1.schedule_items.repository")`
- `get_logger("api.app")`
---
## Anti-patterns / Forbidden Patterns
Grounded by repository rules and existing structure:
- Do not place backend feature code under `apps/**` or non-backend folders; backend runtime code stays in `backend/src/**`.
- Do not bypass module layering by putting SQL and transaction commits directly in router files.
- Current positive references: `backend/src/v1/todo/router.py` (routing only), `backend/src/v1/todo/service.py` (commit/rollback), `backend/src/v1/todo/repository.py` (query layer).
- Do not introduce ad-hoc folder layouts for new domains when `v1/<domain>/{router,service,repository,schemas,dependencies}` already exists.
---
## Uncertainties (documented, not invented)
- There is no explicit convention doc yet for when logic should go to `backend/src/services/**` versus `backend/src/v1/**`; both exist and are currently used for different scopes.
- `backend/src/v1/analytics/` includes `tasks.py` and static web content usage from `backend/src/app.py` (`v1/analytics/web`), which is slightly different from CRUD-oriented modules.
+101
View File
@@ -0,0 +1,101 @@
# Error Handling
> How errors are handled in this project.
---
## Overview
Backend error transport is RFC7807-style `application/problem+json` with stable `code` and optional `params` extensions.
Evidence:
- Error class: `backend/src/core/http/errors.py` (`ApiProblemError`)
- Problem details builder: `backend/src/core/http/response.py`
- Global exception mapping: `backend/src/app.py`
- Error code registry: `docs/protocols/common/http-error-codes.md`
---
## Error Types
### 1) `ApiProblemError` (preferred for business/API errors)
Used to carry `status_code`, `detail`, `code`, `params`.
Examples:
- `backend/src/v1/todo/service.py` (`_todo_error(...)` returns `ApiProblemError`)
- `backend/src/v1/schedule_items/service.py` (raises `ApiProblemError` with `problem_payload(...)`)
- `backend/src/v1/users/dependencies.py` (auth failures mapped to `AUTH_UNAUTHORIZED`, `JWT_VERIFIER_NOT_CONFIGURED`)
### 2) Infra/DB exceptions (caught at boundary and converted)
- `SQLAlchemyError` is commonly caught in service/repository boundaries.
- Many repository methods log and re-raise DB exceptions (for example in todo/schedule_items repositories); generic helpers in `core/db/base_repository.py` may just propagate exceptions.
- Service layer usually maps infra failures to stable codes like `*_SERVICE_UNAVAILABLE` / `*_STORE_UNAVAILABLE`.
Examples:
- `backend/src/v1/todo/repository.py`
- `backend/src/v1/todo/service.py`
- `backend/src/v1/schedule_items/service.py`
---
## Error Handling Patterns
Observed patterns:
1. **Service methods**: catch DB/infrastructure errors, rollback when needed, then raise typed API problem.
- `backend/src/v1/todo/service.py`
- `backend/src/v1/schedule_items/service.py`
2. **Repository methods**: log exception context and re-raise.
- `backend/src/v1/todo/repository.py`
- `backend/src/v1/schedule_items/repository.py`
3. **Global handlers**: convert unhandled errors and framework exceptions into problem+json responses.
- `backend/src/app.py`
---
## API Error Responses
Expected transport:
- Media type: `application/problem+json`
- Fields: RFC7807 core (`type`, `title`, `status`, `detail`, `instance`) + extension fields (`code`, `params`)
Evidence:
- Response builder model in `backend/src/core/http/response.py`
- Exception handlers in `backend/src/app.py`
- Registry and compatibility notes in `docs/protocols/common/http-error-codes.md`
Practical rule:
- For business branches, return stable `UPPER_SNAKE_CASE` codes and keep `detail` human-readable.
---
## Anti-patterns / Forbidden Patterns
- Do not return free-text-only errors for business branches (missing `code`).
- Protocol source: `docs/protocols/common/http-error-codes.md`
- Do not swallow exceptions with `except ...: log; return default` in new code.
- Project-wide rule from root `AGENTS.md`: no error swallowing.
- Do not add new service/repository branches that raise `HTTPException` directly.
- Backend target rule in `backend/AGENTS.md` prefers `ApiProblemError` in non-router layers.
Existing legacy evidence to be aware of:
- `backend/src/core/db/base_service.py` still raises `HTTPException(401, "Unauthorized")`.
- `backend/src/v1/users/dependencies.py` has fallback branches returning `None` after catching exceptions in `_verify_user_with_supabase`.
These are current-state observations, not patterns to copy for new code.
---
## Uncertainties (documented, not invented)
- `backend/src/core/logging/middleware.py` defines `register_exception_handlers(...)` returning `{ "detail": "Internal Server Error" }` JSON (non-problem+json); main app currently defines its own handlers in `backend/src/app.py`. The single enforced runtime path should be clarified before broad refactors.
- There is no automated CI check in this repo yet that verifies every endpoint always includes `code` in all error branches; compliance is currently convention + review driven.
+31
View File
@@ -0,0 +1,31 @@
# Backend Development Guidelines
> Best practices for backend development in this project.
---
## Overview
This directory captures backend conventions observed from the current repository (not aspirational rules).
---
## Guidelines Index
| Guide | Description | Status |
|-------|-------------|--------|
| [Directory Structure](./directory-structure.md) | Module organization and file layout | Completed |
| [Database Guidelines](./database-guidelines.md) | ORM patterns, queries, migrations | Completed |
| [Error Handling](./error-handling.md) | Error types, handling strategies | Completed |
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | Completed |
| [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels | Completed |
---
## Maintenance Notes
When behavior changes, update the corresponding guideline file in the same task/PR:
1. Keep statements grounded in repository evidence (code/config/tests/docs)
2. Add or refresh concrete file-path examples
3. Keep anti-patterns tied to existing rules or existing legacy code evidence
+109
View File
@@ -0,0 +1,109 @@
# Logging Guidelines
> How logging is done in this project.
---
## Overview
Backend uses `structlog` integrated with stdlib logging configuration.
Evidence:
- Logger factory: `backend/src/core/logging/logger.py`
- Logging config and processors: `backend/src/core/logging/config.py`
- Formatter/JSON pipeline: `backend/src/core/logging/formatters.py`
- Request context middleware: `backend/src/core/logging/middleware.py`
- Runtime usage examples: `backend/src/app.py`, `backend/src/v1/todo/service.py`
---
## Log Levels
Observed level usage:
- `debug`: low-level diagnostic events (e.g., JWT success branch)
- `backend/src/v1/users/dependencies.py`
- `info`: normal lifecycle and business events
- `backend/src/app.py` (startup/initialized)
- `backend/src/v1/todo/service.py` (`todo_created`, `todo_updated`, etc.)
- `warning`: recoverable issues, unauthorized attempts, validation problems
- `backend/src/app.py` (HTTP and validation warnings)
- `backend/src/v1/todo/service.py` (unauthorized operations)
- `error` / `exception`: failures needing investigation
- `backend/src/app.py` (`logger.exception("Unhandled error")`)
- repositories with `logger.exception(...)` before re-raise
---
## Structured Logging
Conventions from current setup:
1. Always obtain logger through `core.logging.get_logger(...)`.
2. Use structured fields (keyword args / bound context), not string interpolation only.
3. Request-level context can be attached by `RequestContextMiddleware` (`request_id`, `method`, `path`, `client_ip`, `user_id`) when the middleware is mounted.
4. JSON output is enabled by default (`runtime.log_json = True`), with fallback plain formatter supported.
Examples:
- `backend/src/core/logging/middleware.py` binds and propagates request context (middleware implementation)
- `backend/src/core/logging/config.py` configures processors and handlers
- `backend/tests/integration/test_fastapi_logging_integration.py` mounts the middleware in test FastAPI app and asserts context fields in emitted logs
Current wiring note:
- `backend/src/app.py` currently does **not** call `app.add_middleware(RequestContextMiddleware)`; treat middleware context fields as opt-in wiring, not guaranteed global behavior.
---
## What to Log
Log these consistently:
- Service lifecycle events (startup/shutdown/init failures)
- `backend/src/app.py`
- `backend/src/services/base/service_interface.py`
- Security/auth failures and unauthorized access attempts
- `backend/src/v1/users/dependencies.py`
- `backend/src/v1/todo/service.py`
- Database or external dependency failures with stable context fields
- `backend/src/v1/*/repository.py` patterns
- `backend/src/services/base/{redis.py,supabase.py}`
---
## What NOT to Log
Sensitive data should be redacted; avoid raw secrets/tokens/credentials.
Evidence:
- Sensitive field defaults in `backend/src/core/config/settings.py` (`password`, `token`, `authorization`, etc.)
- Redaction processor tests:
- `backend/tests/unit/test_logging_filters.py`
- `backend/tests/unit/test_logging_config.py`
Avoid logging:
- Raw access tokens / refresh tokens / cookies
- Passwords, API keys, secret values
- Full untrusted payload dumps containing PII
---
## Anti-patterns / Forbidden Patterns
- Do not use `print()` in backend runtime code; use project logger APIs.
- Rule source: `backend/AGENTS.md`
- Do not bypass logging config by ad-hoc logger setup in feature modules.
- Use `configure_logging(...)` from `backend/src/core/logging/config.py`.
- Do not log sensitive fields without redaction.
- Do not assume request context fields exist unless `RequestContextMiddleware` is explicitly mounted.
---
## Uncertainties (documented, not invented)
- This repo has tests for logging behavior and redaction, but no single documented required schema for every log event across all modules (beyond context fields and processor output).
- `core.logging.middleware.register_exception_handlers(...)` exists but app-level error handling is primarily in `backend/src/app.py`; effective production wiring should remain explicit per app bootstrap.
+130
View File
@@ -0,0 +1,130 @@
# Quality Guidelines
> Code quality standards for backend development.
---
## Overview
Backend quality gates are centered around:
1. Static checks (`ruff`, `basedpyright`) via pre-commit
2. Pytest suites under `backend/tests`
3. Layering and contract discipline from `backend/AGENTS.md`
Evidence:
- `.pre-commit-config.yaml` (`basedpyright` + `ruff`, scoped to `^backend/`)
- `pyproject.toml` (`pytest` config, test paths, live marker)
- `backend/AGENTS.md` (layering, logging, error contract, DB and env rules)
---
## Forbidden Patterns
### 1) Error swallowing
- Forbidden by project rule: do not catch/log and silently continue.
- Root rule source: `AGENTS.md`.
Legacy examples to avoid extending:
- `backend/src/v1/users/dependencies.py` (`_verify_user_with_supabase`) catches broad exceptions and returns `None` fallback.
- `backend/src/services/base/service_interface.py` (`initialize_registered_services` / `close_registered_services`) catches broad exceptions and continues lifecycle flow.
### 2) Runtime `print()` usage
- Backend runtime must use `core.logging`, not `print()`.
- Rule source: `backend/AGENTS.md`.
### 3) Unstructured HTTP error contracts
- Business/API errors must not rely only on free-text `detail`; use stable `code` (+ optional `params`).
- Sources:
- `backend/AGENTS.md`
- `docs/protocols/common/http-error-codes.md`
### 4) Reading env by ad-hoc `os.getenv` in runtime
- Runtime configuration must go through `core.config.settings`.
- Source: `backend/AGENTS.md` and implementation in `backend/src/core/config/settings.py`.
---
## Required Patterns
### Layering discipline
- Use `schema -> repository -> service` flow.
- Repository: query/CRUD + `flush`; no transaction ownership.
- Service: authz/business rules + commit/rollback boundary.
Evidence:
- `backend/src/v1/todo/repository.py` vs `backend/src/v1/todo/service.py`
- `backend/src/v1/schedule_items/repository.py` vs `backend/src/v1/schedule_items/service.py`
- `backend/src/core/db/session.py` docs explicitly state caller (service) owns commit/rollback
### Typed boundary models
- Prefer Pydantic models with strict extras handling for API/request contracts.
- Evidence across schemas:
- `backend/src/v1/todo/schemas.py`
- `backend/src/v1/schedule_items/schemas.py`
- `backend/src/v1/users/schemas.py`
### Protocol alignment
- If API contract changes (error codes/data formats), update corresponding `docs/protocols/**` docs in same change.
- Source: root `AGENTS.md` + backend rules.
---
## Testing Requirements
Observed baseline:
- Unit tests: `backend/tests/unit/**`
- Integration tests: `backend/tests/integration/**`
- Live e2e tests separated by marker `live`: `backend/tests/e2e/README-live.md`
Pytest setup evidence:
- `pyproject.toml`
- `testpaths = ["backend/tests"]`
- `markers = ["live: ..."]`
Practical expectations for backend changes:
- Add/adjust tests for changed behavior where possible (especially contract/business logic).
- Keep live tests opt-in unless task explicitly requires real dependency runs.
---
## Code Review Checklist
Use this checklist for backend PR review:
1. **Layering**
- Router stays thin (`backend/src/v1/*/router.py`)
- Service owns transaction and business decisions
- Repository does not own commit/rollback
2. **Error contract**
- Responses align with RFC7807 + stable `code`
- New/changed codes reflected in `docs/protocols/common/http-error-codes.md`
3. **Logging**
- Uses `core.logging.get_logger`
- No raw sensitive values in logs
4. **Config/secrets**
- Reads configuration via `core.config.settings`
- No hardcoded secrets
5. **Tests and checks**
- Relevant pytest scope updated if behavior changed
- `ruff` + `basedpyright` remain passing for backend scope
---
## Uncertainties (documented, not invented)
- Backend runtime still contains some legacy fallback exception patterns in specific infra/dependency paths (for example in `backend/src/v1/users/dependencies.py` fallback helper). New code should avoid expanding these patterns unless explicitly required.
- No dedicated, repository-wide automated rule currently enforces every layering guideline; enforcement is primarily pre-commit + review discipline.