feat(standards): codify codegen, ship generic Tier-B infra, add get_app_template + prompts; fix docs:// regressions

P0: fix doc:// -> docs:// across standards src/server/registry and regenerate stale mongo-ops bundle. P1: build recipe + spec-first routing now document the openapi-first codegen fast path (models/routes with --inject-request/--package). P4: CRUD template documents the model-vs-document id rule (BaseDocument owns id). P5: authless guidance (omit security keys). P6: template gains generic infra (http_cache, keyed SSE bus, KV port+in-memory+Redis), interfaces/openapi.py + .dockerignore + generic build test registered as components, and a get_app_template tool; genericity matrix marks LLM pool + human-in-loop as reference-app patterns. P7: intake/build/start_app/trial prompts. P8: template ships tests/integration/test_openapi_app_builds.py. Rebuilt standards + openapi-first bundles and re-collected.
This commit is contained in:
2026-10-13 03:26:51 +05:30
parent ef951a1913
commit fced40af59
109 changed files with 4082 additions and 5885 deletions

View File

@@ -330,6 +330,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-codegen-fast-path-in-detail" class="md-nav__link">
<span class="md-ellipsis">
🔧 The codegen fast path, in detail
</span>
</a>
</li>
<li class="md-nav__item">
@@ -638,6 +647,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-codegen-fast-path-in-detail" class="md-nav__link">
<span class="md-ellipsis">
🔧 The codegen fast path, in detail
</span>
</a>
</li>
<li class="md-nav__item">
@@ -677,9 +695,9 @@
<h1 id="build-recipe">Build Recipe</h1>
<h2 id="summary">Summary</h2>
<p>The ordered, deterministic checklist for generating a new application of the
standard shape. Every step is fixed; only the resource model (step 2) and the
pipeline family (step 7) need app-specific authoring.</p>
<p>This page is served by the <code>standards</code> MCP as <code>doc://modules/build_recipe</code>. 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.</p>
<p>This page is served by the <code>standards</code> MCP as <code>docs://modules/build_recipe</code>. The
<code>start_app</code> prompt on that server collects the two axes and hands off here; pull
exact files with <code>get_app_component</code>.</p>
<hr />
@@ -777,18 +795,34 @@ environment-only; never commit them.</p>
<span class="normal"><a href="#__codelineno-2-69">69</a></span>
<span class="normal"><a href="#__codelineno-2-70">70</a></span>
<span class="normal"><a href="#__codelineno-2-71">71</a></span>
<span class="normal"><a href="#__codelineno-2-72">72</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="gh"># Build a new application from the Aetos Standards blueprint</span>
<span class="normal"><a href="#__codelineno-2-72">72</a></span>
<span class="normal"><a href="#__codelineno-2-73">73</a></span>
<span class="normal"><a href="#__codelineno-2-74">74</a></span>
<span class="normal"><a href="#__codelineno-2-75">75</a></span>
<span class="normal"><a href="#__codelineno-2-76">76</a></span>
<span class="normal"><a href="#__codelineno-2-77">77</a></span>
<span class="normal"><a href="#__codelineno-2-78">78</a></span>
<span class="normal"><a href="#__codelineno-2-79">79</a></span>
<span class="normal"><a href="#__codelineno-2-80">80</a></span>
<span class="normal"><a href="#__codelineno-2-81">81</a></span>
<span class="normal"><a href="#__codelineno-2-82">82</a></span>
<span class="normal"><a href="#__codelineno-2-83">83</a></span>
<span class="normal"><a href="#__codelineno-2-84">84</a></span>
<span class="normal"><a href="#__codelineno-2-85">85</a></span>
<span class="normal"><a href="#__codelineno-2-86">86</a></span>
<span class="normal"><a href="#__codelineno-2-87">87</a></span>
<span class="normal"><a href="#__codelineno-2-88">88</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="gh"># Build a new application from the Aetos Standards blueprint</span>
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a>You are coding a complete new backend application conforming to the Aetos
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a>standard app blueprint. Execute every step in order. Only steps 2 and 3 use the
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a>standard app blueprint. Execute every step in order. Only steps 2–4 use the
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a>app-specific details provided by the user; everything else is deterministic.
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a>
</span><span id="__span-2-7"><a id="__codelineno-2-7" name="__codelineno-2-7"></a><span class="gu">## Before writing any code</span>
</span><span id="__span-2-8"><a id="__codelineno-2-8" name="__codelineno-2-8"></a>
</span><span id="__span-2-9"><a id="__codelineno-2-9" name="__codelineno-2-9"></a>Connect to the <span class="sb">`standards`</span> MCP server and read:
</span><span id="__span-2-10"><a id="__codelineno-2-10" name="__codelineno-2-10"></a><span class="k">-</span><span class="w"> </span><span class="sb">`doc://nav`</span> (the module list)
</span><span id="__span-2-11"><a id="__codelineno-2-11" name="__codelineno-2-11"></a><span class="k">-</span><span class="w"> </span><span class="sb">`doc://modules/genericity_matrix`</span> (Tier A library / Tier B template / Tier C authored)
</span><span id="__span-2-12"><a id="__codelineno-2-12" name="__codelineno-2-12"></a><span class="k">-</span><span class="w"> </span><span class="sb">`doc://modules/layering_rules`</span> (the dependency rules)
</span><span id="__span-2-10"><a id="__codelineno-2-10" name="__codelineno-2-10"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://nav`</span> (the module list)
</span><span id="__span-2-11"><a id="__codelineno-2-11" name="__codelineno-2-11"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://modules/genericity_matrix`</span> (Tier A library / Tier B template / Tier C authored)
</span><span id="__span-2-12"><a id="__codelineno-2-12" name="__codelineno-2-12"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://modules/layering_rules`</span> (the dependency rules)
</span><span id="__span-2-13"><a id="__codelineno-2-13" name="__codelineno-2-13"></a>
</span><span id="__span-2-14"><a id="__codelineno-2-14" name="__codelineno-2-14"></a>Then:
</span><span id="__span-2-15"><a id="__codelineno-2-15" name="__codelineno-2-15"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`find_library`</span> with <span class="sb">`db`</span>, <span class="sb">`di`</span>, <span class="sb">`routing`</span>, <span class="sb">`ingestion`</span>, <span class="sb">`pipeline`</span>, <span class="sb">`docs`</span>
@@ -799,56 +833,72 @@ environment-only; never commit them.</p>
</span><span id="__span-2-20"><a id="__codelineno-2-20" name="__codelineno-2-20"></a>
</span><span id="__span-2-21"><a id="__codelineno-2-21" name="__codelineno-2-21"></a><span class="gu">## Steps</span>
</span><span id="__span-2-22"><a id="__codelineno-2-22" name="__codelineno-2-22"></a>
</span><span id="__span-2-23"><a id="__codelineno-2-23" name="__codelineno-2-23"></a><span class="k">1.</span> <span class="gs">**Scaffold**</span> — copy <span class="sb">`standards/src/templates/app/`</span> into <span class="sb">`apps/&lt;your-app&gt;/`</span>.
</span><span id="__span-2-24"><a id="__codelineno-2-24" name="__codelineno-2-24"></a> Keep the tree: <span class="sb">`domain/`</span>, <span class="sb">`infra/`</span>, <span class="sb">`interfaces/`</span>, <span class="sb">`pipelines/`</span>, <span class="sb">`tests/`</span>,
</span><span id="__span-2-25"><a id="__codelineno-2-25" name="__codelineno-2-25"></a> <span class="sb">`scripts/`</span>, plus <span class="sb">`pyproject.toml`</span>, <span class="sb">`openapi.yaml`</span>, <span class="sb">`config.py`</span>, <span class="sb">`init.py`</span>,
</span><span id="__span-2-26"><a id="__codelineno-2-26" name="__codelineno-2-26"></a> <span class="sb">`main.py`</span>, <span class="sb">`worker.py`</span>, <span class="sb">`Dockerfile`</span>, <span class="sb">`docker-compose.yaml`</span>, <span class="sb">`.env.example`</span>.
</span><span id="__span-2-27"><a id="__codelineno-2-27" name="__codelineno-2-27"></a>
</span><span id="__span-2-28"><a id="__codelineno-2-28" name="__codelineno-2-28"></a><span class="k">2.</span> <span class="gs">**Author the spec (app-specific)**</span> — write <span class="sb">`openapi.yaml`</span>: resources,
</span><span id="__span-2-29"><a id="__codelineno-2-29" name="__codelineno-2-29"></a> schemas, <span class="sb">`operationId`</span>s, security, and extension metadata. This is the
</span><span id="__span-2-30"><a id="__codelineno-2-30" name="__codelineno-2-30"></a> contract and drives all routing.
</span><span id="__span-2-31"><a id="__codelineno-2-31" name="__codelineno-2-31"></a>
</span><span id="__span-2-32"><a id="__codelineno-2-32" name="__codelineno-2-32"></a><span class="k">3.</span> <span class="gs">**Write each CRUD slice (app-specific)**</span> — per resource create the four-file
</span><span id="__span-2-33"><a id="__codelineno-2-33" name="__codelineno-2-33"></a> slice: <span class="sb">`models.py`</span>, <span class="sb">`ports/db.py`</span>, <span class="sb">`ports/service.py`</span>, and optional
</span><span id="__span-2-34"><a id="__codelineno-2-34" name="__codelineno-2-34"></a> <span class="sb">`converters.py`</span>. Follow the CRUD template.
</span><span id="__span-2-35"><a id="__codelineno-2-35" name="__codelineno-2-35"></a>
</span><span id="__span-2-36"><a id="__codelineno-2-36" name="__codelineno-2-36"></a><span class="k">4.</span> <span class="gs">**Wire the composition root**</span> — in <span class="sb">`init.py`</span>, build leaf repositories, then
</span><span id="__span-2-37"><a id="__codelineno-2-37" name="__codelineno-2-37"></a> dependent repositories, then services; add infra adapters (db, cache, stores,
</span><span id="__span-2-38"><a id="__codelineno-2-38" name="__codelineno-2-38"></a> progress bus, fetchers). Expose <span class="sb">`get_svc`</span> overloads.
</span><span id="__span-2-39"><a id="__codelineno-2-39" name="__codelineno-2-39"></a>
</span><span id="__span-2-40"><a id="__codelineno-2-40" name="__codelineno-2-40"></a><span class="k">5.</span> <span class="gs">**Add routes**</span> — create <span class="sb">`interfaces/&lt;resource&gt;/routes.py`</span> thin handlers bound
</span><span id="__span-2-41"><a id="__codelineno-2-41" name="__codelineno-2-41"></a> to <span class="sb">`operationId`</span>s and re-export them in <span class="sb">`interfaces/openapi.py`</span>. Add the cache
</span><span id="__span-2-42"><a id="__codelineno-2-42" name="__codelineno-2-42"></a> decorator to read endpoints.
</span><span id="__span-2-43"><a id="__codelineno-2-43" name="__codelineno-2-43"></a>
</span><span id="__span-2-44"><a id="__codelineno-2-44" name="__codelineno-2-44"></a><span class="k">6.</span> <span class="gs">**Bootstrap app and worker**</span> — confirm <span class="sb">`main.py`</span> runs the OpenAPI-first app
</span><span id="__span-2-45"><a id="__codelineno-2-45" name="__codelineno-2-45"></a> with its lifespan. If the app has long-running jobs, adapt <span class="sb">`worker.py`</span> to poll
</span><span id="__span-2-46"><a id="__codelineno-2-46" name="__codelineno-2-46"></a> the job collection, run the pipeline, and publish progress.
</span><span id="__span-2-47"><a id="__codelineno-2-47" name="__codelineno-2-47"></a>
</span><span id="__span-2-48"><a id="__codelineno-2-48" name="__codelineno-2-48"></a><span class="k">7.</span> <span class="gs">**Add pipeline families (only if data-driven)**</span> — copy <span class="sb">`pipelines/example/`</span>
</span><span id="__span-2-49"><a id="__codelineno-2-49" name="__codelineno-2-49"></a> to <span class="sb">`pipelines/&lt;family&gt;/`</span> with <span class="sb">`abc.py`</span>, <span class="sb">`impl.py`</span>, <span class="sb">`pipeline.yaml`</span>,
</span><span id="__span-2-50"><a id="__codelineno-2-50" name="__codelineno-2-50"></a> <span class="sb">`resources/`</span>, and reuse <span class="sb">`pipelines/shared/`</span> (loader + registry). Register the
</span><span id="__span-2-51"><a id="__codelineno-2-51" name="__codelineno-2-51"></a> root. Add fixtures and a parity test.
</span><span id="__span-2-52"><a id="__codelineno-2-52" name="__codelineno-2-52"></a>
</span><span id="__span-2-53"><a id="__codelineno-2-53" name="__codelineno-2-53"></a><span class="k">8.</span> <span class="gs">**Add infrastructure only where needed**</span> — Tier A capability: use the
</span><span id="__span-2-54"><a id="__codelineno-2-54" name="__codelineno-2-54"></a> library. Tier B capability: copy the template. Never hand-write a Tier A
</span><span id="__span-2-55"><a id="__codelineno-2-55" name="__codelineno-2-55"></a> library.
</span><span id="__span-2-56"><a id="__codelineno-2-56" name="__codelineno-2-56"></a>
</span><span id="__span-2-57"><a id="__codelineno-2-57" name="__codelineno-2-57"></a><span class="k">9.</span> <span class="gs">**Tests**</span> — integration tests with fakes at the HTTP edge; unit tests for
</span><span id="__span-2-58"><a id="__codelineno-2-58" name="__codelineno-2-58"></a> domain and pipeline logic; a config-consistency test for rule-driven
</span><span id="__span-2-59"><a id="__codelineno-2-59" name="__codelineno-2-59"></a> pipelines.
</span><span id="__span-2-60"><a id="__codelineno-2-60" name="__codelineno-2-60"></a>
</span><span id="__span-2-61"><a id="__codelineno-2-61" name="__codelineno-2-61"></a><span class="k">10.</span> <span class="gs">**Scripts and seeds**</span> — operator scripts under <span class="sb">`scripts/`</span> using
</span><span id="__span-2-62"><a id="__codelineno-2-62" name="__codelineno-2-62"></a> <span class="sb">`init()`</span> / <span class="sb">`get_svc()`</span> / <span class="sb">`shutdown()`</span>; run as <span class="sb">`python -m scripts.&lt;name&gt;`</span>.
</span><span id="__span-2-63"><a id="__codelineno-2-63" name="__codelineno-2-63"></a>
</span><span id="__span-2-64"><a id="__codelineno-2-64" name="__codelineno-2-64"></a><span class="k">11.</span> <span class="gs">**Docs**</span> — author GSDFC docstrings, generate <span class="sb">`.pyi`</span> stubs, mark the package
</span><span id="__span-2-65"><a id="__codelineno-2-65" name="__codelineno-2-65"></a> <span class="sb">`py.typed`</span>, and build the docs.
</span><span id="__span-2-66"><a id="__codelineno-2-66" name="__codelineno-2-66"></a>
</span><span id="__span-2-67"><a id="__codelineno-2-67" name="__codelineno-2-67"></a><span class="gs">**Definition of done:**</span>
</span><span id="__span-2-68"><a id="__codelineno-2-68" name="__codelineno-2-68"></a><span class="k">-</span><span class="w"> </span><span class="sb">`python -c &quot;import main&quot;`</span> succeeds.
</span><span id="__span-2-69"><a id="__codelineno-2-69" name="__codelineno-2-69"></a><span class="k">-</span><span class="w"> </span><span class="sb">`pytest`</span> passes.
</span><span id="__span-2-70"><a id="__codelineno-2-70" name="__codelineno-2-70"></a><span class="k">-</span><span class="w"> </span>The app serves every operation in the spec; the worker drains its queue.
</span><span id="__span-2-71"><a id="__codelineno-2-71" name="__codelineno-2-71"></a><span class="k">-</span><span class="w"> </span>No domain module imports a web framework.
</span><span id="__span-2-72"><a id="__codelineno-2-72" name="__codelineno-2-72"></a><span class="k">-</span><span class="w"> </span>No Tier A library was reimplemented.
</span><span id="__span-2-23"><a id="__codelineno-2-23" name="__codelineno-2-23"></a><span class="k">1.</span> <span class="gs">**Scaffold**</span> — copy <span class="sb">`standards/src/templates/app/`</span> into <span class="sb">`apps/&lt;your-app&gt;/`</span>
</span><span id="__span-2-24"><a id="__codelineno-2-24" name="__codelineno-2-24"></a> (or pull the same tree via <span class="sb">`get_app_template`</span>). Keep the tree: <span class="sb">`domain/`</span>,
</span><span id="__span-2-25"><a id="__codelineno-2-25" name="__codelineno-2-25"></a> <span class="sb">`infra/`</span>, <span class="sb">`interfaces/`</span>, <span class="sb">`pipelines/`</span>, <span class="sb">`tests/`</span>, <span class="sb">`scripts/`</span>, plus
</span><span id="__span-2-26"><a id="__codelineno-2-26" name="__codelineno-2-26"></a> <span class="sb">`pyproject.toml`</span>, <span class="sb">`openapi.yaml`</span>, <span class="sb">`config.py`</span>, <span class="sb">`init.py`</span>, <span class="sb">`main.py`</span>,
</span><span id="__span-2-27"><a id="__codelineno-2-27" name="__codelineno-2-27"></a> <span class="sb">`worker.py`</span>, <span class="sb">`Dockerfile`</span>, <span class="sb">`docker-compose.yaml`</span>, <span class="sb">`.env.example`</span>.
</span><span id="__span-2-28"><a id="__codelineno-2-28" name="__codelineno-2-28"></a>
</span><span id="__span-2-29"><a id="__codelineno-2-29" name="__codelineno-2-29"></a><span class="k">2.</span> <span class="gs">**Author the spec (app-specific)**</span> — write <span class="sb">`openapi.yaml`</span>: resources,
</span><span id="__span-2-30"><a id="__codelineno-2-30" name="__codelineno-2-30"></a> schemas, <span class="sb">`operationId`</span>s, security, and extension metadata. This is the
</span><span id="__span-2-31"><a id="__codelineno-2-31" name="__codelineno-2-31"></a> contract and drives all routing. Every operation MUST have an <span class="sb">`operationId`</span>.
</span><span id="__span-2-32"><a id="__codelineno-2-32" name="__codelineno-2-32"></a>
</span><span id="__span-2-33"><a id="__codelineno-2-33" name="__codelineno-2-33"></a><span class="k">3.</span> <span class="gs">**Generate the scaffold (codegen)**</span> — from the spec, generate the wire
</span><span id="__span-2-34"><a id="__codelineno-2-34" name="__codelineno-2-34"></a> models and the exact handler signatures:
</span><span id="__span-2-35"><a id="__codelineno-2-35" name="__codelineno-2-35"></a><span class="sb"> ```bash</span>
</span><span id="__span-2-36"><a id="__codelineno-2-36" name="__codelineno-2-36"></a><span class="w"> </span>openapi-first<span class="w"> </span>models<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/models.py
</span><span id="__span-2-37"><a id="__codelineno-2-37" name="__codelineno-2-37"></a><span class="w"> </span>openapi-first<span class="w"> </span>routes<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/routes<span class="w"> </span><span class="se">\</span>
</span><span id="__span-2-38"><a id="__codelineno-2-38" name="__codelineno-2-38"></a><span class="w"> </span>--use-models<span class="w"> </span>--models-module<span class="w"> </span>_gen.models<span class="w"> </span>--inject-request<span class="w"> </span>--package
</span><span id="__span-2-39"><a id="__codelineno-2-39" name="__codelineno-2-39"></a><span class="sb"> ```</span>
</span><span id="__span-2-40"><a id="__codelineno-2-40" name="__codelineno-2-40"></a> <span class="sb">`_gen/models.py`</span> is the <span class="gs">**wire contract**</span> (Pydantic, one class per
</span><span id="__span-2-41"><a id="__codelineno-2-41" name="__codelineno-2-41"></a> <span class="sb">`components.schemas`</span> entry). <span class="sb">`_gen/routes/&lt;resource&gt;/routes.py`</span> lists every
</span><span id="__span-2-42"><a id="__codelineno-2-42" name="__codelineno-2-42"></a> <span class="sb">`operationId`</span> with its exact parameter signature. Treat <span class="sb">`_gen/`</span> as generated
</span><span id="__span-2-43"><a id="__codelineno-2-43" name="__codelineno-2-43"></a> (never hand-edit it) — the canonical handlers live in <span class="sb">`interfaces/`</span>.
</span><span id="__span-2-44"><a id="__codelineno-2-44" name="__codelineno-2-44"></a>
</span><span id="__span-2-45"><a id="__codelineno-2-45" name="__codelineno-2-45"></a><span class="k">4.</span> <span class="gs">**Write each CRUD slice (app-specific)**</span> — per resource create the four-file
</span><span id="__span-2-46"><a id="__codelineno-2-46" name="__codelineno-2-46"></a> slice: <span class="sb">`models.py`</span> (domain model, <span class="gs">**no `id`**</span>), <span class="sb">`ports/db.py`</span>
</span><span id="__span-2-47"><a id="__codelineno-2-47" name="__codelineno-2-47"></a> (<span class="sb">`class XDocument(BaseDocument, X)`</span> — id comes from <span class="sb">`BaseDocument`</span>),
</span><span id="__span-2-48"><a id="__codelineno-2-48" name="__codelineno-2-48"></a> <span class="sb">`ports/service.py`</span>, and optional <span class="sb">`converters.py`</span>. Follow the CRUD template.
</span><span id="__span-2-49"><a id="__codelineno-2-49" name="__codelineno-2-49"></a>
</span><span id="__span-2-50"><a id="__codelineno-2-50" name="__codelineno-2-50"></a><span class="k">5.</span> <span class="gs">**Wire the composition root**</span> — in <span class="sb">`init.py`</span>, build leaf repositories, then
</span><span id="__span-2-51"><a id="__codelineno-2-51" name="__codelineno-2-51"></a> dependent repositories, then services; add infra adapters (db, cache, stores,
</span><span id="__span-2-52"><a id="__codelineno-2-52" name="__codelineno-2-52"></a> progress bus, fetchers). Expose <span class="sb">`get_svc`</span> overloads.
</span><span id="__span-2-53"><a id="__codelineno-2-53" name="__codelineno-2-53"></a>
</span><span id="__span-2-54"><a id="__codelineno-2-54" name="__codelineno-2-54"></a><span class="k">6.</span> <span class="gs">**Add routes**</span> — create <span class="sb">`interfaces/&lt;resource&gt;/routes.py`</span> thin handlers whose
</span><span id="__span-2-55"><a id="__codelineno-2-55" name="__codelineno-2-55"></a> names match the <span class="sb">`operationId`</span>s (use the signatures from step 3 as your
</span><span id="__span-2-56"><a id="__codelineno-2-56" name="__codelineno-2-56"></a> checklist) and re-export them in <span class="sb">`interfaces/openapi.py`</span>. Add the cache
</span><span id="__span-2-57"><a id="__codelineno-2-57" name="__codelineno-2-57"></a> decorator to read endpoints.
</span><span id="__span-2-58"><a id="__codelineno-2-58" name="__codelineno-2-58"></a>
</span><span id="__span-2-59"><a id="__codelineno-2-59" name="__codelineno-2-59"></a><span class="k">7.</span> <span class="gs">**Bootstrap app and worker**</span> — confirm <span class="sb">`main.py`</span> runs the OpenAPI-first app
</span><span id="__span-2-60"><a id="__codelineno-2-60" name="__codelineno-2-60"></a> with its lifespan. If the app has long-running jobs, adapt <span class="sb">`worker.py`</span> to poll
</span><span id="__span-2-61"><a id="__codelineno-2-61" name="__codelineno-2-61"></a> the job collection, run the pipeline, and publish progress.
</span><span id="__span-2-62"><a id="__codelineno-2-62" name="__codelineno-2-62"></a>
</span><span id="__span-2-63"><a id="__codelineno-2-63" name="__codelineno-2-63"></a><span class="k">8.</span> <span class="gs">**Add pipeline families (only if data-driven)**</span> — copy <span class="sb">`pipelines/example/`</span>
</span><span id="__span-2-64"><a id="__codelineno-2-64" name="__codelineno-2-64"></a> to <span class="sb">`pipelines/&lt;family&gt;/`</span> with <span class="sb">`abc.py`</span>, <span class="sb">`impl.py`</span>, <span class="sb">`pipeline.yaml`</span>,
</span><span id="__span-2-65"><a id="__codelineno-2-65" name="__codelineno-2-65"></a> <span class="sb">`resources/`</span>, and reuse <span class="sb">`pipelines/shared/`</span> (loader + registry). Register the
</span><span id="__span-2-66"><a id="__codelineno-2-66" name="__codelineno-2-66"></a> root. Add fixtures and a parity test.
</span><span id="__span-2-67"><a id="__codelineno-2-67" name="__codelineno-2-67"></a>
</span><span id="__span-2-68"><a id="__codelineno-2-68" name="__codelineno-2-68"></a><span class="k">9.</span> <span class="gs">**Add infrastructure only where needed**</span> — Tier A capability: use the
</span><span id="__span-2-69"><a id="__codelineno-2-69" name="__codelineno-2-69"></a> library. Tier B capability: copy the template. Never hand-write a Tier A
</span><span id="__span-2-70"><a id="__codelineno-2-70" name="__codelineno-2-70"></a> library.
</span><span id="__span-2-71"><a id="__codelineno-2-71" name="__codelineno-2-71"></a>
</span><span id="__span-2-72"><a id="__codelineno-2-72" name="__codelineno-2-72"></a><span class="k">10.</span> <span class="gs">**Tests**</span> — integration tests with fakes at the HTTP edge; unit tests for
</span><span id="__span-2-73"><a id="__codelineno-2-73" name="__codelineno-2-73"></a> domain and pipeline logic; a config-consistency test for rule-driven
</span><span id="__span-2-74"><a id="__codelineno-2-74" name="__codelineno-2-74"></a> pipelines. Include <span class="sb">`tests/integration/test_openapi_app_builds.py`</span> (imports
</span><span id="__span-2-75"><a id="__codelineno-2-75" name="__codelineno-2-75"></a> the app, asserts <span class="sb">`import main`</span> binds every operation).
</span><span id="__span-2-76"><a id="__codelineno-2-76" name="__codelineno-2-76"></a>
</span><span id="__span-2-77"><a id="__codelineno-2-77" name="__codelineno-2-77"></a><span class="k">11.</span> <span class="gs">**Scripts and seeds**</span> — operator scripts under <span class="sb">`scripts/`</span> using
</span><span id="__span-2-78"><a id="__codelineno-2-78" name="__codelineno-2-78"></a> <span class="sb">`init()`</span> / <span class="sb">`get_svc()`</span> / <span class="sb">`shutdown()`</span>; run as <span class="sb">`python -m scripts.&lt;name&gt;`</span>.
</span><span id="__span-2-79"><a id="__codelineno-2-79" name="__codelineno-2-79"></a>
</span><span id="__span-2-80"><a id="__codelineno-2-80" name="__codelineno-2-80"></a><span class="k">12.</span> <span class="gs">**Docs**</span> — author GSDFC docstrings, generate <span class="sb">`.pyi`</span> stubs, mark the package
</span><span id="__span-2-81"><a id="__codelineno-2-81" name="__codelineno-2-81"></a> <span class="sb">`py.typed`</span>, and build the docs.
</span><span id="__span-2-82"><a id="__codelineno-2-82" name="__codelineno-2-82"></a>
</span><span id="__span-2-83"><a id="__codelineno-2-83" name="__codelineno-2-83"></a><span class="gs">**Definition of done:**</span>
</span><span id="__span-2-84"><a id="__codelineno-2-84" name="__codelineno-2-84"></a><span class="k">-</span><span class="w"> </span><span class="sb">`python -c &quot;import main&quot;`</span> succeeds.
</span><span id="__span-2-85"><a id="__codelineno-2-85" name="__codelineno-2-85"></a><span class="k">-</span><span class="w"> </span><span class="sb">`pytest`</span> passes.
</span><span id="__span-2-86"><a id="__codelineno-2-86" name="__codelineno-2-86"></a><span class="k">-</span><span class="w"> </span>The app serves every operation in the spec; the worker drains its queue.
</span><span id="__span-2-87"><a id="__codelineno-2-87" name="__codelineno-2-87"></a><span class="k">-</span><span class="w"> </span>No domain module imports a web framework.
</span><span id="__span-2-88"><a id="__codelineno-2-88" name="__codelineno-2-88"></a><span class="k">-</span><span class="w"> </span>No Tier A library was reimplemented.
</span></code></pre></div></td></tr></table></div>
<hr />
<h2 id="where-the-steps-land">🧭 Where the steps land</h2>
@@ -873,53 +923,94 @@ environment-only; never commit them.</p>
<td>—</td>
</tr>
<tr>
<td>3 · CRUD slices</td>
<td>3 · Codegen</td>
<td><code>_gen/</code> (generated)</td>
<td>spec, openapi-first</td>
</tr>
<tr>
<td>4 · CRUD slices</td>
<td><code>domain/&lt;resource&gt;/</code></td>
<td>spec, CRUD template</td>
</tr>
<tr>
<td>4 · Wiring</td>
<td>5 · Wiring</td>
<td><code>init.py</code></td>
<td>every slice</td>
</tr>
<tr>
<td>5 · Routes</td>
<td>6 · Routes</td>
<td><code>interfaces/&lt;resource&gt;/</code></td>
<td>services, openapi route registry</td>
</tr>
<tr>
<td>6 · Bootstrap</td>
<td>7 · Bootstrap</td>
<td><code>main.py</code>, <code>worker.py</code></td>
<td>composition root</td>
</tr>
<tr>
<td>7 · Pipelines</td>
<td>8 · Pipelines</td>
<td><code>pipelines/&lt;family&gt;/</code>, <code>shared/</code></td>
<td>library pipeline engine</td>
</tr>
<tr>
<td>8 · Infra</td>
<td>9 · Infra</td>
<td><code>infra/</code></td>
<td>domain ports</td>
</tr>
<tr>
<td>9 · Tests</td>
<td>10 · Tests</td>
<td><code>tests/</code></td>
<td>app + fixtures</td>
</tr>
<tr>
<td>10 · Scripts</td>
<td>11 · Scripts</td>
<td><code>scripts/</code></td>
<td>composition root</td>
</tr>
<tr>
<td>11 · Docs</td>
<td>12 · Docs</td>
<td>docstrings + docs build</td>
<td>code, Doc Forge</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="the-codegen-fast-path-in-detail">🔧 The codegen fast path, in detail</h2>
<p><code>openapi-first</code> is spec → code only (it never generates a spec). Use it to erase
the two mechanical parts of the build:</p>
<table>
<thead>
<tr>
<th>Generated artifact</th>
<th>Replaces</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>_gen/models.py</code></td>
<td>hand-writing Pydantic wire models</td>
<td>one class per <code>components.schemas</code> entry</td>
</tr>
<tr>
<td><code>_gen/routes/&lt;resource&gt;/routes.py</code></td>
<td>hand-typing handler signatures</td>
<td>names == <code>operationId</code>s; <code>request: Request</code> injected</td>
</tr>
</tbody>
</table>
<p>Rules of the road:</p>
<ul>
<li><strong>Wire models are not domain models.</strong> <code>_gen/models.py</code> is the HTTP contract;
<code>domain/&lt;resource&gt;/models.py</code> is your business shape. If the spec's schema has
a required <code>id: str</code>, keep it off the domain model and let the document get its
id from <code>BaseDocument</code>.</li>
<li><strong>Resource names are sanitized.</strong> <code>/fetch-requests</code> → <code>fetch_requests.py</code>.</li>
<li><strong>Multipart bodies expand</strong> to <code>UploadFile = File(...)</code> / <code>Form(...)</code> params.</li>
<li><strong><code>_gen/</code> is disposable.</strong> Regenerate on every spec change; <code>git diff</code> shows
exactly what moved.</li>
</ul>
<hr />
<h2 id="definition-of-done">⚠️ Definition of done</h2>
<ul>
<li><code>python -c "import main"</code> succeeds.</li>

View File

@@ -414,6 +414,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#authless-apps" class="md-nav__link">
<span class="md-ellipsis">
Authless apps
</span>
</a>
</li>
<li class="md-nav__item">
@@ -665,6 +674,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#authless-apps" class="md-nav__link">
<span class="md-ellipsis">
Authless apps
</span>
</a>
</li>
<li class="md-nav__item">
@@ -763,10 +781,33 @@ can resolve <code>operationId</code> across the whole app:</p>
<li>Security is declared once in the spec and enforced by the framework; handlers
contain no auth code.</li>
</ul>
<h2 id="authless-apps">Authless apps</h2>
<p>Auth is <strong>deferred</strong> in the standard: the same spec-first app runs with or
without it, because security lives entirely in the spec, never in handlers.
For an app that needs no auth yet, do <strong>not</strong> invent per-handler auth checks —
just omit the security keys:</p>
<ul>
<li>Remove the top-level <code>security:</code> block.</li>
<li>Remove <code>components.securitySchemes</code>.</li>
<li>Do <strong>not</strong> add <code>security: []</code> to operations. An explicit empty array is a
per-operation <em>override</em> that means "public", and it only matters when a
global <code>security</code> block exists. Leaving every operation without a <code>security</code>
key is the cleanest authless spec.</li>
</ul>
<p>The handlers, services, and composition root are unchanged. When auth is needed
later, add the global <code>security</code> block and an introspecting dependency; only
handlers that must opt out get <code>security: []</code>.</p>
<h2 id="codegen">Codegen</h2>
<p>The <code>openapi_first</code> library can scaffold models and route stubs from the spec,
and ships app templates (CRUD app, model app, service). Prefer codegen over
hand-writing boilerplate.</p>
<p><code>openapi_first</code> scaffolds models and route stubs from the spec (see the
<a href="../01_build_recipe/">Build Recipe</a>, step 3):</p>
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-4-1">1</a></span>
<span class="normal"><a href="#__codelineno-4-2">2</a></span>
<span class="normal"><a href="#__codelineno-4-3">3</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1"></a>openapi-first<span class="w"> </span>models<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/models.py
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2"></a>openapi-first<span class="w"> </span>routes<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/routes<span class="w"> </span><span class="se">\</span>
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3"></a><span class="w"> </span>--use-models<span class="w"> </span>--models-module<span class="w"> </span>_gen.models<span class="w"> </span>--inject-request<span class="w"> </span>--package
</span></code></pre></div></td></tr></table></div>
<p>Prefer codegen over hand-writing boilerplate. The result is the wire contract
and a set of <code>operationId</code>-named signatures — not the canonical handlers.</p>

View File

@@ -400,6 +400,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-id-rule-model-vs-document" class="md-nav__link">
<span class="md-ellipsis">
The id rule (model vs. document)
</span>
</a>
</li>
<li class="md-nav__item">
@@ -629,6 +638,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-id-rule-model-vs-document" class="md-nav__link">
<span class="md-ellipsis">
The id rule (model vs. document)
</span>
</a>
</li>
<li class="md-nav__item">
@@ -710,8 +728,53 @@ makes the codebase predictable and generatable.</p>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5"></a> service.py # service class + factory
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6"></a> converters.py # (optional) transforms
</span></code></pre></div></td></tr></table></div>
<h2 id="the-id-rule-model-vs-document">The id rule (model vs. document)</h2>
<p>Two different objects, two different jobs:</p>
<table>
<thead>
<tr>
<th>Object</th>
<th>Lives in</th>
<th>Has <code>id</code>?</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Domain model</strong></td>
<td><code>domain/&lt;resource&gt;/models.py</code></td>
<td><strong>No</strong></td>
<td>business shape, framework-free</td>
</tr>
<tr>
<td><strong>Document</strong></td>
<td><code>domain/&lt;resource&gt;/ports/db.py</code></td>
<td><strong>Yes</strong> (from <code>BaseDocument</code>)</td>
<td>Mongo persistence shape</td>
</tr>
</tbody>
</table>
<p><code>BaseDocument</code> (mongo-ops) supplies <code>id: Optional[PyObjectId]</code> (aliased to
<code>_id</code>) plus <code>created_at</code> / <code>updated_at</code>. The domain model must <strong>not</strong> declare
<code>id</code>; Mongo owns it.</p>
<blockquote>
<p><strong>Trap — generated wire models.</strong> <code>openapi-first models</code> produces the HTTP
<em>contract</em> (see the <a href="../01_build_recipe/">Build Recipe</a>, step 3). Response
schemas frequently declare a required <code>id: str</code>, so a generated class is
<strong>not</strong> a domain model. If you write
<code>class ItemDocument(Item, BaseDocument)</code> and <code>Item</code> re-exports a required
<code>id: str</code>, that field shadows <code>BaseDocument.id</code> and no Mongo <code>_id</code> can ever be
set. Fix it one of three ways:</p>
<ol>
<li><strong>Preferred:</strong> keep the domain model id-free (hand-author it from the CRUD
template), and keep the required <code>id</code> only on the wire model.</li>
<li>Order the bases so the document wins:
<code>class ItemDocument(BaseDocument, Item)</code>.</li>
<li>Keep a separate document type and translate with <code>ports/converters.py</code>.</li>
</ol>
</blockquote>
<h2 id="modelspy">models.py</h2>
<p>A framework-free Pydantic model describing the business shape.</p>
<p>A framework-free Pydantic model describing the business shape (no <code>id</code>).</p>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
<span class="normal"><a href="#__codelineno-1-2">2</a></span>
<span class="normal"><a href="#__codelineno-1-3">3</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a><span class="k">class</span><span class="w"> </span><span class="nc">Item</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
@@ -730,16 +793,18 @@ The repository factory selects the right repository flavour.</p>
<span class="normal"><a href="#__codelineno-2-7"> 7</a></span>
<span class="normal"><a href="#__codelineno-2-8"> 8</a></span>
<span class="normal"><a href="#__codelineno-2-9"> 9</a></span>
<span class="normal"><a href="#__codelineno-2-10">10</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="k">class</span><span class="w"> </span><span class="nc">ItemDocument</span><span class="p">(</span><span class="n">Item</span><span class="p">,</span> <span class="n">BaseDocument</span><span class="p">):</span> <span class="c1"># BaseDocument adds id/created_at/...</span>
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a> <span class="k">pass</span>
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a>
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a><span class="k">def</span><span class="w"> </span><span class="nf">create_item_repo</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">owner_repo</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ItemRepository</span><span class="p">:</span>
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a> <span class="k">return</span> <span class="n">repo_factory</span><span class="p">(</span>
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a> <span class="n">collection</span><span class="o">=</span><span class="s2">&quot;items&quot;</span><span class="p">,</span>
</span><span id="__span-2-7"><a id="__codelineno-2-7" name="__codelineno-2-7"></a> <span class="n">document</span><span class="o">=</span><span class="n">ItemDocument</span><span class="p">,</span>
</span><span id="__span-2-8"><a id="__codelineno-2-8" name="__codelineno-2-8"></a> <span class="n">populate_rules</span><span class="o">=</span><span class="p">[</span><span class="n">PopulateRule</span><span class="p">(</span><span class="n">field</span><span class="o">=</span><span class="s2">&quot;owner&quot;</span><span class="p">,</span> <span class="n">collection</span><span class="o">=</span><span class="s2">&quot;users&quot;</span><span class="p">)],</span>
</span><span id="__span-2-9"><a id="__codelineno-2-9" name="__codelineno-2-9"></a> <span class="n">cache</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="c1"># or a CachedBaseRepository config</span>
</span><span id="__span-2-10"><a id="__codelineno-2-10" name="__codelineno-2-10"></a> <span class="p">)</span>
<span class="normal"><a href="#__codelineno-2-10">10</a></span>
<span class="normal"><a href="#__codelineno-2-11">11</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="c1"># Item is id-free; BaseDocument adds id/created_at/updated_at.</span>
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a><span class="k">class</span><span class="w"> </span><span class="nc">ItemDocument</span><span class="p">(</span><span class="n">Item</span><span class="p">,</span> <span class="n">BaseDocument</span><span class="p">):</span>
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a> <span class="k">pass</span>
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a>
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a><span class="k">def</span><span class="w"> </span><span class="nf">create_item_repo</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">owner_repo</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ItemRepository</span><span class="p">:</span>
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a> <span class="k">return</span> <span class="n">repo_factory</span><span class="p">(</span>
</span><span id="__span-2-7"><a id="__codelineno-2-7" name="__codelineno-2-7"></a> <span class="n">collection</span><span class="o">=</span><span class="s2">&quot;items&quot;</span><span class="p">,</span>
</span><span id="__span-2-8"><a id="__codelineno-2-8" name="__codelineno-2-8"></a> <span class="n">document</span><span class="o">=</span><span class="n">ItemDocument</span><span class="p">,</span>
</span><span id="__span-2-9"><a id="__codelineno-2-9" name="__codelineno-2-9"></a> <span class="n">populate_rules</span><span class="o">=</span><span class="p">[</span><span class="n">PopulateRule</span><span class="p">(</span><span class="n">field</span><span class="o">=</span><span class="s2">&quot;owner&quot;</span><span class="p">,</span> <span class="n">collection</span><span class="o">=</span><span class="s2">&quot;users&quot;</span><span class="p">)],</span>
</span><span id="__span-2-10"><a id="__codelineno-2-10" name="__codelineno-2-10"></a> <span class="n">cache</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="c1"># or a CachedBaseRepository config</span>
</span><span id="__span-2-11"><a id="__codelineno-2-11" name="__codelineno-2-11"></a> <span class="p">)</span>
</span></code></pre></div></td></tr></table></div>
<h2 id="portsservicepy">ports/service.py</h2>
<p>The service is built through the DI library, injecting the repository. The

View File

@@ -519,6 +519,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tier-b-patterns-port-from-the-reference-app" class="md-nav__link">
<span class="md-ellipsis">
Tier B (patterns) — port from the reference app
</span>
</a>
</li>
<li class="md-nav__item">
@@ -620,6 +629,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tier-b-patterns-port-from-the-reference-app" class="md-nav__link">
<span class="md-ellipsis">
Tier B (patterns) — port from the reference app
</span>
</a>
</li>
<li class="md-nav__item">
@@ -721,13 +739,16 @@ for its API. Writing a private copy of any of these is a defect.</p>
<h2 id="tier-b-template-provided-copy-from-templatesapp-then-adapt">Tier B — Template-provided (copy from <code>templates/app</code>, then adapt)</h2>
<p>These are reusable internal components shipped <strong>inside</strong> the template — they are
not independent libraries. Copy them as-is, then rename/populate only what is
app-specific. They give every app the same wiring shape as the reference app.</p>
app-specific. They give every app the same wiring shape as the reference app.
Fetch them by kind with <code>get_app_component</code>, or the whole tree with
<code>get_app_template</code>.</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Template location</th>
<th>Action</th>
<th>Shipped</th>
</tr>
</thead>
<tbody>
@@ -735,81 +756,114 @@ app-specific. They give every app the same wiring shape as the reference app.</p
<td>Layering &amp; dependency rule</td>
<td>the package layout + import rules</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Composition root shape</td>
<td><code>init.py</code> (<code>get_svc</code>/<code>get_db</code>, idempotency)</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Spec-first web edge</td>
<td><code>interfaces/openapi.py</code>, thin handlers bound to <code>operationId</code></td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>CRUD slice pattern</td>
<td><code>domain/&lt;resource&gt;/{models.py, ports/db.py, ports/service.py, converters.py}</code></td>
<td>rename + fill</td>
<td>✅</td>
</tr>
<tr>
<td>Generic CRUD service</td>
<td><code>infra/adapters/service.py</code> (<code>CrudsServiceAdaptor</code>)</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Repository factory</td>
<td><code>infra/db/repos/mongo.py</code> (<code>_repo_factory</code>)</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Worker poll + status loop</td>
<td><code>worker.py</code></td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Progress bus + streaming</td>
<td>bus port + in-memory adapter + SSE endpoint</td>
<td>keep</td>
</tr>
<tr>
<td>Human-in-the-loop</td>
<td>durable pause + question document + resolve endpoint + resume map</td>
<td>Progress bus</td>
<td><code>infra/adapters/sse.py</code> (keyed in-memory bus) + an app SSE endpoint</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>HTTP response cache</td>
<td><code>cache_response</code> decorator + generation invalidation</td>
<td><code>infra/http_cache.py</code> (<code>cache_response</code> + generation invalidation)</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Key-value store</td>
<td>KV store port + Redis adapter + in-memory fallback</td>
<td>keep</td>
</tr>
<tr>
<td>LLM client pool</td>
<td>provider-select + retry pool scaffold</td>
<td><code>infra/adapters/kv.py</code> (port + in-memory + Redis adaptor)</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Pipeline scaffold</td>
<td><code>pipelines/shared/{ports,loader,registry}.py</code> + <code>pipelines/&lt;family&gt;/</code></td>
<td>rename + fill</td>
<td>✅</td>
</tr>
<tr>
<td>Operator scripts</td>
<td><code>scripts/</code> (<code>init()</code> / <code>get_svc()</code> / <code>shutdown()</code>)</td>
<td>rename</td>
<td>✅</td>
</tr>
<tr>
<td>Testing harness</td>
<td>integration fakes + fixture-parity pattern</td>
<td>keep</td>
<td>✅</td>
</tr>
<tr>
<td>Project scaffolding</td>
<td><code>pyproject.toml</code>, <code>Dockerfile</code>, <code>docker-compose.yaml</code>, <code>.env.example</code>, <code>openapi.yaml</code></td>
<td>rename + fill</td>
<td>✅</td>
</tr>
</tbody>
</table>
<h2 id="tier-b-patterns-port-from-the-reference-app">Tier B (patterns) — port from the reference app</h2>
<p>A few Tier B capabilities are real and reusable, but are <strong>shaped by the app or
the provider</strong>, so the template does not ship them verbatim — copying them
blindly would carry app-specific names. Treat them as patterns: lift the shape
from the reference app and rename per app.</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Reference location</th>
<th>Action</th>
<th>Shipped</th>
</tr>
</thead>
<tbody>
<tr>
<td>Human-in-the-loop</td>
<td>durable pause + question document + resolve endpoint + resume map</td>
<td>port + reshape</td>
<td>✗</td>
</tr>
<tr>
<td>LLM client pool</td>
<td>provider-select + retry/rotation pool scaffold</td>
<td>port + reshape</td>
<td>✗</td>
</tr>
</tbody>
</table>

View File

@@ -661,7 +661,7 @@ every application. <strong>Use them.</strong> Writing a private copy of any capa
is a defect.</p>
<p>Each library also exposes its own documentation as an MCP server. An AI agent
can read a library's full API by connecting to its MCP and reading
<code>doc://index</code>, <code>doc://nav</code>, and <code>doc://modules/{module}</code>.</p>
<code>docs://index</code>, <code>docs://nav</code>, and <code>docs://modules/{module}</code>.</p>
<hr />
<h2 id="catalog">Catalog</h2>
<table>
@@ -790,8 +790,8 @@ endpoint. Query it before writing any infrastructure.</p>
<h2 id="notes">Notes</h2>
<ul>
<li><strong>Hexa</strong> is a full library with its own MCP server on <code>:8008</code>: read
<code>doc://index</code>, <code>doc://nav</code>, and <code>doc://modules/{module}</code> (e.g.
<code>doc://modules/hexa.container</code>) to discover the DI surface — <code>build(cls, *,
<code>docs://index</code>, <code>docs://nav</code>, and <code>docs://modules/{module}</code> (e.g.
<code>docs://modules/hexa.container</code>) to discover the DI surface — <code>build(cls, *,
instances, config)</code> and the <code>PortNode</code> model.</li>
<li><strong>Auth Server</strong> is deferred: it has no <code>pyproject.toml</code>/importable package and
no MCP bundle. Do not reach for it yet; <code>py-jwt</code> covers token primitives.</li>

File diff suppressed because one or more lines are too long