diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2bd344a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,107 @@ +# 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. +- **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.