From f8cf35de0e6590934bbfa23efed171bd37c2cb9a Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Tue, 20 Oct 2026 15:02:11 +0530 Subject: [PATCH] Phase 8: add the Reference App wiki walkthrough (06_reference_app) - four pages walking the vet sample: overview, domain & API, pipelines & worker, spec-driven client admin - mkdocs nav section, index + start-an-app cross links - bundle now exposes the walkthrough as wiki_reference_app_* modules --- standards/mcp/index.json | 2 +- standards/mcp/modules/wiki_index.json | 2 +- .../wiki_reference_app_client_admin.json | 8 + .../wiki_reference_app_domain_and_api.json | 8 + .../modules/wiki_reference_app_overview.json | 8 + ...ki_reference_app_pipelines_and_worker.json | 8 + standards/mcp/modules/wiki_start_an_app.json | 2 +- standards/mcp/nav.json | 20 + standards/src/mkdocs.wiki.yml | 5 + standards/src/wiki/03_start_an_app.md | 1 + .../src/wiki/06_reference_app/01_overview.md | 70 ++ .../06_reference_app/02_domain_and_api.md | 73 ++ .../03_pipelines_and_worker.md | 85 ++ .../wiki/06_reference_app/04_client_admin.md | 60 + standards/src/wiki/index.md | 5 +- standards/wiki/01_overview/index.html | 178 ++- standards/wiki/02_components/index.html | 178 ++- standards/wiki/03_start_an_app/index.html | 181 ++- .../wiki/04_development/01_testing/index.html | 178 ++- .../04_development/02_releasing/index.html | 178 ++- standards/wiki/05_mcp_setup/index.html | 186 ++- .../06_reference_app/01_overview/index.html | 1007 +++++++++++++++++ .../02_domain_and_api/index.html | 990 ++++++++++++++++ .../03_pipelines_and_worker/index.html | 1006 ++++++++++++++++ .../04_client_admin/index.html | 974 ++++++++++++++++ standards/wiki/404.html | 178 ++- standards/wiki/index.html | 186 ++- standards/wiki/search/search_index.json | 2 +- 28 files changed, 5760 insertions(+), 19 deletions(-) create mode 100644 standards/mcp/modules/wiki_reference_app_client_admin.json create mode 100644 standards/mcp/modules/wiki_reference_app_domain_and_api.json create mode 100644 standards/mcp/modules/wiki_reference_app_overview.json create mode 100644 standards/mcp/modules/wiki_reference_app_pipelines_and_worker.json create mode 100644 standards/src/wiki/06_reference_app/01_overview.md create mode 100644 standards/src/wiki/06_reference_app/02_domain_and_api.md create mode 100644 standards/src/wiki/06_reference_app/03_pipelines_and_worker.md create mode 100644 standards/src/wiki/06_reference_app/04_client_admin.md create mode 100644 standards/wiki/06_reference_app/01_overview/index.html create mode 100644 standards/wiki/06_reference_app/02_domain_and_api/index.html create mode 100644 standards/wiki/06_reference_app/03_pipelines_and_worker/index.html create mode 100644 standards/wiki/06_reference_app/04_client_admin/index.html diff --git a/standards/mcp/index.json b/standards/mcp/index.json index 416cb71..50b698c 100644 --- a/standards/mcp/index.json +++ b/standards/mcp/index.json @@ -1,6 +1,6 @@ { "project": "standards", "type": "standards", - "modules_count": 24, + "modules_count": 28, "source": "hand-authored" } \ No newline at end of file diff --git a/standards/mcp/modules/wiki_index.json b/standards/mcp/modules/wiki_index.json index aacaf24..78a58ab 100644 --- a/standards/mcp/modules/wiki_index.json +++ b/standards/mcp/modules/wiki_index.json @@ -2,7 +2,7 @@ "module": "wiki_index", "content": { "path": "wiki_index", - "docstring": "# 🧩 Standards — the Aetos App Blueprint\n\nOne reusable architecture for every backend application in the Aetos ecosystem:\na spec-first HTTP edge, a framework-free domain core, a pluggable infrastructure\nlayer, and a single composition root.\n\nFill in **two axes only** and a new app is done: your **resource model**, and\n(if needed) your **pipeline family**. The blueprint itself never names a\nbusiness domain — expenses, health metrics, inventory, and analytics are all\nthe same shape underneath.\n\n> **Doc model:** this wiki is written for humans — how-to guides, the build\n> recipe, and quality gates. The enforceable rules (layering, composition root,\n> CRUD template, pipeline scaffold) and the library registry live in the\n> **reference** pages, mirrored by the machine-readable `standards` MCP bundle.\n> Query `find_library` before writing any infrastructure.\n\n---\n\n## 🚀 Key Features\n\n- 🧭 **Spec-first routing** — the OpenAPI document, not decorator routers, is\n the source of truth (operationId lookup)\n- 🏗️ **One-way layering** — `interfaces → domain → infra`, enforced\n- 🔌 **Single composition root** — every dependency is wired in exactly one place\n- 🧩 **Generic, not app-specific** — reusable rules versus two authoring axes\n- 📚 **Library-backed** — DB, DI, pipelines, ingestion, auth, and docs come from\n internal libraries, never hand-written\n- 🚦 **Explicit state machines** — durable job statuses, progress, and\n human-in-the-loop pauses\n\n---\n\n## 📁 Documentation Structure\n\n| Section | Description |\n|-----------------------------------------------------|--------------------------------------------------------|\n| [Overview](01_overview.md) | Philosophy, the one-way dependency rule, architecture |\n| [App Components](02_components.md) | The component model and how the pieces wire together |\n| [Start an App](03_start_an_app.md) | The entry point: gather the two axes, then build |\n| [Testing & Quality](04_development/01_testing.md) | Test layout and acceptance gates |\n\n**Reference** (separate site, mirrors the MCP bundle): the enforceable\nanatomy — start at the [Build Recipe](../lib/01_build_recipe/), then the\npillars: domains ([Layering](../lib/10_layering_rules/)), the OpenAPI\ncontract ([Composition Root](../lib/02_composition_root/),\n[Spec-First Routing](../lib/20_spec_first_routing/), [CRUD\nTemplate](../lib/21_crud_template/), [Auth\nIntegration](../lib/22_auth_integration/)), interfaces & infra ([Infra\nAdapters](../lib/30_infra_adaptors/)), UI ([Auto-Rendered\nAdmin](../lib/40_ui_admin/)), the optional pipelines layer ([Pipeline\nScaffold](../lib/50_pipeline_scaffold/), [Worker &\nProgress](../lib/51_worker_and_progress/), [Human-in-the-Loop](../lib/52_human_in_loop/)),\nand delivery/meta ([Genericity\nMatrix](../lib/92_genericity_matrix/), [Library\nRegistry](../lib/93_library_registry/)).\n\n---\n\n## 🔗 Related Resources\n\n- **MCP bundle** — `standards` server on `:8009`: read `docs://index`,\n `docs://nav`, `docs://modules/{module}`; call `find_library(capability)`\n- **Library registry** — the catalog of internal libraries behind every app\n (`libraries.json`, served as `registry://libraries`)\n- **Template** — the runnable skeleton at `standards/src/templates/app/`\n- **Start a build** — invoke the `start_app` prompt on the `standards` server,\n or read the [Start an App](03_start_an_app.md) guide\n\n---\n\n© Aetoskia Internal — `standards`\n", + "docstring": "# 🧩 Standards — the Aetos App Blueprint\n\nOne reusable architecture for every backend application in the Aetos ecosystem:\na spec-first HTTP edge, a framework-free domain core, a pluggable infrastructure\nlayer, and a single composition root.\n\nFill in **two axes only** and a new app is done: your **resource model**, and\n(if needed) your **pipeline family**. The blueprint itself never names a\nbusiness domain — expenses, health metrics, inventory, and analytics are all\nthe same shape underneath.\n\n> **Doc model:** this wiki is written for humans — how-to guides, the build\n> recipe, and quality gates. The enforceable rules (layering, composition root,\n> CRUD template, pipeline scaffold) and the library registry live in the\n> **reference** pages, mirrored by the machine-readable `standards` MCP bundle.\n> Query `find_library` before writing any infrastructure.\n\n---\n\n## 🚀 Key Features\n\n- 🧭 **Spec-first routing** — the OpenAPI document, not decorator routers, is\n the source of truth (operationId lookup)\n- 🏗️ **One-way layering** — `interfaces → domain → infra`, enforced\n- 🔌 **Single composition root** — every dependency is wired in exactly one place\n- 🧩 **Generic, not app-specific** — reusable rules versus two authoring axes\n- 📚 **Library-backed** — DB, DI, pipelines, ingestion, auth, and docs come from\n internal libraries, never hand-written\n- 🚦 **Explicit state machines** — durable job statuses, progress, and\n human-in-the-loop pauses\n\n---\n\n## 📁 Documentation Structure\n\n| Section | Description |\n|-----------------------------------------------------|--------------------------------------------------------|\n| [Overview](01_overview.md) | Philosophy, the one-way dependency rule, architecture |\n| [App Components](02_components.md) | The component model and how the pieces wire together |\n| [Start an App](03_start_an_app.md) | The entry point: gather the two axes, then build |\n| [Reference App](06_reference_app/01_overview.md) | The runnable vet sample — server, pipelines, client |\n| [Testing & Quality](04_development/01_testing.md) | Test layout and acceptance gates |\n\n**Reference** (separate site, mirrors the MCP bundle): the enforceable\nanatomy — start at the [Build Recipe](../lib/01_build_recipe/), then the\npillars: domains ([Layering](../lib/10_layering_rules/)), the OpenAPI\ncontract ([Composition Root](../lib/02_composition_root/),\n[Spec-First Routing](../lib/20_spec_first_routing/), [CRUD\nTemplate](../lib/21_crud_template/), [Auth\nIntegration](../lib/22_auth_integration/)), interfaces & infra ([Infra\nAdapters](../lib/30_infra_adaptors/)), UI ([Auto-Rendered\nAdmin](../lib/40_ui_admin/)), the optional pipelines layer ([Pipeline\nScaffold](../lib/50_pipeline_scaffold/), [Worker &\nProgress](../lib/51_worker_and_progress/), [Human-in-the-Loop](../lib/52_human_in_loop/)),\nand delivery/meta ([Genericity\nMatrix](../lib/92_genericity_matrix/), [Library\nRegistry](../lib/93_library_registry/)).\n\n---\n\n## 🔗 Related Resources\n\n- **MCP bundle** — `standards` server on `:8009`: read `docs://index`,\n `docs://nav`, `docs://modules/{module}`; call `find_library(capability)`\n- **Library registry** — the catalog of internal libraries behind every app\n (`libraries.json`, served as `registry://libraries`)\n- **Template** — the runnable sample at `standards/src/templates/app/`\n (server) and `standards/src/templates/client/` (React admin); walkthrough in\n the [Reference App](06_reference_app/01_overview.md)\n- **Start a build** — invoke the `start_app` prompt on the `standards` server,\n or read the [Start an App](03_start_an_app.md) guide\n\n---\n\n© Aetoskia Internal — `standards`\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/modules/wiki_reference_app_client_admin.json b/standards/mcp/modules/wiki_reference_app_client_admin.json new file mode 100644 index 0000000..b1b48ab --- /dev/null +++ b/standards/mcp/modules/wiki_reference_app_client_admin.json @@ -0,0 +1,8 @@ +{ + "module": "wiki_reference_app_client_admin", + "content": { + "path": "wiki_reference_app_client_admin", + "docstring": "# Client Admin — the spec-driven frontend\n\nThe client sample (`standards/src/templates/client/`) renders a full admin —\nauth pages, resource lists, forms, detail views — **from the server's\n`openapi.yaml`**, driven entirely by the x- extension fields. There are no\nhand-written screens per resource.\n\n## What ships\n\nA Vite + React + MUI app wiring the two Aetos packages:\n\n- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`,\n builds the router and the resource admin from the spec's schemas and x-\n fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)\n- **`@aetoskia/react-auth`** — login/register/logout against the auth-server\n paths declared in the spec's `securitySchemes.bearerAuth`, and attaches the\n JWT to every API call\n- a shared MUI theme (`src/shared-theme/`) and the shell\n (`Header` / `Footer` / `Home`)\n\n`src/openapi-config.ts` is the only configuration:\n\n```ts\nexport const specConfiguration: SpecConfiguration = {\n specUrl: `${apiBase}/openapi.json`,\n baseApiUrl: apiBase,\n title: \"Vet Admin\",\n getToken: () => tokenStore.get(),\n};\n```\n\n## Pair it with the sample server\n\n```bash\ncd apps/vet-sample-client\nprintf 'VITE_API_BASE_URL=http://localhost:8020\\nVITE_AUTH_BASE_URL=http://localhost:8020\\n' > .env.local\nnpm install\nnpm run dev\n```\n\nOpen the dev-server URL; the admin lists Parents, Pets, Treatments, Vets,\nAppointments and Jobs straight from the vet spec. Edit `openapi.yaml` on the\nserver and the client follows on reload — the spec is the contract on both\nsides.\n\n## Where the x- extensions come from\n\nThe canonical catalog lives in the react-openapi package\n(`spec-extensions.ts`, served to agents as the `specExtensions` module of the\nreact-openapi MCP bundle). The worked examples on the server side are the\n`Job` schema (enum selects via `x-edit-as: select` + `x-ui-type: enum`,\n`x-hidden` field masks, `x-list-columns`) and the FK fields on `Appointment`\n(`x-fk: {resource: pets, prefetch: true}`).\n\n## CI\n\nThe client ships a `drone-npm` shape (Type B in\n[Drone Pipelines](../lib/90_drone_pipelines/)) and follows the\n[Versioning](../lib/91_versioning/) standard (`package.json` +\n`CHANGELOG.md`).\n", + "objects": {} + } +} \ No newline at end of file diff --git a/standards/mcp/modules/wiki_reference_app_domain_and_api.json b/standards/mcp/modules/wiki_reference_app_domain_and_api.json new file mode 100644 index 0000000..154464d --- /dev/null +++ b/standards/mcp/modules/wiki_reference_app_domain_and_api.json @@ -0,0 +1,8 @@ +{ + "module": "wiki_reference_app_domain_and_api", + "content": { + "path": "wiki_reference_app_domain_and_api", + "docstring": "# Domain & API — the resource axis\n\nEvery aggregate follows the same four-file slice\n([CRUD Template](../lib/21_crud_template/)); walk `parents` first, then\n`appointments` when you need foreign keys.\n\n## The slice, end to end (parents)\n\n```\ndomain/parents/\n models.py # framework-free Pydantic model (id optional)\n ports/\n db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo()\n service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service()\ninterfaces/parents/\n routes.py # list/create/get/update/delete — names == operationIds\n```\n\nWiring happens in exactly one place — the composition root\n`init.py`:\n\n```python\nparent_repo = create_parent_repo()\n_resources[\"parent_svc\"] = get_parent_service(parent_repo)\n```\n\nRoutes resolve the service from app state through the shared helper\n(`interfaces/_common.py`):\n\n```python\nasync def list_parents(request: Request, limit: int = 20, offset: int = 0) -> dict:\n docs = await svc(request, \"parent\").search({})\n return paginate([dump(d) for d in docs], limit, offset)\n```\n\n## The spec is the contract\n\n`openapi.yaml` is OpenAPI 3.1 and the\nsingle source of truth: `main.py` builds an `OpenAPIFirstApp` that binds each\n`operationId` to a function of the same name in `interfaces/openapi.py` (a flat\nre-export of every routes module). There are no decorator routers.\n\nThe x- extension fields on schemas and properties are what make the admin\nclient render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and\nthe worked `Job` schema (enum selects, hidden fields, filterable columns).\n\n## Foreign keys (appointments)\n\n`Appointment` declares `pet`, `vet`, `treatment` as `dict | int | None`. The\nwrite path normalizes bare ids through `expand_ref`; the read path returns\nfully expanded objects:\n\n```python\npet = await expand_ref(svc(request, \"pet\"), body.get(\"pet\"), \"pet\")\n```\n\nThe list endpoint additionally filters by date prefix and embedded fk ids —\nclient-side over the repository result, the pattern for small reference data.\n\n## Ids on the wire\n\nThe domain models declare `id: int | None = None` and documents use the\n**integer-id** repository variants (`infra/db/repos/mongo_int.py`,\n`create_repo()` in `domain/*/ports/db.py`, `REPO_BACKEND=memory|mongo`). This\nkeeps the OpenAPI `id: integer` contract, the in-memory demo store, and the\nMongo persistence layer consistent.\n\n## Tests\n\n- `tests/integration/test_openapi_app_builds.py` — generic gate: every\n `operationId` resolves and the app builds. Copy it verbatim.\n- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a\n service double (no HTTP client needed).\n", + "objects": {} + } +} \ No newline at end of file diff --git a/standards/mcp/modules/wiki_reference_app_overview.json b/standards/mcp/modules/wiki_reference_app_overview.json new file mode 100644 index 0000000..2e85eab --- /dev/null +++ b/standards/mcp/modules/wiki_reference_app_overview.json @@ -0,0 +1,8 @@ +{ + "module": "wiki_reference_app_overview", + "content": { + "path": "wiki_reference_app_overview", + "docstring": "# Reference App — Overview\n\nThe standards ship a **runnable sample** of the whole blueprint: the **vet**\nveterinary-clinic application. It exists to be copied — it is the reference\nfor how a real domain sits on the architecture, and the code an agent pulls\nthrough the MCP tools (`get_app_template`, `get_app_component`).\n\n!!! note \"One sample, two templates\"\n The **server** sample lives at `standards/src/templates/app/`\n (FastAPI + openapi-first + mongo-ops + hexa). The **client** sample lives\n at `standards/src/templates/client/` (Vite + MUI + `@aetoskia/react-openapi`\n + `@aetoskia/react-auth`). Together they are the full-stack reference.\n\n---\n\n## The domain at a glance\n\n| Aggregate | What it demonstrates |\n|---------------|---------------------------------------------------------------|\n| **parents** | The simplest CRUD slice (no foreign keys) — copy this first |\n| **pets** | Embedded list-of-refs (`parents[]`), file upload, enum fields |\n| **treatments**| Plain resource with pricing fields |\n| **vets** | Plain resource; target of prefetch FKs |\n| **appointments** | Single FKs (`pet`, `vet`, `treatment`) expanded on read, filtered list |\n| **jobs** | The worker's queue — durable status machine |\n\nThe pipeline axis ships one worked family, **`appointment_reminder`**, plus the\nreusable `pipelines/shared/` scaffold and a `worker.py` that dispatches jobs by\n`job.kind`.\n\n## Run it\n\n```bash\ncp -r /standards/src/templates/app apps/vet-sample\ncd apps/vet-sample\npip install -e \".[dev]\"\n\nuvicorn main:app --reload # API on :8020 (memory backend, zero services)\npython worker.py # background worker (polls jobs)\npython -m scripts.seed # optional: seed demo data (mongo backend)\n```\n\nThe default `REPO_BACKEND=memory` means the API boots with **no external\nservices** — repositories are process-local dictionaries. Switch to\n`REPO_BACKEND=mongo` for persistence (see `.env.example`).\n\nThe client pairs with it:\n\n```bash\ncp -r /standards/src/templates/client apps/vet-sample-client\ncd apps/vet-sample-client\necho \"VITE_API_BASE_URL=http://localhost:8020\" > .env.local\necho \"VITE_AUTH_BASE_URL=http://localhost:8020\" >> .env.local\nnpm install\nnpm run dev # Vite dev server\n```\n\n!!! warning \"Auth\"\n The server ships with the `bearerAuth` security scheme declared\n (`{AUTH_SERVER}` introspection). Without a reachable auth-server the API\n answers `401` on resource routes; `/health` stays public. For a local\n no-auth demo, remove the `security` block from `openapi.yaml` and the\n global security entry — see\n [Auth Integration](../lib/22_auth_integration/).\n\n## Next\n\n- [Domain & API](02_domain_and_api.md) — walking the resource axis\n- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis\n- [Client Admin](04_client_admin.md) — the spec-driven frontend\n", + "objects": {} + } +} \ No newline at end of file diff --git a/standards/mcp/modules/wiki_reference_app_pipelines_and_worker.json b/standards/mcp/modules/wiki_reference_app_pipelines_and_worker.json new file mode 100644 index 0000000..8d016af --- /dev/null +++ b/standards/mcp/modules/wiki_reference_app_pipelines_and_worker.json @@ -0,0 +1,8 @@ +{ + "module": "wiki_reference_app_pipelines_and_worker", + "content": { + "path": "wiki_reference_app_pipelines_and_worker", + "docstring": "# Pipelines & Worker — the pipeline axis\n\nThe sample proves the full loop: **API creates a job document → worker polls →\npipeline family runs → job reaches a terminal status.**\n\n## The jobs slice\n\n`domain/jobs/` is an ordinary CRUD slice; its model is the durable state\nmachine the worker drives:\n\n```python\nclass Job(BaseModel):\n name: str\n kind: str # dispatch key\n status: Literal[\"pending\", \"processing\", \"done\", \"failed\"] = \"pending\"\n payload: dict | None = None\n retry_count: int = 0\n error_message: str | None = None\n```\n\n`POST /jobs` with `{\"name\": \"...\", \"kind\": \"appointment_reminder\",\n\"payload\": {\"appointment_id\": 1}}` enqueues work; `GET /jobs?status=...`\ninspects the queue.\n\n## The appointment_reminder family\n\n```\npipelines/appointment_reminder/\n abc.py # LoadPort / ComposePort / DeliverPort + root interface\n impl.py # concrete stages, wired root, register(\"appointment_reminder\", ...)\n pipeline.yaml # graph spec (nodes, edges, roots)\n resources/\n pipeline.yaml # runtime spec consumed by the loader\n patterns/reminder.yaml # message template fragments\npipelines/shared/\n ports.py loader.py registry.py # reused by every family\n```\n\nEach stage is a small, independently testable unit. The loader\n(`pipelines/shared/loader.py`) wires the root through `hexa.build` and merges\n`resources/patterns/*.yaml` into the stage-injected registry:\n\n```python\npipeline = get_appointment_reminder_pipeline(appointment_svc)\nreminder = await pipeline.run(appointment_id)\n```\n\nTo add a family: copy `appointment_reminder/`, replace the stages, call\n`register(kind, variant, root_cls)` — see\n[Pipeline Scaffold](../lib/50_pipeline_scaffold/).\n\n## The worker\n\n`worker.py` is the process that turns\ndocuments into execution:\n\n1. poll `job_svc.search({\"status\": \"pending\"})` on `WORKER_POLL_SECONDS`\n2. claim a job: `status → processing`\n3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)`\n4. on success `status → done`; on exception `status → failed`,\n `retry_count += 1`, `error_message` captured; jobs at\n `WORKER_MAX_RETRIES` are skipped\n\n```python\nHANDLERS: dict[str, Callable] = {\n \"appointment_reminder\": run_appointment_reminder,\n # add yours here\n}\n```\n\nThe worker shares the composition root: `from init import init` gives it the\nsame wired services the API uses — one place to add a dependency.\n\n!!! note \"Same process model in production\"\n API and worker are separate processes (see `docker-compose.yaml`: `api`,\n `worker`, `mongo`). Any in-memory progress bus is per-process; to surface\n live progress through the API, swap the bus adaptor for a broker-backed\n one — see [Worker & Progress](../lib/51_worker_and_progress/).\n\n## Tests\n\n- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired\n root against a fake service.\n- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry\n exhaustion, using an in-memory job service double and a cancelled poll task.\n", + "objects": {} + } +} \ No newline at end of file diff --git a/standards/mcp/modules/wiki_start_an_app.json b/standards/mcp/modules/wiki_start_an_app.json index 96acb9b..7e5c103 100644 --- a/standards/mcp/modules/wiki_start_an_app.json +++ b/standards/mcp/modules/wiki_start_an_app.json @@ -2,7 +2,7 @@ "module": "wiki_start_an_app", "content": { "path": "wiki_start_an_app", - "docstring": "# 🚀 Start an App\n\nThis is the entry point for building a new application from the blueprint. The\ngoal is one deterministic pass: you supply only the **two axes**, and the agent\ndoes the rest by reading the `standards` MCP, importing Tier A libraries, and\ncopying Tier B template components.\n\n> **Doc model:** this page is the human entry point. The deterministic,\n> machine-facing checklist lives in the reference page\n> [Build Recipe](../lib/01_build_recipe/), served as `docs://modules/build_recipe`.\n\n---\n\n## 🧾 What the agent needs from you\n\nGather these before starting — the `start_app` MCP prompt asks for them:\n\n1. **App name + one-line purpose.**\n2. **Axis 1 — resources.** Each resource, its fields, and relationships. Paste\n `openapi.yaml` if you already have it.\n3. **Axis 2 — pipelines.** Only if an input needs a multi-step transformation:\n the families and their input formats (or \"none\").\n4. **Auth needed now?** Default **no**. Auth is deferred (`py-jwt` primitives\n only), and an authless app is just a spec with no `security` keys — see\n [Spec-First Routing → Authless apps](../lib/20_spec_first_routing/#authless-apps).\n5. **Integrations.** Mongo, ingestion formats, email, LLM provider.\n\nIf you can state axes 1 and 2, the rest is mechanical.\n\n---\n\n## ▶️ How to start\n\nFrom an MCP client connected to the `standards` server:\n\n1. Invoke the **`intake`** prompt (or `start_app` with no arguments) to get the\n intake questions.\n2. Answer them, then invoke **`build`**\n (`build(app_name=..., resources=..., pipelines=..., auth=...)`), or use\n **`start_app`** to intake-and-build in one call.\n3. The prompt hands off to `docs://modules/build_recipe`; the agent scaffolds\n with `get_app_template()` and pulls exact files with\n `get_app_component(kind)`, authoring only the app-specific parts.\n4. To validate the standard without touching a real app, invoke **`trial`**\n (`trial(app_name=..., build_path=...)`) to build into a scratch directory,\n prove it builds, and report findings.\n\nManual alternative — copy the skeleton and follow the reference recipe:\n\n```bash\ncp -r /standards/src/templates/app apps/\n```\n\n---\n\n## 📚 Reference\n\n- [Build Recipe](../lib/01_build_recipe/) — the deterministic checklist\n- [Genericity Matrix](../lib/92_genericity_matrix/) — Tier A / B / C\n- [Library Registry](../lib/93_library_registry/) — the Tier A catalog\n- [App Components](02_components.md) — the component model\n\n---\n\n## Related\n\n- [01 – Overview](01_overview.md) · [02 – App Components](02_components.md) · [04 – Testing & Quality](04_development/01_testing.md)\n", + "docstring": "# 🚀 Start an App\n\nThis is the entry point for building a new application from the blueprint. The\ngoal is one deterministic pass: you supply only the **two axes**, and the agent\ndoes the rest by reading the `standards` MCP, importing Tier A libraries, and\ncopying Tier B template components.\n\n> **Doc model:** this page is the human entry point. The deterministic,\n> machine-facing checklist lives in the reference page\n> [Build Recipe](../lib/01_build_recipe/), served as `docs://modules/build_recipe`.\n\n---\n\n## 🧾 What the agent needs from you\n\nGather these before starting — the `start_app` MCP prompt asks for them:\n\n1. **App name + one-line purpose.**\n2. **Axis 1 — resources.** Each resource, its fields, and relationships. Paste\n `openapi.yaml` if you already have it.\n3. **Axis 2 — pipelines.** Only if an input needs a multi-step transformation:\n the families and their input formats (or \"none\").\n4. **Auth needed now?** Default **no**. Auth is deferred (`py-jwt` primitives\n only), and an authless app is just a spec with no `security` keys — see\n [Spec-First Routing → Authless apps](../lib/20_spec_first_routing/#authless-apps).\n5. **Integrations.** Mongo, ingestion formats, email, LLM provider.\n\nIf you can state axes 1 and 2, the rest is mechanical.\n\n---\n\n## ▶️ How to start\n\nFrom an MCP client connected to the `standards` server:\n\n1. Invoke the **`intake`** prompt (or `start_app` with no arguments) to get the\n intake questions.\n2. Answer them, then invoke **`build`**\n (`build(app_name=..., resources=..., pipelines=..., auth=...)`), or use\n **`start_app`** to intake-and-build in one call.\n3. The prompt hands off to `docs://modules/build_recipe`; the agent scaffolds\n with `get_app_template()` and pulls exact files with\n `get_app_component(kind)`, authoring only the app-specific parts.\n4. To validate the standard without touching a real app, invoke **`trial`**\n (`trial(app_name=..., build_path=...)`) to build into a scratch directory,\n prove it builds, and report findings.\n\nManual alternative — copy the skeleton and follow the reference recipe:\n\n```bash\ncp -r /standards/src/templates/app apps/\n```\n\n---\n\n## 📚 Reference\n\n- [Build Recipe](../lib/01_build_recipe/) — the deterministic checklist\n- [Genericity Matrix](../lib/92_genericity_matrix/) — Tier A / B / C\n- [Library Registry](../lib/93_library_registry/) — the Tier A catalog\n- [App Components](02_components.md) — the component model\n- [Reference App](06_reference_app/01_overview.md) — the runnable vet sample to copy\n\n---\n\n## Related\n\n- [01 – Overview](01_overview.md) · [02 – App Components](02_components.md) · [04 – Testing & Quality](04_development/01_testing.md)\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/nav.json b/standards/mcp/nav.json index 9af782e..36da649 100644 --- a/standards/mcp/nav.json +++ b/standards/mcp/nav.json @@ -114,6 +114,26 @@ "resource": "docs://modules/wiki_mcp_setup", "title": "🔌 MCP Setup" }, + { + "module": "wiki_reference_app_overview", + "resource": "docs://modules/wiki_reference_app_overview", + "title": "Overview" + }, + { + "module": "wiki_reference_app_domain_and_api", + "resource": "docs://modules/wiki_reference_app_domain_and_api", + "title": "Domain And Api" + }, + { + "module": "wiki_reference_app_pipelines_and_worker", + "resource": "docs://modules/wiki_reference_app_pipelines_and_worker", + "title": "Pipelines And Worker" + }, + { + "module": "wiki_reference_app_client_admin", + "resource": "docs://modules/wiki_reference_app_client_admin", + "title": "Client Admin" + }, { "module": "wiki_index", "resource": "docs://modules/wiki_index", diff --git a/standards/src/mkdocs.wiki.yml b/standards/src/mkdocs.wiki.yml index 39a6e77..1cf637f 100644 --- a/standards/src/mkdocs.wiki.yml +++ b/standards/src/mkdocs.wiki.yml @@ -70,6 +70,11 @@ nav: - Overview: 01_overview.md - App Components: 02_components.md - Start an App: 03_start_an_app.md + - Reference App: + - Overview: 06_reference_app/01_overview.md + - Domain & API: 06_reference_app/02_domain_and_api.md + - Pipelines & Worker: 06_reference_app/03_pipelines_and_worker.md + - Client Admin: 06_reference_app/04_client_admin.md - MCP: - Setup: 05_mcp_setup.md - Testing & Quality: 04_development/01_testing.md diff --git a/standards/src/wiki/03_start_an_app.md b/standards/src/wiki/03_start_an_app.md index 1ad66d9..f830a0c 100644 --- a/standards/src/wiki/03_start_an_app.md +++ b/standards/src/wiki/03_start_an_app.md @@ -59,6 +59,7 @@ cp -r /standards/src/templates/app apps/ - [Genericity Matrix](../lib/92_genericity_matrix/) — Tier A / B / C - [Library Registry](../lib/93_library_registry/) — the Tier A catalog - [App Components](02_components.md) — the component model +- [Reference App](06_reference_app/01_overview.md) — the runnable vet sample to copy --- diff --git a/standards/src/wiki/06_reference_app/01_overview.md b/standards/src/wiki/06_reference_app/01_overview.md new file mode 100644 index 0000000..2aa055b --- /dev/null +++ b/standards/src/wiki/06_reference_app/01_overview.md @@ -0,0 +1,70 @@ +# Reference App — Overview + +The standards ship a **runnable sample** of the whole blueprint: the **vet** +veterinary-clinic application. It exists to be copied — it is the reference +for how a real domain sits on the architecture, and the code an agent pulls +through the MCP tools (`get_app_template`, `get_app_component`). + +!!! note "One sample, two templates" + The **server** sample lives at `standards/src/templates/app/` + (FastAPI + openapi-first + mongo-ops + hexa). The **client** sample lives + at `standards/src/templates/client/` (Vite + MUI + `@aetoskia/react-openapi` + + `@aetoskia/react-auth`). Together they are the full-stack reference. + +--- + +## The domain at a glance + +| Aggregate | What it demonstrates | +|---------------|---------------------------------------------------------------| +| **parents** | The simplest CRUD slice (no foreign keys) — copy this first | +| **pets** | Embedded list-of-refs (`parents[]`), file upload, enum fields | +| **treatments**| Plain resource with pricing fields | +| **vets** | Plain resource; target of prefetch FKs | +| **appointments** | Single FKs (`pet`, `vet`, `treatment`) expanded on read, filtered list | +| **jobs** | The worker's queue — durable status machine | + +The pipeline axis ships one worked family, **`appointment_reminder`**, plus the +reusable `pipelines/shared/` scaffold and a `worker.py` that dispatches jobs by +`job.kind`. + +## Run it + +```bash +cp -r /standards/src/templates/app apps/vet-sample +cd apps/vet-sample +pip install -e ".[dev]" + +uvicorn main:app --reload # API on :8020 (memory backend, zero services) +python worker.py # background worker (polls jobs) +python -m scripts.seed # optional: seed demo data (mongo backend) +``` + +The default `REPO_BACKEND=memory` means the API boots with **no external +services** — repositories are process-local dictionaries. Switch to +`REPO_BACKEND=mongo` for persistence (see `.env.example`). + +The client pairs with it: + +```bash +cp -r /standards/src/templates/client apps/vet-sample-client +cd apps/vet-sample-client +echo "VITE_API_BASE_URL=http://localhost:8020" > .env.local +echo "VITE_AUTH_BASE_URL=http://localhost:8020" >> .env.local +npm install +npm run dev # Vite dev server +``` + +!!! warning "Auth" + The server ships with the `bearerAuth` security scheme declared + (`{AUTH_SERVER}` introspection). Without a reachable auth-server the API + answers `401` on resource routes; `/health` stays public. For a local + no-auth demo, remove the `security` block from `openapi.yaml` and the + global security entry — see + [Auth Integration](../lib/22_auth_integration/). + +## Next + +- [Domain & API](02_domain_and_api.md) — walking the resource axis +- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis +- [Client Admin](04_client_admin.md) — the spec-driven frontend diff --git a/standards/src/wiki/06_reference_app/02_domain_and_api.md b/standards/src/wiki/06_reference_app/02_domain_and_api.md new file mode 100644 index 0000000..2720118 --- /dev/null +++ b/standards/src/wiki/06_reference_app/02_domain_and_api.md @@ -0,0 +1,73 @@ +# Domain & API — the resource axis + +Every aggregate follows the same four-file slice +([CRUD Template](../lib/21_crud_template/)); walk `parents` first, then +`appointments` when you need foreign keys. + +## The slice, end to end (parents) + +``` +domain/parents/ + models.py # framework-free Pydantic model (id optional) + ports/ + db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo() + service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service() +interfaces/parents/ + routes.py # list/create/get/update/delete — names == operationIds +``` + +Wiring happens in exactly one place — the composition root +`init.py`: + +```python +parent_repo = create_parent_repo() +_resources["parent_svc"] = get_parent_service(parent_repo) +``` + +Routes resolve the service from app state through the shared helper +(`interfaces/_common.py`): + +```python +async def list_parents(request: Request, limit: int = 20, offset: int = 0) -> dict: + docs = await svc(request, "parent").search({}) + return paginate([dump(d) for d in docs], limit, offset) +``` + +## The spec is the contract + +`openapi.yaml` is OpenAPI 3.1 and the +single source of truth: `main.py` builds an `OpenAPIFirstApp` that binds each +`operationId` to a function of the same name in `interfaces/openapi.py` (a flat +re-export of every routes module). There are no decorator routers. + +The x- extension fields on schemas and properties are what make the admin +client render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and +the worked `Job` schema (enum selects, hidden fields, filterable columns). + +## Foreign keys (appointments) + +`Appointment` declares `pet`, `vet`, `treatment` as `dict | int | None`. The +write path normalizes bare ids through `expand_ref`; the read path returns +fully expanded objects: + +```python +pet = await expand_ref(svc(request, "pet"), body.get("pet"), "pet") +``` + +The list endpoint additionally filters by date prefix and embedded fk ids — +client-side over the repository result, the pattern for small reference data. + +## Ids on the wire + +The domain models declare `id: int | None = None` and documents use the +**integer-id** repository variants (`infra/db/repos/mongo_int.py`, +`create_repo()` in `domain/*/ports/db.py`, `REPO_BACKEND=memory|mongo`). This +keeps the OpenAPI `id: integer` contract, the in-memory demo store, and the +Mongo persistence layer consistent. + +## Tests + +- `tests/integration/test_openapi_app_builds.py` — generic gate: every + `operationId` resolves and the app builds. Copy it verbatim. +- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a + service double (no HTTP client needed). diff --git a/standards/src/wiki/06_reference_app/03_pipelines_and_worker.md b/standards/src/wiki/06_reference_app/03_pipelines_and_worker.md new file mode 100644 index 0000000..2cbaa49 --- /dev/null +++ b/standards/src/wiki/06_reference_app/03_pipelines_and_worker.md @@ -0,0 +1,85 @@ +# Pipelines & Worker — the pipeline axis + +The sample proves the full loop: **API creates a job document → worker polls → +pipeline family runs → job reaches a terminal status.** + +## The jobs slice + +`domain/jobs/` is an ordinary CRUD slice; its model is the durable state +machine the worker drives: + +```python +class Job(BaseModel): + name: str + kind: str # dispatch key + status: Literal["pending", "processing", "done", "failed"] = "pending" + payload: dict | None = None + retry_count: int = 0 + error_message: str | None = None +``` + +`POST /jobs` with `{"name": "...", "kind": "appointment_reminder", +"payload": {"appointment_id": 1}}` enqueues work; `GET /jobs?status=...` +inspects the queue. + +## The appointment_reminder family + +``` +pipelines/appointment_reminder/ + abc.py # LoadPort / ComposePort / DeliverPort + root interface + impl.py # concrete stages, wired root, register("appointment_reminder", ...) + pipeline.yaml # graph spec (nodes, edges, roots) + resources/ + pipeline.yaml # runtime spec consumed by the loader + patterns/reminder.yaml # message template fragments +pipelines/shared/ + ports.py loader.py registry.py # reused by every family +``` + +Each stage is a small, independently testable unit. The loader +(`pipelines/shared/loader.py`) wires the root through `hexa.build` and merges +`resources/patterns/*.yaml` into the stage-injected registry: + +```python +pipeline = get_appointment_reminder_pipeline(appointment_svc) +reminder = await pipeline.run(appointment_id) +``` + +To add a family: copy `appointment_reminder/`, replace the stages, call +`register(kind, variant, root_cls)` — see +[Pipeline Scaffold](../lib/50_pipeline_scaffold/). + +## The worker + +`worker.py` is the process that turns +documents into execution: + +1. poll `job_svc.search({"status": "pending"})` on `WORKER_POLL_SECONDS` +2. claim a job: `status → processing` +3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)` +4. on success `status → done`; on exception `status → failed`, + `retry_count += 1`, `error_message` captured; jobs at + `WORKER_MAX_RETRIES` are skipped + +```python +HANDLERS: dict[str, Callable] = { + "appointment_reminder": run_appointment_reminder, + # add yours here +} +``` + +The worker shares the composition root: `from init import init` gives it the +same wired services the API uses — one place to add a dependency. + +!!! note "Same process model in production" + API and worker are separate processes (see `docker-compose.yaml`: `api`, + `worker`, `mongo`). Any in-memory progress bus is per-process; to surface + live progress through the API, swap the bus adaptor for a broker-backed + one — see [Worker & Progress](../lib/51_worker_and_progress/). + +## Tests + +- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired + root against a fake service. +- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry + exhaustion, using an in-memory job service double and a cancelled poll task. diff --git a/standards/src/wiki/06_reference_app/04_client_admin.md b/standards/src/wiki/06_reference_app/04_client_admin.md new file mode 100644 index 0000000..66b5750 --- /dev/null +++ b/standards/src/wiki/06_reference_app/04_client_admin.md @@ -0,0 +1,60 @@ +# Client Admin — the spec-driven frontend + +The client sample (`standards/src/templates/client/`) renders a full admin — +auth pages, resource lists, forms, detail views — **from the server's +`openapi.yaml`**, driven entirely by the x- extension fields. There are no +hand-written screens per resource. + +## What ships + +A Vite + React + MUI app wiring the two Aetos packages: + +- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`, + builds the router and the resource admin from the spec's schemas and x- + fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…) +- **`@aetoskia/react-auth`** — login/register/logout against the auth-server + paths declared in the spec's `securitySchemes.bearerAuth`, and attaches the + JWT to every API call +- a shared MUI theme (`src/shared-theme/`) and the shell + (`Header` / `Footer` / `Home`) + +`src/openapi-config.ts` is the only configuration: + +```ts +export const specConfiguration: SpecConfiguration = { + specUrl: `${apiBase}/openapi.json`, + baseApiUrl: apiBase, + title: "Vet Admin", + getToken: () => tokenStore.get(), +}; +``` + +## Pair it with the sample server + +```bash +cd apps/vet-sample-client +printf 'VITE_API_BASE_URL=http://localhost:8020\nVITE_AUTH_BASE_URL=http://localhost:8020\n' > .env.local +npm install +npm run dev +``` + +Open the dev-server URL; the admin lists Parents, Pets, Treatments, Vets, +Appointments and Jobs straight from the vet spec. Edit `openapi.yaml` on the +server and the client follows on reload — the spec is the contract on both +sides. + +## Where the x- extensions come from + +The canonical catalog lives in the react-openapi package +(`spec-extensions.ts`, served to agents as the `specExtensions` module of the +react-openapi MCP bundle). The worked examples on the server side are the +`Job` schema (enum selects via `x-edit-as: select` + `x-ui-type: enum`, +`x-hidden` field masks, `x-list-columns`) and the FK fields on `Appointment` +(`x-fk: {resource: pets, prefetch: true}`). + +## CI + +The client ships a `drone-npm` shape (Type B in +[Drone Pipelines](../lib/90_drone_pipelines/)) and follows the +[Versioning](../lib/91_versioning/) standard (`package.json` + +`CHANGELOG.md`). diff --git a/standards/src/wiki/index.md b/standards/src/wiki/index.md index 8d4f736..6ae6b35 100644 --- a/standards/src/wiki/index.md +++ b/standards/src/wiki/index.md @@ -38,6 +38,7 @@ the same shape underneath. | [Overview](01_overview.md) | Philosophy, the one-way dependency rule, architecture | | [App Components](02_components.md) | The component model and how the pieces wire together | | [Start an App](03_start_an_app.md) | The entry point: gather the two axes, then build | +| [Reference App](06_reference_app/01_overview.md) | The runnable vet sample — server, pipelines, client | | [Testing & Quality](04_development/01_testing.md) | Test layout and acceptance gates | **Reference** (separate site, mirrors the MCP bundle): the enforceable @@ -63,7 +64,9 @@ Registry](../lib/93_library_registry/)). `docs://nav`, `docs://modules/{module}`; call `find_library(capability)` - **Library registry** — the catalog of internal libraries behind every app (`libraries.json`, served as `registry://libraries`) -- **Template** — the runnable skeleton at `standards/src/templates/app/` +- **Template** — the runnable sample at `standards/src/templates/app/` + (server) and `standards/src/templates/client/` (React admin); walkthrough in + the [Reference App](06_reference_app/01_overview.md) - **Start a build** — invoke the `start_app` prompt on the `standards` server, or read the [Start an App](03_start_an_app.md) guide diff --git a/standards/wiki/01_overview/index.html b/standards/wiki/01_overview/index.html index afc06d9..7dca34d 100644 --- a/standards/wiki/01_overview/index.html +++ b/standards/wiki/01_overview/index.html @@ -279,6 +279,28 @@ +
  • + + + + + + + Reference App + + +
  • + + + + + + + + + + +
  • @@ -553,6 +575,12 @@ + + + + + + @@ -576,7 +604,7 @@ - MCP + Reference App @@ -586,6 +614,154 @@ + +
  • + + + + + + + + + + + + + + + + + + + + + + + +
  • + + + + + + + + + + +