Build Recipe
Summary
The ordered, deterministic checklist for generating a new application of the
standard shape. Every step is fixed; only the resource model (step 2), the
codegen pass (step 3), and the pipeline family (step 8) need app-specific work.
The reference pages behind this recipe are organized as an anatomy: the
domains layer, the OpenAPI contract that drives routing and the admin UI,
the interfaces/infra seam, the auto-rendered UI client, and an optional
pipelines layer — see the Standards Reference index.
This page is served by the standards MCP as docs://modules/build_recipe. The
start_app prompt on that server collects the two axes and hands off here; pull
exact files with get_app_component.
🚀 Bootstrap
From the standards docs checkout:
| cp -r <docs>/standards/src/templates/app apps/<your-app>
cd apps/<your-app>
|
Pair it with the React client template when a UI is needed (auto-rendered
admin from the spec — see 40 – UI):
| cp -r <docs>/standards/src/templates/client apps/<your-app>-client
cd apps/<your-app>-client && npm install
|
Install the internal libraries — editable local checkouts (preferred in
development) or the private index (CI / fresh machines). Credentials are
environment-only; never commit them.
| # local checkouts
pip install -e ../aetos/openapi-first -e ../aetos/mongo-ops -e ../aetos/hexa \
-e ../aetos/dagpipe -e ../aetos/doc-forge
# private index
pip install --extra-index-url \
"https://$PIP_REGISTRY_USERNAME:$PIP_REGISTRY_PASSWORD@pip.aetoskia.com/simple" .
|
📋 Agent prompt (copyable)
| # Build a new application from the Aetos Standards blueprint
You are coding a complete new backend application conforming to the Aetos
standard app blueprint. Execute every step in order. Only steps 2–4 use the
app-specific details provided by the user; everything else is deterministic.
## Before writing any code
Connect to the `standards` MCP server and read:
- `docs://nav` (the module list)
- `docs://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)
- `docs://modules/layering_rules` (the dependency rules)
Then:
- call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs`
and note the Tier A libraries that already exist — never reimplement one;
- call `list_app_components()` and pull each file you need with
`get_app_component(kind)` instead of writing it from scratch.
## Steps
1. **Scaffold** — copy `standards/src/templates/app/` into `apps/<your-app>/`
(or pull the same tree via `get_app_template`). Keep the tree: `domain/`,
`infra/`, `interfaces/`, `pipelines/`, `tests/`, `scripts/`, plus
`pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, `main.py`,
`worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`.
2. **Author the spec (app-specific)** — write `openapi.yaml`: resources,
schemas, `operationId`s, security, and extension metadata. This is the
contract and drives all routing. Every operation MUST have an `operationId`.
3. **Generate the scaffold (codegen)** — from the spec, generate the wire
models and the exact handler signatures:
```bash
openapi-first models openapi.yaml -o _gen/models.py
openapi-first routes openapi.yaml -o _gen/routes \
--use-models --models-module _gen.models --inject-request --package --template-style
```
`_gen/models.py` is the **wire contract** (Pydantic, one class per
`components.schemas` entry). `_gen/routes/<resource>/routes.py` lists every
`operationId` with its exact parameter signature. `--template-style` makes
those stubs `async def` with a `body` parameter, matching the handlers you
keep in `interfaces/` (without it they are sync `def` stubs taking
`payload`; both bind the same way). Treat `_gen/` as generated
(never hand-edit it) — the canonical handlers live in `interfaces/`.
4. **Write each CRUD slice (app-specific)** — per resource create the four-file
slice: `models.py` (domain model, **no `id`**), `ports/db.py`
(`class XDocument(BaseDocument, X)` — id comes from `BaseDocument`),
`ports/service.py`, and optional `converters.py`. Follow the CRUD template.
5. **Wire the composition root** — in `init.py`, build leaf repositories, then
dependent repositories, then services; add infra adapters (db, cache, stores,
progress bus, fetchers). Expose `get_svc` overloads.
6. **Add routes** — create `interfaces/<resource>/routes.py` thin handlers whose
names match the `operationId`s (use the signatures from step 3 as your
checklist) and re-export them in `interfaces/openapi.py`. Add the cache
decorator to read endpoints.
7. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app
with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll
the job collection, run the pipeline, and publish progress.
8. **Add pipeline families (only if data-driven)** — copy the worked family
`pipelines/appointment_reminder/` to `pipelines/<family>/` with `abc.py`,
`impl.py`, `pipeline.yaml`, `resources/`, and reuse `pipelines/shared/`
(loader + registry). Register the root, add the `job.kind` handler in
`worker.py`. Add fixtures and a parity test.
9. **Add infrastructure only where needed** — Tier A capability: use the
library. Tier B capability: copy the template. Never hand-write a Tier A
library.
10. **Tests** — integration tests with fakes at the HTTP edge; unit tests for
domain and pipeline logic; a config-consistency test for rule-driven
pipelines. Include `tests/integration/test_openapi_app_builds.py` (imports
the app, asserts `import main` binds every operation).
11. **Scripts and seeds** — operator scripts under `scripts/` using
`init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.<name>`.
12. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package
`py.typed`, and build the docs.
**Definition of done:**
- `python -c "import main"` succeeds.
- `pytest` passes.
- The app serves every operation in the spec; the worker drains its queue.
- No domain module imports a web framework.
- No Tier A library was reimplemented.
|
🧭 Where the steps land
The steps map onto the layers as follows:
| Step |
Package / file |
Depends on |
| 1 · Scaffold |
whole templates/app/ |
— |
| 2 · Spec |
openapi.yaml |
— |
| 3 · Codegen |
_gen/ (generated) |
spec, openapi-first |
| 4 · CRUD slices |
domain/<resource>/ |
spec, CRUD template |
| 5 · Wiring |
init.py |
every slice |
| 6 · Routes |
interfaces/<resource>/ |
services, openapi route registry |
| 7 · Bootstrap |
main.py, worker.py |
composition root |
| 8 · Pipelines |
pipelines/<family>/, shared/ |
library pipeline engine |
| 9 · Infra |
infra/ |
domain ports |
| 10 · Tests |
tests/ |
app + fixtures |
| 11 · Scripts |
scripts/ |
composition root |
| 12 · Docs |
docstrings + docs build |
code, Doc Forge |
🔧 The codegen fast path, in detail
openapi-first is spec → code only (it never generates a spec). Use it to erase
the two mechanical parts of the build:
| Generated artifact |
Replaces |
Notes |
_gen/models.py |
hand-writing Pydantic wire models |
one class per components.schemas entry |
_gen/routes/<resource>/routes.py |
hand-typing handler signatures |
names == operationIds; request: Request injected |
Rules of the road:
- Wire models are not domain models.
_gen/models.py is the HTTP contract;
domain/<resource>/models.py is your business shape. If the spec's schema has
a required id: str, keep it off the domain model and let the document get its
id from BaseDocument.
- Use
--template-style. Stubs are then async def ...(request, body: ...)
matching interfaces/; without it they are sync def ...(payload: ...).
Either binds (the router introspects the signature), but --template-style
keeps the scaffold consistent with the code you keep.
- Resource names are sanitized.
/fetch-requests → fetch_requests.py.
- Multipart bodies expand to
UploadFile = File(...) / Form(...) params.
_gen/ is disposable. Regenerate on every spec change; git diff shows
exactly what moved.
⚠️ Definition of done
python -c "import main" succeeds.
pytest passes.
- The app serves the spec's operations; the worker drains its queue.
- No domain module imports a web framework.
- No Tier A library was reimplemented.