generated from john/python-template
114 lines
6.1 KiB
Markdown
114 lines
6.1 KiB
Markdown
# AGENTS.md
|
|
|
|
Orientation for AI agents working in this repository. This file is a **router**, not a spec:
|
|
it points at canonical authority and flags the traps that are expensive to discover by trial.
|
|
Where this file and `docs/*` disagree, `docs/*` wins.
|
|
|
|
## What This Is
|
|
|
|
A document transcription system that preserves durable archival records (Documents, Sources,
|
|
People) and executes page transcription asynchronously through vision/LLM providers. Its
|
|
defining constraint is **evidence**: every machine attempt is recorded append-only with
|
|
request/response provenance. Features that would lose, mutate, or obscure that history are
|
|
wrong regardless of how convenient they are.
|
|
|
|
Stack: Python 3.12+ · FastAPI + NiceGUI · SQLModel/SQLAlchemy (SQLite-first, PostgreSQL-
|
|
compatible) · Pydantic V2 · asyncio worker · OpenRouter adapter.
|
|
|
|
## Commands
|
|
|
|
This is a `uv` project. **Nothing is on `PATH`** — `ruff`, `ty`, and `pytest` all require
|
|
`uv run`. Bare invocations fail with command-not-found.
|
|
|
|
```bash
|
|
uv run ruff check . # lint (blocking in pre-commit)
|
|
uv run ruff format --check . # format (blocking in pre-commit)
|
|
uv run ty check # types (blocking in pre-commit)
|
|
uv run pytest -q -m "not external" # default verification run
|
|
```
|
|
|
|
`external` marks tests that hit live services; always exclude it unless explicitly asked.
|
|
All four commands are expected to pass clean — there is no tolerated baseline of failures.
|
|
If `ty` reports something, fix it or suppress it inline *with a rationale comment*; a bare
|
|
`ignore` will not survive review.
|
|
|
|
## Authority Order
|
|
|
|
Resolve every question in this order, and stop at the first that answers it:
|
|
|
|
1. **`docs/*`** — canonical. Start at [`docs/index.md`](docs/index.md), which defines the
|
|
reading order. `docs/invariant/*` holds cross-version rules that outlive any release.
|
|
2. **`.github/instructions/*.md`** — active steering, auto-attached when you edit matching
|
|
paths. Covers services, UI, error handling, and documentation sync.
|
|
3. **`.github/skills/*`** — periodic audit procedures (code review, provenance, test
|
|
effectiveness).
|
|
4. **`tests/`** — deterministic enforcement. A guard test is the ground truth for whatever
|
|
rule it encodes.
|
|
|
|
`docs/reviews/**` is **not** canonical. Those are dated, opinionated snapshots that were
|
|
accurate when written and may since have been fixed, superseded, or found wrong.
|
|
|
|
## Layout
|
|
|
|
| Path | Role |
|
|
| :--- | :--- |
|
|
| `src/transcription/ui/**`, `api/**` | Interface. No direct persistence access. |
|
|
| `src/transcription/services/**` | Domain logic and transaction ownership. |
|
|
| `src/transcription/db/**` | Models and persistence. |
|
|
| `src/transcription/providers/**` | Provider adapters; provider details stop here. |
|
|
| `src/transcription/worker.py` | Asyncio worker loop. |
|
|
| `tests/` | Includes boundary/contract guards, not just behavior tests. |
|
|
|
|
## Enforced Boundaries
|
|
|
|
These are not conventions — a test fails if you break them:
|
|
|
|
- **No service-to-service imports** (`test_service_boundaries.py`). Compose in the caller.
|
|
- **No persistence access from pages/components** (`test_ui_boundaries.py`, allowlist-based).
|
|
- **No hand-rolled error notifications in UI** — use the shared error presenter.
|
|
- **No stringly-typed status literals** — use the enums (`test_model_contract_guards.py`).
|
|
- **Attempt history is append-only** (`test_v42_evidence.py`).
|
|
- **`docs/schema.md` stays field-accurate** with `db/models.py`.
|
|
- **Orphans are tracked, not tolerated** — `test_orphan_sweep.py` records each retained
|
|
orphan with rationale in `KNOWN_ORPHANS`.
|
|
|
|
## Traps
|
|
|
|
Non-obvious things that have already caused real bugs here:
|
|
|
|
- **`AppError.message` vs `AppError.detail`.** `message` is user/API-facing and must stay
|
|
generic — never put exception text or filesystem paths in it. `detail` is internal-only and
|
|
is what reaches logs and `ExecutionAttempt.error_detail`. Putting root-cause data in
|
|
`message` leaks; removing it from `detail` silently degrades provenance. See
|
|
`docs/error_handling.md`.
|
|
- **Two competing atomicity invariants in `services/workflows.py`.** Intermediate pages must
|
|
commit individually (durability across a long multi-page job); the *final* page must commit
|
|
atomically with the terminal job status. Collapsing the batch into one transaction satisfies
|
|
the second and destroys the first. Both are guarded — `test_workflows_reliability.py` and
|
|
`tests/integration/test_pipeline_atomicity.py`.
|
|
- **Shared symbols have more consumers than the obvious one.** Before changing a model field,
|
|
exception attribute, or helper return value, grep for every consumer including tests.
|
|
Evidence and logging paths frequently read the same fields the UI does.
|
|
- **`Tag` is owned by two services, on purpose.** Every other model has exactly one owning
|
|
service, so the ownership rule reads as absolute — it isn't. `Tag` is a single table reached
|
|
through two `RegistryService[Tag]` facades, `TagRegistry` (documents) and `PersonTagRegistry`
|
|
(people), which count usage through `DocumentTag` and `PersonTag` respectively. Changing tag
|
|
semantics through one facade silently changes the other. Consolidating them under one service
|
|
is not a cleanup; it makes the other side a cross-aggregate writer.
|
|
- **Import style:** ruff `isort` runs with `force-single-line = true`. One import per line.
|
|
- **Latent defects have ordering constraints.** Some code is unreachable only because of a
|
|
current setting or single-instance deployment. Fix it *before* the change that unblocks it,
|
|
not after.
|
|
|
|
## Change Protocol
|
|
|
|
- **Write the failing test first** for behavioral fixes, and confirm it actually fails for the
|
|
reason you think. Several bugs here were subtle enough that a test written afterward would
|
|
have passed against the broken code.
|
|
- **Update docs in the same change** when you alter a contract, behavior, or scope — see
|
|
`.github/instructions/documentation-sync.instructions.md`.
|
|
- **Do not commit unless asked.** Making a requested change is not consent to commit it.
|
|
- **Do not push or open PRs on your own initiative.**
|
|
- **Scope discipline:** fix what was asked plus what your change genuinely breaks. Pre-existing
|
|
unrelated issues are a separate conversation.
|