# AI Agent Instructions
A Rust backend generated by [Puerto](https://github.com/Guuri11/Puerto): DDD + Hexagonal
(Ports & Adapters), three crates, REST API with poem-openapi. This file is the entry point for any
coding agent (`CLAUDE.md` imports it). Each crate has its own detail:
`business/AGENTS.md`, `infrastructure/AGENTS.md`, `presentation/AGENTS.md`.
## Architecture
```
presentation → infrastructure → business (application → domain)
```
Dependencies point inward only. `business` depends on no other crate of this workspace;
`infrastructure` never depends on `presentation`.
| `business` | domain (models, value objects, errors, repository ports, use case traits) + application (use case impls) | imports a database driver, HTTP framework or adapter |
| `infrastructure` | adapters implementing the domain ports (in-memory or Postgres repositories) | contains business rules |
| `presentation` | HTTP: routes, DTOs, responses, error mapping, health probes, DI wiring | exposes a domain model in a response |
**The Puerto runtime.** What every Puerto project shares is not copied into it: it is a crate,
declared once in the root `Cargo.toml` (`[workspace.dependencies]`, on the same minor as the
`puerto` CLI) and inherited by each member with `{ workspace = true }`.
| `puerto-core` | `RepositoryError` (what every repository port fails with), `PageRequest` / `Page<T>` (paged lists, limit capped at 100) | `use puerto_core::{Page, PageRequest, RepositoryError};` |
| `puerto-web` | the server (`App`: tracing, CORS, graceful shutdown), `Config` (read from the environment and `.env`), the `/health` and `/health/ready` probes, `ErrorResponse` | `presentation` only — `main.rs`, `generated/bootstrap.rs`, `use puerto_web::ErrorResponse;` |
| `puerto-sqlx` (`--db` projects) | the Postgres pool, migrations at startup, the readiness `ping`, `map_sqlx_error` (driver error → `RepositoryError`) | `infrastructure` only — re-exported by `infrastructure/src/db.rs`, which keeps the project's `MIGRATOR` |
`puerto-core` depends on nothing but `thiserror`, so the domain may use it; `puerto-web` carries
poem and stays in `presentation`; `puerto-sqlx` carries sqlx and stays in `infrastructure`. Do not
copy their code into the project; upgrade them with the `puerto` CLI (`puerto validate` warns when
they drift apart). Your own middleware and routes go in `presentation/src/app.rs`, which no
generator touches.
**Modules are declared inline in each crate's `lib.rs`** (nested `pub mod {}` blocks). There are
no `mod.rs` files: adding a use case is one line in `lib.rs` plus its file.
## `puerto.toml` is the source of truth
It describes every entity, its fields and use cases. Generate code from it instead of writing the
wiring by hand:
```bash
puerto generate scaffold <Name> -- title:String price:i64! # every layer of a CRUD entity
puerto generate use-case <Entity> <action> # one more use case (snake_case)
puerto generate value-object <Name> <type> # a shared value object
puerto generate migration <name> # SQL migration (--db projects)
puerto generate bootstrap # rewire DI from puerto.toml
puerto validate # check puerto.toml against the code
```
- **Never hand-edit `presentation/src/generated/bootstrap.rs`** — it is rewritten from
`puerto.toml`. Change the manifest, then run `puerto generate bootstrap`.
- `puerto generate scaffold` refuses to overwrite an entity that already has files; `--force`
discards hand-written changes in them.
## Frontend (`web/`)
A React + daisyUI app on Bun that talks to the API under `/api`. It follows the same Dependency
Rule as the crates (`domain` ← `application` ← `infrastructure` ← `presentation`), checked by
`make web/lint`. Its rules — layers, generated files, where use cases go — are in `web/AGENTS.md`.
The OpenAPI spec is the contract between the two halves: `docs/api/openapi.json`, exported by a
test in `bootstrap.rs`. **After a scaffold and after touching a route or a DTO, run
`make web/sync`** (it re-exports the spec, regenerates the frontend's wire types and formats
`web/`) and commit both. Until then `make test` fails — that failure is the drift check.
`puerto generate scaffold` writes a CRUD entity's screens too; `puerto generate web <Name>` writes
them alone.
## Conventions
- **TDD.** Write the failing test first, then the minimum code. Test names:
`should_<expected_behaviour>_when_<scenario>`, with `// Arrange` / `// Act` / `// Assert`.
- **Errors are machine-readable codes** (`product.not_found`,
`product.validation_error.title_empty`), never sentences. The API returns them in
`ErrorResponse.name`; domain validation failures are `422`.
- **Logging:** `tracing::info!` / `warn!` / `error!` directly — no injected logger. A use case logs
`<use_case>: start` and `<use_case>: done`; never log request payloads (they are user data).
- **No `unwrap()` / `expect()`** outside tests.
- **API First:** change `routes.rs` + `dto.rs` (the contract) before the implementation.
## Check before you are done
```bash
make test # unit + integration tests
make lint # clippy -D warnings — the toolchain is pinned in rust-toolchain.toml
make format # rustfmt check
```
`rust-toolchain.toml` pins the Rust version: clippy gains lints between releases, so a pinned
toolchain is what makes `make lint` give the same answer on every machine.
## How to work here
**Make one commit per piece.** Finish one use case (or one fix), get `make test` and `make lint`
green, commit, then start the next. A rule like "small steps" that is not tied to anything
checkable does not get followed; a commit per piece is visible in `git log`.
**Where a human must read the diff** — calibrate by how forgiving the system is when the code is
wrong:
- **Tier 1 — fails silently, slowly or expensively.** A green test suite is not enough; a person
reads the whole diff before it is committed: SQL migrations (above all destructive ones),
anything touching money or other arithmetic the business relies on, authentication and
authorization checks, uniqueness / deduplication rules, data that crosses a tenant or customer
boundary.
- **Tier 2 — fails loudly and reversibly.** The checks above are enough: DTO ↔ domain mapping,
adapters covered by real integration tests, read-only queries, docs.
A change touching both tiers is Tier 1.
**Write decisions down.** When a design decision rests on context that is not in the code, record
it — an ADR under `docs/adr/` or an update to these files — so the next session inherits the
reason instead of re-deriving it or undoing it.