feat: renumber lib reference into 4-pillar anatomy

Renumber the 15 reference docs into pillar groups (recipe -> anatomy):
- 01 Build Recipe (entry), 02 Composition Root
- domains: 10 Layering Rules
- contract: 20 Spec-First Routing, 21 CRUD Template, 22 Auth Integration
- interfaces/infra: 30 Infra Adapters
- UI: 40 UI: Auto-Rendered Admin & Custom Pages (new; @aetoskia packages,
  client template, x- catalog via react-openapi MCP module)
- pipelines: 50 Pipeline Scaffold, 51 Worker & Progress, 52 Human-in-the-Loop
- delivery/meta: 90 Drone Pipelines, 91 Versioning & Changelog,
  92 Genericity Matrix, 93 Library Registry, 94 Repository Inventory

MCP module names are unchanged (NN_ prefix stripped); all cross-references
(wiki, templates, hooks, registry) updated; lib index rewritten pillar-first;
build recipe bootstraps the client template. Bundle + HTML sites regenerated.
This commit is contained in:
2026-10-19 17:42:55 +05:30
parent e88005d936
commit 406affcfcf
88 changed files with 3017 additions and 2295 deletions

View File

@@ -14,8 +14,6 @@
<link rel="prev" href="..">
<link rel="next" href="../02_layering_rules/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
@@ -374,7 +372,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -396,7 +394,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -418,7 +416,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -440,7 +438,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -462,7 +460,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -484,7 +482,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -506,7 +504,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -528,7 +526,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -550,7 +548,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -572,7 +570,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -594,7 +592,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -616,7 +614,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -638,7 +636,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -660,7 +658,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">
@@ -785,6 +783,10 @@
<p>The ordered, deterministic checklist for generating a new application of the
standard shape. Every step is fixed; only the resource model (step 2), the
codegen pass (step 3), and the pipeline family (step 8) need app-specific work.</p>
<p>The reference pages behind this recipe are organized as an anatomy: the
domains layer, the OpenAPI contract that drives routing <em>and</em> the admin UI,
the interfaces/infra seam, the auto-rendered UI client, and an optional
pipelines layer — see the <a href="../">Standards Reference</a> index.</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>
@@ -795,204 +797,210 @@ exact files with <code>get_app_component</code>.</p>
<span class="normal"><a href="#__codelineno-0-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>cp<span class="w"> </span>-r<span class="w"> </span>&lt;docs&gt;/standards/src/templates/app<span class="w"> </span>apps/&lt;your-app&gt;
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a><span class="nb">cd</span><span class="w"> </span>apps/&lt;your-app&gt;
</span></code></pre></div></td></tr></table></div>
<p>Pair it with the React client template when a UI is needed (auto-rendered
admin from the spec — see <a href="../40_ui_admin/">40 – UI</a>):</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-1-1">1</a></span>
<span class="normal"><a href="#__codelineno-1-2">2</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>cp<span class="w"> </span>-r<span class="w"> </span>&lt;docs&gt;/standards/src/templates/client<span class="w"> </span>apps/&lt;your-app&gt;-client
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a><span class="nb">cd</span><span class="w"> </span>apps/&lt;your-app&gt;-client<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>npm<span class="w"> </span>install
</span></code></pre></div></td></tr></table></div>
<p>Install the internal libraries — editable local checkouts (preferred in
development) or the private index (CI / fresh machines). Credentials are
environment-only; never commit them.</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-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>
<span class="normal"><a href="#__codelineno-1-4">4</a></span>
<span class="normal"><a href="#__codelineno-1-5">5</a></span>
<span class="normal"><a href="#__codelineno-1-6">6</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="c1"># local checkouts</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a>pip<span class="w"> </span>install<span class="w"> </span>-e<span class="w"> </span>../aetos/openapi-first<span class="w"> </span>-e<span class="w"> </span>../aetos/mongo-ops<span class="w"> </span>-e<span class="w"> </span>../aetos/hexa<span class="w"> </span><span class="se">\</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a><span class="w"> </span>-e<span class="w"> </span>../aetos/dagpipe<span class="w"> </span>-e<span class="w"> </span>../aetos/doc-forge
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4"></a><span class="c1"># private index</span>
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5"></a>pip<span class="w"> </span>install<span class="w"> </span>--extra-index-url<span class="w"> </span><span class="se">\</span>
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6"></a><span class="w"> </span><span class="s2">&quot;https://</span><span class="nv">$PIP_REGISTRY_USERNAME</span><span class="s2">:</span><span class="nv">$PIP_REGISTRY_PASSWORD</span><span class="s2">@pip.aetoskia.com/simple&quot;</span><span class="w"> </span>.
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1">1</a></span>
<span class="normal"><a href="#__codelineno-2-2">2</a></span>
<span class="normal"><a href="#__codelineno-2-3">3</a></span>
<span class="normal"><a href="#__codelineno-2-4">4</a></span>
<span class="normal"><a href="#__codelineno-2-5">5</a></span>
<span class="normal"><a href="#__codelineno-2-6">6</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"># local checkouts</span>
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>pip<span class="w"> </span>install<span class="w"> </span>-e<span class="w"> </span>../aetos/openapi-first<span class="w"> </span>-e<span class="w"> </span>../aetos/mongo-ops<span class="w"> </span>-e<span class="w"> </span>../aetos/hexa<span class="w"> </span><span class="se">\</span>
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a><span class="w"> </span>-e<span class="w"> </span>../aetos/dagpipe<span class="w"> </span>-e<span class="w"> </span>../aetos/doc-forge
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a><span class="c1"># private index</span>
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a>pip<span class="w"> </span>install<span class="w"> </span>--extra-index-url<span class="w"> </span><span class="se">\</span>
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a><span class="w"> </span><span class="s2">&quot;https://</span><span class="nv">$PIP_REGISTRY_USERNAME</span><span class="s2">:</span><span class="nv">$PIP_REGISTRY_PASSWORD</span><span class="s2">@pip.aetoskia.com/simple&quot;</span><span class="w"> </span>.
</span></code></pre></div></td></tr></table></div>
<hr />
<h2 id="agent-prompt-copyable">📋 Agent prompt (copyable)</h2>
<div class="language-markdown highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1"> 1</a></span>
<span class="normal"><a href="#__codelineno-2-2"> 2</a></span>
<span class="normal"><a href="#__codelineno-2-3"> 3</a></span>
<span class="normal"><a href="#__codelineno-2-4"> 4</a></span>
<span class="normal"><a href="#__codelineno-2-5"> 5</a></span>
<span class="normal"><a href="#__codelineno-2-6"> 6</a></span>
<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>
<span class="normal"><a href="#__codelineno-2-11">11</a></span>
<span class="normal"><a href="#__codelineno-2-12">12</a></span>
<span class="normal"><a href="#__codelineno-2-13">13</a></span>
<span class="normal"><a href="#__codelineno-2-14">14</a></span>
<span class="normal"><a href="#__codelineno-2-15">15</a></span>
<span class="normal"><a href="#__codelineno-2-16">16</a></span>
<span class="normal"><a href="#__codelineno-2-17">17</a></span>
<span class="normal"><a href="#__codelineno-2-18">18</a></span>
<span class="normal"><a href="#__codelineno-2-19">19</a></span>
<span class="normal"><a href="#__codelineno-2-20">20</a></span>
<span class="normal"><a href="#__codelineno-2-21">21</a></span>
<span class="normal"><a href="#__codelineno-2-22">22</a></span>
<span class="normal"><a href="#__codelineno-2-23">23</a></span>
<span class="normal"><a href="#__codelineno-2-24">24</a></span>
<span class="normal"><a href="#__codelineno-2-25">25</a></span>
<span class="normal"><a href="#__codelineno-2-26">26</a></span>
<span class="normal"><a href="#__codelineno-2-27">27</a></span>
<span class="normal"><a href="#__codelineno-2-28">28</a></span>
<span class="normal"><a href="#__codelineno-2-29">29</a></span>
<span class="normal"><a href="#__codelineno-2-30">30</a></span>
<span class="normal"><a href="#__codelineno-2-31">31</a></span>
<span class="normal"><a href="#__codelineno-2-32">32</a></span>
<span class="normal"><a href="#__codelineno-2-33">33</a></span>
<span class="normal"><a href="#__codelineno-2-34">34</a></span>
<span class="normal"><a href="#__codelineno-2-35">35</a></span>
<span class="normal"><a href="#__codelineno-2-36">36</a></span>
<span class="normal"><a href="#__codelineno-2-37">37</a></span>
<span class="normal"><a href="#__codelineno-2-38">38</a></span>
<span class="normal"><a href="#__codelineno-2-39">39</a></span>
<span class="normal"><a href="#__codelineno-2-40">40</a></span>
<span class="normal"><a href="#__codelineno-2-41">41</a></span>
<span class="normal"><a href="#__codelineno-2-42">42</a></span>
<span class="normal"><a href="#__codelineno-2-43">43</a></span>
<span class="normal"><a href="#__codelineno-2-44">44</a></span>
<span class="normal"><a href="#__codelineno-2-45">45</a></span>
<span class="normal"><a href="#__codelineno-2-46">46</a></span>
<span class="normal"><a href="#__codelineno-2-47">47</a></span>
<span class="normal"><a href="#__codelineno-2-48">48</a></span>
<span class="normal"><a href="#__codelineno-2-49">49</a></span>
<span class="normal"><a href="#__codelineno-2-50">50</a></span>
<span class="normal"><a href="#__codelineno-2-51">51</a></span>
<span class="normal"><a href="#__codelineno-2-52">52</a></span>
<span class="normal"><a href="#__codelineno-2-53">53</a></span>
<span class="normal"><a href="#__codelineno-2-54">54</a></span>
<span class="normal"><a href="#__codelineno-2-55">55</a></span>
<span class="normal"><a href="#__codelineno-2-56">56</a></span>
<span class="normal"><a href="#__codelineno-2-57">57</a></span>
<span class="normal"><a href="#__codelineno-2-58">58</a></span>
<span class="normal"><a href="#__codelineno-2-59">59</a></span>
<span class="normal"><a href="#__codelineno-2-60">60</a></span>
<span class="normal"><a href="#__codelineno-2-61">61</a></span>
<span class="normal"><a href="#__codelineno-2-62">62</a></span>
<span class="normal"><a href="#__codelineno-2-63">63</a></span>
<span class="normal"><a href="#__codelineno-2-64">64</a></span>
<span class="normal"><a href="#__codelineno-2-65">65</a></span>
<span class="normal"><a href="#__codelineno-2-66">66</a></span>
<span class="normal"><a href="#__codelineno-2-67">67</a></span>
<span class="normal"><a href="#__codelineno-2-68">68</a></span>
<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>
<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>
<span class="normal"><a href="#__codelineno-2-89">89</a></span>
<span class="normal"><a href="#__codelineno-2-90">90</a></span>
<span class="normal"><a href="#__codelineno-2-91">91</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–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">`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>
</span><span id="__span-2-16"><a id="__codelineno-2-16" name="__codelineno-2-16"></a> and note the Tier A libraries that already exist — never reimplement one;
</span><span id="__span-2-17"><a id="__codelineno-2-17" name="__codelineno-2-17"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`list_app_components()`</span> and pull each file you need with
</span><span id="__span-2-18"><a id="__codelineno-2-18" name="__codelineno-2-18"></a> <span class="sb">`get_app_component(kind, resource=..., family=...)`</span> instead of writing it from
</span><span id="__span-2-19"><a id="__codelineno-2-19" name="__codelineno-2-19"></a> scratch.
</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> (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 class="w"> </span>--template-style
</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. <span class="sb">`--template-style`</span> makes
</span><span id="__span-2-43"><a id="__codelineno-2-43" name="__codelineno-2-43"></a> those stubs <span class="sb">`async def`</span> with a <span class="sb">`body`</span> parameter, matching the handlers you
</span><span id="__span-2-44"><a id="__codelineno-2-44" name="__codelineno-2-44"></a> keep in <span class="sb">`interfaces/`</span> (without it they are sync <span class="sb">`def`</span> stubs taking
</span><span id="__span-2-45"><a id="__codelineno-2-45" name="__codelineno-2-45"></a> <span class="sb">`payload`</span>; both bind the same way). Treat <span class="sb">`_gen/`</span> as generated
</span><span id="__span-2-46"><a id="__codelineno-2-46" name="__codelineno-2-46"></a> (never hand-edit it) — the canonical handlers live in <span class="sb">`interfaces/`</span>.
</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">4.</span> <span class="gs">**Write each CRUD slice (app-specific)**</span> — per resource create the four-file
</span><span id="__span-2-49"><a id="__codelineno-2-49" name="__codelineno-2-49"></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-50"><a id="__codelineno-2-50" name="__codelineno-2-50"></a> (<span class="sb">`class XDocument(BaseDocument, X)`</span> — id comes from <span class="sb">`BaseDocument`</span>),
</span><span id="__span-2-51"><a id="__codelineno-2-51" name="__codelineno-2-51"></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-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">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-54"><a id="__codelineno-2-54" name="__codelineno-2-54"></a> dependent repositories, then services; add infra adapters (db, cache, stores,
</span><span id="__span-2-55"><a id="__codelineno-2-55" name="__codelineno-2-55"></a> progress bus, fetchers). Expose <span class="sb">`get_svc`</span> overloads.
</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">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-58"><a id="__codelineno-2-58" name="__codelineno-2-58"></a> names match the <span class="sb">`operationId`</span>s (use the signatures from step 3 as your
</span><span id="__span-2-59"><a id="__codelineno-2-59" name="__codelineno-2-59"></a> checklist) and re-export them in <span class="sb">`interfaces/openapi.py`</span>. Add the cache
</span><span id="__span-2-60"><a id="__codelineno-2-60" name="__codelineno-2-60"></a> decorator to read endpoints.
</span><span id="__span-2-61"><a id="__codelineno-2-61" name="__codelineno-2-61"></a>
</span><span id="__span-2-62"><a id="__codelineno-2-62" name="__codelineno-2-62"></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-63"><a id="__codelineno-2-63" name="__codelineno-2-63"></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-64"><a id="__codelineno-2-64" name="__codelineno-2-64"></a> the job collection, run the pipeline, and publish progress.
</span><span id="__span-2-65"><a id="__codelineno-2-65" name="__codelineno-2-65"></a>
</span><span id="__span-2-66"><a id="__codelineno-2-66" name="__codelineno-2-66"></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-67"><a id="__codelineno-2-67" name="__codelineno-2-67"></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-68"><a id="__codelineno-2-68" name="__codelineno-2-68"></a> <span class="sb">`resources/`</span>, and reuse <span class="sb">`pipelines/shared/`</span> (loader + registry). Register the
</span><span id="__span-2-69"><a id="__codelineno-2-69" name="__codelineno-2-69"></a> root. Add fixtures and a parity test.
</span><span id="__span-2-70"><a id="__codelineno-2-70" name="__codelineno-2-70"></a>
</span><span id="__span-2-71"><a id="__codelineno-2-71" name="__codelineno-2-71"></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-72"><a id="__codelineno-2-72" name="__codelineno-2-72"></a> library. Tier B capability: copy the template. Never hand-write a Tier A
</span><span id="__span-2-73"><a id="__codelineno-2-73" name="__codelineno-2-73"></a> library.
</span><span id="__span-2-74"><a id="__codelineno-2-74" name="__codelineno-2-74"></a>
</span><span id="__span-2-75"><a id="__codelineno-2-75" name="__codelineno-2-75"></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-76"><a id="__codelineno-2-76" name="__codelineno-2-76"></a> domain and pipeline logic; a config-consistency test for rule-driven
</span><span id="__span-2-77"><a id="__codelineno-2-77" name="__codelineno-2-77"></a> pipelines. Include <span class="sb">`tests/integration/test_openapi_app_builds.py`</span> (imports
</span><span id="__span-2-78"><a id="__codelineno-2-78" name="__codelineno-2-78"></a> the app, asserts <span class="sb">`import main`</span> binds every operation).
</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">11.</span> <span class="gs">**Scripts and seeds**</span> — operator scripts under <span class="sb">`scripts/`</span> using
</span><span id="__span-2-81"><a id="__codelineno-2-81" name="__codelineno-2-81"></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-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="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-84"><a id="__codelineno-2-84" name="__codelineno-2-84"></a> <span class="sb">`py.typed`</span>, and build the docs.
</span><span id="__span-2-85"><a id="__codelineno-2-85" name="__codelineno-2-85"></a>
</span><span id="__span-2-86"><a id="__codelineno-2-86" name="__codelineno-2-86"></a><span class="gs">**Definition of done:**</span>
</span><span id="__span-2-87"><a id="__codelineno-2-87" name="__codelineno-2-87"></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-88"><a id="__codelineno-2-88" name="__codelineno-2-88"></a><span class="k">-</span><span class="w"> </span><span class="sb">`pytest`</span> passes.
</span><span id="__span-2-89"><a id="__codelineno-2-89" name="__codelineno-2-89"></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-90"><a id="__codelineno-2-90" name="__codelineno-2-90"></a><span class="k">-</span><span class="w"> </span>No domain module imports a web framework.
</span><span id="__span-2-91"><a id="__codelineno-2-91" name="__codelineno-2-91"></a><span class="k">-</span><span class="w"> </span>No Tier A library was reimplemented.
<div class="language-markdown highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1"> 1</a></span>
<span class="normal"><a href="#__codelineno-3-2"> 2</a></span>
<span class="normal"><a href="#__codelineno-3-3"> 3</a></span>
<span class="normal"><a href="#__codelineno-3-4"> 4</a></span>
<span class="normal"><a href="#__codelineno-3-5"> 5</a></span>
<span class="normal"><a href="#__codelineno-3-6"> 6</a></span>
<span class="normal"><a href="#__codelineno-3-7"> 7</a></span>
<span class="normal"><a href="#__codelineno-3-8"> 8</a></span>
<span class="normal"><a href="#__codelineno-3-9"> 9</a></span>
<span class="normal"><a href="#__codelineno-3-10">10</a></span>
<span class="normal"><a href="#__codelineno-3-11">11</a></span>
<span class="normal"><a href="#__codelineno-3-12">12</a></span>
<span class="normal"><a href="#__codelineno-3-13">13</a></span>
<span class="normal"><a href="#__codelineno-3-14">14</a></span>
<span class="normal"><a href="#__codelineno-3-15">15</a></span>
<span class="normal"><a href="#__codelineno-3-16">16</a></span>
<span class="normal"><a href="#__codelineno-3-17">17</a></span>
<span class="normal"><a href="#__codelineno-3-18">18</a></span>
<span class="normal"><a href="#__codelineno-3-19">19</a></span>
<span class="normal"><a href="#__codelineno-3-20">20</a></span>
<span class="normal"><a href="#__codelineno-3-21">21</a></span>
<span class="normal"><a href="#__codelineno-3-22">22</a></span>
<span class="normal"><a href="#__codelineno-3-23">23</a></span>
<span class="normal"><a href="#__codelineno-3-24">24</a></span>
<span class="normal"><a href="#__codelineno-3-25">25</a></span>
<span class="normal"><a href="#__codelineno-3-26">26</a></span>
<span class="normal"><a href="#__codelineno-3-27">27</a></span>
<span class="normal"><a href="#__codelineno-3-28">28</a></span>
<span class="normal"><a href="#__codelineno-3-29">29</a></span>
<span class="normal"><a href="#__codelineno-3-30">30</a></span>
<span class="normal"><a href="#__codelineno-3-31">31</a></span>
<span class="normal"><a href="#__codelineno-3-32">32</a></span>
<span class="normal"><a href="#__codelineno-3-33">33</a></span>
<span class="normal"><a href="#__codelineno-3-34">34</a></span>
<span class="normal"><a href="#__codelineno-3-35">35</a></span>
<span class="normal"><a href="#__codelineno-3-36">36</a></span>
<span class="normal"><a href="#__codelineno-3-37">37</a></span>
<span class="normal"><a href="#__codelineno-3-38">38</a></span>
<span class="normal"><a href="#__codelineno-3-39">39</a></span>
<span class="normal"><a href="#__codelineno-3-40">40</a></span>
<span class="normal"><a href="#__codelineno-3-41">41</a></span>
<span class="normal"><a href="#__codelineno-3-42">42</a></span>
<span class="normal"><a href="#__codelineno-3-43">43</a></span>
<span class="normal"><a href="#__codelineno-3-44">44</a></span>
<span class="normal"><a href="#__codelineno-3-45">45</a></span>
<span class="normal"><a href="#__codelineno-3-46">46</a></span>
<span class="normal"><a href="#__codelineno-3-47">47</a></span>
<span class="normal"><a href="#__codelineno-3-48">48</a></span>
<span class="normal"><a href="#__codelineno-3-49">49</a></span>
<span class="normal"><a href="#__codelineno-3-50">50</a></span>
<span class="normal"><a href="#__codelineno-3-51">51</a></span>
<span class="normal"><a href="#__codelineno-3-52">52</a></span>
<span class="normal"><a href="#__codelineno-3-53">53</a></span>
<span class="normal"><a href="#__codelineno-3-54">54</a></span>
<span class="normal"><a href="#__codelineno-3-55">55</a></span>
<span class="normal"><a href="#__codelineno-3-56">56</a></span>
<span class="normal"><a href="#__codelineno-3-57">57</a></span>
<span class="normal"><a href="#__codelineno-3-58">58</a></span>
<span class="normal"><a href="#__codelineno-3-59">59</a></span>
<span class="normal"><a href="#__codelineno-3-60">60</a></span>
<span class="normal"><a href="#__codelineno-3-61">61</a></span>
<span class="normal"><a href="#__codelineno-3-62">62</a></span>
<span class="normal"><a href="#__codelineno-3-63">63</a></span>
<span class="normal"><a href="#__codelineno-3-64">64</a></span>
<span class="normal"><a href="#__codelineno-3-65">65</a></span>
<span class="normal"><a href="#__codelineno-3-66">66</a></span>
<span class="normal"><a href="#__codelineno-3-67">67</a></span>
<span class="normal"><a href="#__codelineno-3-68">68</a></span>
<span class="normal"><a href="#__codelineno-3-69">69</a></span>
<span class="normal"><a href="#__codelineno-3-70">70</a></span>
<span class="normal"><a href="#__codelineno-3-71">71</a></span>
<span class="normal"><a href="#__codelineno-3-72">72</a></span>
<span class="normal"><a href="#__codelineno-3-73">73</a></span>
<span class="normal"><a href="#__codelineno-3-74">74</a></span>
<span class="normal"><a href="#__codelineno-3-75">75</a></span>
<span class="normal"><a href="#__codelineno-3-76">76</a></span>
<span class="normal"><a href="#__codelineno-3-77">77</a></span>
<span class="normal"><a href="#__codelineno-3-78">78</a></span>
<span class="normal"><a href="#__codelineno-3-79">79</a></span>
<span class="normal"><a href="#__codelineno-3-80">80</a></span>
<span class="normal"><a href="#__codelineno-3-81">81</a></span>
<span class="normal"><a href="#__codelineno-3-82">82</a></span>
<span class="normal"><a href="#__codelineno-3-83">83</a></span>
<span class="normal"><a href="#__codelineno-3-84">84</a></span>
<span class="normal"><a href="#__codelineno-3-85">85</a></span>
<span class="normal"><a href="#__codelineno-3-86">86</a></span>
<span class="normal"><a href="#__codelineno-3-87">87</a></span>
<span class="normal"><a href="#__codelineno-3-88">88</a></span>
<span class="normal"><a href="#__codelineno-3-89">89</a></span>
<span class="normal"><a href="#__codelineno-3-90">90</a></span>
<span class="normal"><a href="#__codelineno-3-91">91</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="gh"># Build a new application from the Aetos Standards blueprint</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a>
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3"></a>You are coding a complete new backend application conforming to the Aetos
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4"></a>standard app blueprint. Execute every step in order. Only steps 2–4 use the
</span><span id="__span-3-5"><a id="__codelineno-3-5" name="__codelineno-3-5"></a>app-specific details provided by the user; everything else is deterministic.
</span><span id="__span-3-6"><a id="__codelineno-3-6" name="__codelineno-3-6"></a>
</span><span id="__span-3-7"><a id="__codelineno-3-7" name="__codelineno-3-7"></a><span class="gu">## Before writing any code</span>
</span><span id="__span-3-8"><a id="__codelineno-3-8" name="__codelineno-3-8"></a>
</span><span id="__span-3-9"><a id="__codelineno-3-9" name="__codelineno-3-9"></a>Connect to the <span class="sb">`standards`</span> MCP server and read:
</span><span id="__span-3-10"><a id="__codelineno-3-10" name="__codelineno-3-10"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://nav`</span> (the module list)
</span><span id="__span-3-11"><a id="__codelineno-3-11" name="__codelineno-3-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-3-12"><a id="__codelineno-3-12" name="__codelineno-3-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-3-13"><a id="__codelineno-3-13" name="__codelineno-3-13"></a>
</span><span id="__span-3-14"><a id="__codelineno-3-14" name="__codelineno-3-14"></a>Then:
</span><span id="__span-3-15"><a id="__codelineno-3-15" name="__codelineno-3-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>
</span><span id="__span-3-16"><a id="__codelineno-3-16" name="__codelineno-3-16"></a> and note the Tier A libraries that already exist — never reimplement one;
</span><span id="__span-3-17"><a id="__codelineno-3-17" name="__codelineno-3-17"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`list_app_components()`</span> and pull each file you need with
</span><span id="__span-3-18"><a id="__codelineno-3-18" name="__codelineno-3-18"></a> <span class="sb">`get_app_component(kind, resource=..., family=...)`</span> instead of writing it from
</span><span id="__span-3-19"><a id="__codelineno-3-19" name="__codelineno-3-19"></a> scratch.
</span><span id="__span-3-20"><a id="__codelineno-3-20" name="__codelineno-3-20"></a>
</span><span id="__span-3-21"><a id="__codelineno-3-21" name="__codelineno-3-21"></a><span class="gu">## Steps</span>
</span><span id="__span-3-22"><a id="__codelineno-3-22" name="__codelineno-3-22"></a>
</span><span id="__span-3-23"><a id="__codelineno-3-23" name="__codelineno-3-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-3-24"><a id="__codelineno-3-24" name="__codelineno-3-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-3-25"><a id="__codelineno-3-25" name="__codelineno-3-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-3-26"><a id="__codelineno-3-26" name="__codelineno-3-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-3-27"><a id="__codelineno-3-27" name="__codelineno-3-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-3-28"><a id="__codelineno-3-28" name="__codelineno-3-28"></a>
</span><span id="__span-3-29"><a id="__codelineno-3-29" name="__codelineno-3-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-3-30"><a id="__codelineno-3-30" name="__codelineno-3-30"></a> schemas, <span class="sb">`operationId`</span>s, security, and extension metadata. This is the
</span><span id="__span-3-31"><a id="__codelineno-3-31" name="__codelineno-3-31"></a> contract and drives all routing. Every operation MUST have an <span class="sb">`operationId`</span>.
</span><span id="__span-3-32"><a id="__codelineno-3-32" name="__codelineno-3-32"></a>
</span><span id="__span-3-33"><a id="__codelineno-3-33" name="__codelineno-3-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-3-34"><a id="__codelineno-3-34" name="__codelineno-3-34"></a> models and the exact handler signatures:
</span><span id="__span-3-35"><a id="__codelineno-3-35" name="__codelineno-3-35"></a><span class="sb"> ```bash</span>
</span><span id="__span-3-36"><a id="__codelineno-3-36" name="__codelineno-3-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-3-37"><a id="__codelineno-3-37" name="__codelineno-3-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-3-38"><a id="__codelineno-3-38" name="__codelineno-3-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 class="w"> </span>--template-style
</span><span id="__span-3-39"><a id="__codelineno-3-39" name="__codelineno-3-39"></a><span class="sb"> ```</span>
</span><span id="__span-3-40"><a id="__codelineno-3-40" name="__codelineno-3-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-3-41"><a id="__codelineno-3-41" name="__codelineno-3-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-3-42"><a id="__codelineno-3-42" name="__codelineno-3-42"></a> <span class="sb">`operationId`</span> with its exact parameter signature. <span class="sb">`--template-style`</span> makes
</span><span id="__span-3-43"><a id="__codelineno-3-43" name="__codelineno-3-43"></a> those stubs <span class="sb">`async def`</span> with a <span class="sb">`body`</span> parameter, matching the handlers you
</span><span id="__span-3-44"><a id="__codelineno-3-44" name="__codelineno-3-44"></a> keep in <span class="sb">`interfaces/`</span> (without it they are sync <span class="sb">`def`</span> stubs taking
</span><span id="__span-3-45"><a id="__codelineno-3-45" name="__codelineno-3-45"></a> <span class="sb">`payload`</span>; both bind the same way). Treat <span class="sb">`_gen/`</span> as generated
</span><span id="__span-3-46"><a id="__codelineno-3-46" name="__codelineno-3-46"></a> (never hand-edit it) — the canonical handlers live in <span class="sb">`interfaces/`</span>.
</span><span id="__span-3-47"><a id="__codelineno-3-47" name="__codelineno-3-47"></a>
</span><span id="__span-3-48"><a id="__codelineno-3-48" name="__codelineno-3-48"></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-3-49"><a id="__codelineno-3-49" name="__codelineno-3-49"></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-3-50"><a id="__codelineno-3-50" name="__codelineno-3-50"></a> (<span class="sb">`class XDocument(BaseDocument, X)`</span> — id comes from <span class="sb">`BaseDocument`</span>),
</span><span id="__span-3-51"><a id="__codelineno-3-51" name="__codelineno-3-51"></a> <span class="sb">`ports/service.py`</span>, and optional <span class="sb">`converters.py`</span>. Follow the CRUD template.
</span><span id="__span-3-52"><a id="__codelineno-3-52" name="__codelineno-3-52"></a>
</span><span id="__span-3-53"><a id="__codelineno-3-53" name="__codelineno-3-53"></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-3-54"><a id="__codelineno-3-54" name="__codelineno-3-54"></a> dependent repositories, then services; add infra adapters (db, cache, stores,
</span><span id="__span-3-55"><a id="__codelineno-3-55" name="__codelineno-3-55"></a> progress bus, fetchers). Expose <span class="sb">`get_svc`</span> overloads.
</span><span id="__span-3-56"><a id="__codelineno-3-56" name="__codelineno-3-56"></a>
</span><span id="__span-3-57"><a id="__codelineno-3-57" name="__codelineno-3-57"></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-3-58"><a id="__codelineno-3-58" name="__codelineno-3-58"></a> names match the <span class="sb">`operationId`</span>s (use the signatures from step 3 as your
</span><span id="__span-3-59"><a id="__codelineno-3-59" name="__codelineno-3-59"></a> checklist) and re-export them in <span class="sb">`interfaces/openapi.py`</span>. Add the cache
</span><span id="__span-3-60"><a id="__codelineno-3-60" name="__codelineno-3-60"></a> decorator to read endpoints.
</span><span id="__span-3-61"><a id="__codelineno-3-61" name="__codelineno-3-61"></a>
</span><span id="__span-3-62"><a id="__codelineno-3-62" name="__codelineno-3-62"></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-3-63"><a id="__codelineno-3-63" name="__codelineno-3-63"></a> with its lifespan. If the app has long-running jobs, adapt <span class="sb">`worker.py`</span> to poll
</span><span id="__span-3-64"><a id="__codelineno-3-64" name="__codelineno-3-64"></a> the job collection, run the pipeline, and publish progress.
</span><span id="__span-3-65"><a id="__codelineno-3-65" name="__codelineno-3-65"></a>
</span><span id="__span-3-66"><a id="__codelineno-3-66" name="__codelineno-3-66"></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-3-67"><a id="__codelineno-3-67" name="__codelineno-3-67"></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-3-68"><a id="__codelineno-3-68" name="__codelineno-3-68"></a> <span class="sb">`resources/`</span>, and reuse <span class="sb">`pipelines/shared/`</span> (loader + registry). Register the
</span><span id="__span-3-69"><a id="__codelineno-3-69" name="__codelineno-3-69"></a> root. Add fixtures and a parity test.
</span><span id="__span-3-70"><a id="__codelineno-3-70" name="__codelineno-3-70"></a>
</span><span id="__span-3-71"><a id="__codelineno-3-71" name="__codelineno-3-71"></a><span class="k">9.</span> <span class="gs">**Add infrastructure only where needed**</span> — Tier A capability: use the
</span><span id="__span-3-72"><a id="__codelineno-3-72" name="__codelineno-3-72"></a> library. Tier B capability: copy the template. Never hand-write a Tier A
</span><span id="__span-3-73"><a id="__codelineno-3-73" name="__codelineno-3-73"></a> library.
</span><span id="__span-3-74"><a id="__codelineno-3-74" name="__codelineno-3-74"></a>
</span><span id="__span-3-75"><a id="__codelineno-3-75" name="__codelineno-3-75"></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-3-76"><a id="__codelineno-3-76" name="__codelineno-3-76"></a> domain and pipeline logic; a config-consistency test for rule-driven
</span><span id="__span-3-77"><a id="__codelineno-3-77" name="__codelineno-3-77"></a> pipelines. Include <span class="sb">`tests/integration/test_openapi_app_builds.py`</span> (imports
</span><span id="__span-3-78"><a id="__codelineno-3-78" name="__codelineno-3-78"></a> the app, asserts <span class="sb">`import main`</span> binds every operation).
</span><span id="__span-3-79"><a id="__codelineno-3-79" name="__codelineno-3-79"></a>
</span><span id="__span-3-80"><a id="__codelineno-3-80" name="__codelineno-3-80"></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-3-81"><a id="__codelineno-3-81" name="__codelineno-3-81"></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-3-82"><a id="__codelineno-3-82" name="__codelineno-3-82"></a>
</span><span id="__span-3-83"><a id="__codelineno-3-83" name="__codelineno-3-83"></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-3-84"><a id="__codelineno-3-84" name="__codelineno-3-84"></a> <span class="sb">`py.typed`</span>, and build the docs.
</span><span id="__span-3-85"><a id="__codelineno-3-85" name="__codelineno-3-85"></a>
</span><span id="__span-3-86"><a id="__codelineno-3-86" name="__codelineno-3-86"></a><span class="gs">**Definition of done:**</span>
</span><span id="__span-3-87"><a id="__codelineno-3-87" name="__codelineno-3-87"></a><span class="k">-</span><span class="w"> </span><span class="sb">`python -c &quot;import main&quot;`</span> succeeds.
</span><span id="__span-3-88"><a id="__codelineno-3-88" name="__codelineno-3-88"></a><span class="k">-</span><span class="w"> </span><span class="sb">`pytest`</span> passes.
</span><span id="__span-3-89"><a id="__codelineno-3-89" name="__codelineno-3-89"></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-3-90"><a id="__codelineno-3-90" name="__codelineno-3-90"></a><span class="k">-</span><span class="w"> </span>No domain module imports a web framework.
</span><span id="__span-3-91"><a id="__codelineno-3-91" name="__codelineno-3-91"></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>
@@ -1120,7 +1128,7 @@ the two mechanical parts of the build:</p>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../02_layering_rules/">02 – Layering Rules</a> · <a href="../05_crud_template/">05 – CRUD Template</a> · <a href="../06_pipeline_scaffold/">06 – Pipeline Scaffold</a> · <a href="../12_genericity_matrix/">12 – Genericity Matrix</a></li>
<li><a href="../10_layering_rules/">10 – Layering Rules</a> · <a href="../21_crud_template/">21 – CRUD Template</a> · <a href="../22_auth_integration/">22 – Auth Integration</a> · <a href="../40_ui_admin/">40 – UI</a> · <a href="../50_pipeline_scaffold/">50 – Pipeline Scaffold</a> · <a href="../92_genericity_matrix/">92 – Genericity Matrix</a></li>
</ul>

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../02_layering_rules/">
<link rel="next" href="../04_spec_first_routing/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -290,30 +286,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Composition Root
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -324,71 +298,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-resource-registry" class="md-nav__link">
<span class="md-ellipsis">
The resource registry
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#shape" class="md-nav__link">
<span class="md-ellipsis">
Shape
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#typed-accessors" class="md-nav__link">
<span class="md-ellipsis">
Typed accessors
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#rules" class="md-nav__link">
<span class="md-ellipsis">
Rules
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -400,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -422,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -444,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -466,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -488,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -510,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -532,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -554,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -576,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -598,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -620,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -642,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../01_build_recipe/">
<link rel="next" href="../03_composition_root/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -268,30 +264,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Layering Rules
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -302,80 +276,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#allowed-imports" class="md-nav__link">
<span class="md-ellipsis">
Allowed imports
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#forbidden-imports" class="md-nav__link">
<span class="md-ellipsis">
Forbidden imports
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#why" class="md-nav__link">
<span class="md-ellipsis">
Why
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-portadapter-contract" class="md-nav__link">
<span class="md-ellipsis">
The port/adapter contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#enforcement" class="md-nav__link">
<span class="md-ellipsis">
Enforcement
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -387,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -409,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -431,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -453,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -475,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -497,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -519,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -541,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -563,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -585,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -607,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -629,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -651,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../03_composition_root/">
<link rel="next" href="../05_crud_template/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -312,30 +308,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Spec-First Routing
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -346,98 +320,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-contract" class="md-nav__link">
<span class="md-ellipsis">
The contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#app-bootstrap" class="md-nav__link">
<span class="md-ellipsis">
App bootstrap
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#handler-registry" class="md-nav__link">
<span class="md-ellipsis">
Handler registry
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#handler-shape" class="md-nav__link">
<span class="md-ellipsis">
Handler shape
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#conventions" class="md-nav__link">
<span class="md-ellipsis">
Conventions
</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">
<a href="#codegen" class="md-nav__link">
<span class="md-ellipsis">
Codegen
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -449,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -471,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -493,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -515,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -537,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -559,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -581,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -603,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -625,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -647,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -669,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../04_spec_first_routing/">
<link rel="next" href="../06_pipeline_scaffold/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -334,30 +330,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
CRUD Template
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -368,107 +342,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-slice" class="md-nav__link">
<span class="md-ellipsis">
The slice
</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">
<a href="#modelspy" class="md-nav__link">
<span class="md-ellipsis">
models.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#portsdbpy" class="md-nav__link">
<span class="md-ellipsis">
ports/db.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nested-references" class="md-nav__link">
<span class="md-ellipsis">
Nested references
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#portsservicepy" class="md-nav__link">
<span class="md-ellipsis">
ports/service.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#naming-conventions" class="md-nav__link">
<span class="md-ellipsis">
Naming conventions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optional-files" class="md-nav__link">
<span class="md-ellipsis">
Optional files
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -480,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -502,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -524,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -546,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -568,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -590,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -612,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -634,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -656,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -678,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../12_genericity_matrix/">
<link rel="next" href="../13_library_registry/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -401,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -423,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -445,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -467,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -489,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -511,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -532,30 +528,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Auth Integration
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -566,104 +540,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#read-the-contract" class="md-nav__link">
<span class="md-ellipsis">
Read the contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wire-it-up-declare-dont-code" class="md-nav__link">
<span class="md-ellipsis">
Wire it up: declare, don't code
</span>
</a>
<nav class="md-nav" aria-label="Wire it up: declare, don&#39;t code">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#public-operations" class="md-nav__link">
<span class="md-ellipsis">
Public operations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#environment" class="md-nav__link">
<span class="md-ellipsis">
Environment
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#path-1-jwtlib-client-no-generator" class="md-nav__link">
<span class="md-ellipsis">
Path 1: jwtlib client (no generator)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#golden-rules" class="md-nav__link">
<span class="md-ellipsis">
Golden rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -675,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../08_human_in_loop/">
<link rel="next" href="../10_drone_pipelines/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -401,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -422,30 +418,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Infra Adapters
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -456,98 +430,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-mongodb" class="md-nav__link">
<span class="md-ellipsis">
Database (MongoDB)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#http-response-cache" class="md-nav__link">
<span class="md-ellipsis">
HTTP response cache
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#event-progress-bus" class="md-nav__link">
<span class="md-ellipsis">
Event / progress bus
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#external-clients-ingestion" class="md-nav__link">
<span class="md-ellipsis">
External clients (ingestion)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#llm-clients" class="md-nav__link">
<span class="md-ellipsis">
LLM clients
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#key-value-store" class="md-nav__link">
<span class="md-ellipsis">
Key-value store
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#rule" class="md-nav__link">
<span class="md-ellipsis">
Rule
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -559,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -581,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -603,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -625,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -647,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -669,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -260,7 +260,7 @@
<li class="md-nav__item">
<a href="/02_layering_rules/" class="md-nav__link">
<a href="/02_layering_rules.md" class="md-nav__link">
@@ -282,7 +282,7 @@
<li class="md-nav__item">
<a href="/03_composition_root/" class="md-nav__link">
<a href="/03_composition_root.md" class="md-nav__link">
@@ -304,7 +304,7 @@
<li class="md-nav__item">
<a href="/04_spec_first_routing/" class="md-nav__link">
<a href="/04_spec_first_routing.md" class="md-nav__link">
@@ -326,7 +326,7 @@
<li class="md-nav__item">
<a href="/05_crud_template/" class="md-nav__link">
<a href="/05_crud_template.md" class="md-nav__link">
@@ -348,7 +348,7 @@
<li class="md-nav__item">
<a href="/06_pipeline_scaffold/" class="md-nav__link">
<a href="/06_pipeline_scaffold.md" class="md-nav__link">
@@ -370,7 +370,7 @@
<li class="md-nav__item">
<a href="/07_worker_and_progress/" class="md-nav__link">
<a href="/07_worker_and_progress.md" class="md-nav__link">
@@ -392,7 +392,7 @@
<li class="md-nav__item">
<a href="/08_human_in_loop/" class="md-nav__link">
<a href="/08_human_in_loop.md" class="md-nav__link">
@@ -414,7 +414,7 @@
<li class="md-nav__item">
<a href="/09_infra_adaptors/" class="md-nav__link">
<a href="/09_infra_adaptors.md" class="md-nav__link">
@@ -436,7 +436,7 @@
<li class="md-nav__item">
<a href="/10_drone_pipelines/" class="md-nav__link">
<a href="/10_drone_pipelines.md" class="md-nav__link">
@@ -458,7 +458,7 @@
<li class="md-nav__item">
<a href="/11_versioning_and_changelog/" class="md-nav__link">
<a href="/11_versioning_and_changelog.md" class="md-nav__link">
@@ -480,7 +480,7 @@
<li class="md-nav__item">
<a href="/15_repository_inventory/" class="md-nav__link">
<a href="/15_repository_inventory.md" class="md-nav__link">
@@ -502,7 +502,7 @@
<li class="md-nav__item">
<a href="/12_genericity_matrix/" class="md-nav__link">
<a href="/12_genericity_matrix.md" class="md-nav__link">
@@ -524,7 +524,7 @@
<li class="md-nav__item">
<a href="/14_auth_integration/" class="md-nav__link">
<a href="/14_auth_integration.md" class="md-nav__link">
@@ -546,7 +546,7 @@
<li class="md-nav__item">
<a href="/13_library_registry/" class="md-nav__link">
<a href="/13_library_registry.md" class="md-nav__link">

View File

@@ -0,0 +1,804 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="The rules and library registry of the standardized application blueprint.">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>UI: Auto-Rendered Admin & Custom Pages - Aetos Standards — Reference</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#ui-auto-rendered-admin-custom-pages" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Aetos Standards — Reference" class="md-header__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Aetos Standards — Reference
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
UI: Auto-Rendered Admin &amp; Custom Pages
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan" aria-hidden="true" type="radio" name="__palette" id="__palette_0">
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Aetos Standards — Reference" class="md-nav__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Aetos Standards — Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../01_build_recipe/" class="md-nav__link">
<span class="md-ellipsis">
Build Recipe
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../02_layering_rules.md" class="md-nav__link">
<span class="md-ellipsis">
Layering Rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_composition_root.md" class="md-nav__link">
<span class="md-ellipsis">
Composition Root
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../04_spec_first_routing.md" class="md-nav__link">
<span class="md-ellipsis">
Spec-First Routing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../05_crud_template.md" class="md-nav__link">
<span class="md-ellipsis">
CRUD Template
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
<span class="md-ellipsis">
Pipeline Scaffold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../07_worker_and_progress.md" class="md-nav__link">
<span class="md-ellipsis">
Worker & Progress
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../08_human_in_loop.md" class="md-nav__link">
<span class="md-ellipsis">
Human-in-the-Loop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../09_infra_adaptors.md" class="md-nav__link">
<span class="md-ellipsis">
Infra Adapters
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../10_drone_pipelines.md" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory.md" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix.md" class="md-nav__link">
<span class="md-ellipsis">
Genericity Matrix
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../14_auth_integration.md" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../13_library_registry.md" class="md-nav__link">
<span class="md-ellipsis">
Library Registry
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scaffold" class="md-nav__link">
<span class="md-ellipsis">
Scaffold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#x-extensions-how-the-spec-steers-the-ui" class="md-nav__link">
<span class="md-ellipsis">
x- extensions: how the spec steers the UI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#golden-rules" class="md-nav__link">
<span class="md-ellipsis">
Golden rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="ui-auto-rendered-admin-custom-pages">UI: Auto-Rendered Admin &amp; Custom Pages</h1>
<h2 id="summary">Summary</h2>
<p>The UI axis of an Aetos app is a <strong>published React client</strong>, not code written
per application. Two npm packages carry the whole surface:</p>
<table>
<thead>
<tr>
<th>Package</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>@aetoskia/react-openapi</code></td>
<td>Renders a complete admin panel (CRUD tables, forms, filters, sorting, pagination, uploads) <strong>from the app's <code>openapi.yaml</code></strong> — zero UI code.</td>
</tr>
<tr>
<td><code>@aetoskia/react-auth</code></td>
<td>Auth contexts and pages (login/register/me) wired to the auth-server, plus the axios client with token handling.</td>
</tr>
</tbody>
</table>
<p>Both publish to the private registry <code>https://npm.aetoskia.com</code>. The server
spec is the single source of truth: the same <code>openapi.yaml</code> that drives
<code>openapi-first</code> routing drives the admin UI, through <strong>x- extension fields</strong>
(see below).</p>
<hr />
<h2 id="scaffold">Scaffold</h2>
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1">1</a></span>
<span class="normal"><a href="#__codelineno-0-2">2</a></span>
<span class="normal"><a href="#__codelineno-0-3">3</a></span>
<span class="normal"><a href="#__codelineno-0-4">4</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>cp<span class="w"> </span>-r<span class="w"> </span>&lt;docs&gt;/standards/src/templates/client<span class="w"> </span>apps/&lt;your-app&gt;-client
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a><span class="nb">cd</span><span class="w"> </span>apps/&lt;your-app&gt;-client
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3"></a>npm<span class="w"> </span>install<span class="w"> </span><span class="c1"># deps come from npm.aetoskia.com via .npmrc</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4"></a>npm<span class="w"> </span>run<span class="w"> </span>dev
</span></code></pre></div></td></tr></table></div>
<p>The client template is a Vite + MUI app with:</p>
<ul>
<li><code>src/openapi-config.ts</code> — points the renderer at the app's spec and the
auth-server's spec;</li>
<li><code>.env.example</code> — <code>VITE_API_BASE_URL</code> (app API) and <code>VITE_AUTH_BASE_URL</code>
(auth-server);</li>
<li>a <code>Home</code> page that auto-lists every resource the spec declares, and a
<code>Header</code>/<code>Footer</code>/theme scaffold.</li>
</ul>
<p>Custom pages are ordinary React routes layered on top; the auto-rendered admin
is available per resource at <code>#/resource/&lt;name&gt;</code>.</p>
<hr />
<h2 id="x-extensions-how-the-spec-steers-the-ui">x- extensions: how the spec steers the UI</h2>
<p>The admin renderer reads a catalog of OpenAPI extension fields (<code>x-resource</code>,
<code>x-list-columns</code>, <code>x-display-format</code>, <code>x-label</code>, <code>x-order</code>, <code>x-hidden</code>,
<code>x-sortable</code>, <code>x-filterable</code>, <code>x-autocomplete</code>, <code>x-fk</code>, <code>x-ui-type</code>,
<code>x-upload</code>, <code>x-sse</code>, <code>x-profile</code>, and the auth wiring <code>x-server-url</code> /
<code>x-login-path</code> / <code>x-me-path</code> / …) declared on schemas and properties.</p>
<p><strong>The catalog is canonical in the react-openapi source, not in this doc.</strong>
Read it from the MCP bundle:</p>
<div class="language-text 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></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>docs://modules/react-openapi # module: specExtensions — the full catalog
</span></code></pre></div></td></tr></table></div>
<p>The app's own spec carries the per-resource values; the catalog explains what
each field does. The template's <code>openapi.yaml</code> shows the convention on the
example <code>Item</code> and <code>Job</code> schemas.</p>
<hr />
<h2 id="golden-rules">Golden rules</h2>
<ol>
<li><strong>The spec owns the UI.</strong> Add a resource or rename a column in
<code>openapi.yaml</code>; the admin follows. Never fork the renderer per app.</li>
<li><strong>Custom pages for workflows, auto-render for CRUD.</strong> Anything that is
"list / create / edit / delete an entity" is a spec concern; anything that
is a bespoke workflow is a React page.</li>
<li><strong>Auth is consumed, never re-implemented.</strong> <code>@aetoskia/react-auth</code> holds
tokens and calls the auth-server endpoints declared by the x- wiring; pages
read the context.</li>
<li><strong>Publish, don't vendor.</strong> UI shared across apps belongs in
<code>@aetoskia/react-openapi</code> or <code>@aetoskia/react-auth</code> (SemVer, tag-triggered
npm release), not copy-pasted into app trees.</li>
</ol>
<hr />
<h2 id="notes">Notes</h2>
<ul>
<li>The server template ships authless by default (see
<a href="../22_auth_integration/">22 – Auth Integration</a>); the client template pairs
with it and degrades to "no login" until a security scheme is declared.</li>
<li><code>react/vet</code> is the full-stack reference implementation: spec with x-
extensions, server, and the consuming client in one tree.</li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.sections", "navigation.expand", "navigation.top", "navigation.instant", "navigation.tracking", "navigation.indexes", "content.code.copy", "content.code.annotate", "content.tabs.link", "content.action.edit", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../05_crud_template/">
<link rel="next" href="../07_worker_and_progress/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -356,30 +352,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Pipeline Scaffold
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -390,98 +364,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#when-to-use-a-pipeline" class="md-nav__link">
<span class="md-ellipsis">
When to use a pipeline
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#structure-of-a-family" class="md-nav__link">
<span class="md-ellipsis">
Structure of a family
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#shared-scaffolding" class="md-nav__link">
<span class="md-ellipsis">
Shared scaffolding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-loader" class="md-nav__link">
<span class="md-ellipsis">
The loader
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-registry" class="md-nav__link">
<span class="md-ellipsis">
The registry
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#driving-a-pipeline" class="md-nav__link">
<span class="md-ellipsis">
Driving a pipeline
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#testing" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -493,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -515,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -537,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -559,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -581,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -603,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -625,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -647,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -669,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../06_pipeline_scaffold/">
<link rel="next" href="../08_human_in_loop/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -110,7 +106,7 @@
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Worker & Progress
Worker &amp; Progress
</span>
</div>
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -378,30 +374,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Worker & Progress
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -412,71 +386,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-status-machine" class="md-nav__link">
<span class="md-ellipsis">
The status machine
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-poll-loop" class="md-nav__link">
<span class="md-ellipsis">
The poll loop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#progress-events" class="md-nav__link">
<span class="md-ellipsis">
Progress events
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#streaming-endpoint" class="md-nav__link">
<span class="md-ellipsis">
Streaming endpoint
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -488,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -510,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -532,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -554,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -576,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -598,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -620,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -642,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../07_worker_and_progress/">
<link rel="next" href="../09_infra_adaptors/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -400,30 +396,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Human-in-the-Loop
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -434,80 +408,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-mechanism" class="md-nav__link">
<span class="md-ellipsis">
The mechanism
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-question-document" class="md-nav__link">
<span class="md-ellipsis">
The question document
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#resume-map" class="md-nav__link">
<span class="md-ellipsis">
Resume map
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#resolve-endpoint" class="md-nav__link">
<span class="md-ellipsis">
Resolve endpoint
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#cli-parity" class="md-nav__link">
<span class="md-ellipsis">
CLI parity
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -519,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -541,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -563,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -585,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -607,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -629,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -651,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../09_infra_adaptors/">
<link rel="next" href="../11_versioning_and_changelog/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -401,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -423,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -444,30 +440,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Drone Pipelines
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -478,71 +452,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#type-a-pypi-package-python-libyaml" class="md-nav__link">
<span class="md-ellipsis">
Type A — PyPI package (python-lib.yaml)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#type-b-docker-deployment-service-dockeryaml" class="md-nav__link">
<span class="md-ellipsis">
Type B — Docker deployment (service-docker.yaml)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-not-to-do" class="md-nav__link">
<span class="md-ellipsis">
What not to do
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -554,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -576,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -598,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -620,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -642,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">
@@ -788,7 +697,7 @@ with <code>from_secret</code> — never commit credentials.</p>
<li><code>check-version</code> — <strong>guard</strong>: fail unless <code>$DRONE_TAG</code> equals the version in
<code>pyproject.toml</code>; then read <code>name</code> / <code>version</code> and query
<code>$PIP_REPO_URL/simple/&lt;package&gt;/</code>; if that version already exists, exit
<code>78</code> and skip the whole build (see <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a>).</li>
<code>78</code> and skip the whole build (see <a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a>).</li>
<li><code>quality-gate</code> — install the package with its <code>[dev]</code> extras from the
private index, then <code>black --check</code>, <code>ruff check</code>, <code>mypy</code>, <code>pydoclint</code>,
<code>pytest</code>.</li>
@@ -815,7 +724,7 @@ triggered on <code>tag</code>:</p>
<li><code>fetch-tags</code> — resolve the latest tag into <code>LATEST_TAG.txt</code>; fail if there
are no tags.</li>
<li><code>check-version</code> — <strong>guard</strong>: fail unless <code>$DRONE_TAG</code> equals the repo-root
<code>VERSION</code> file (see <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a>).</li>
<code>VERSION</code> file (see <a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a>).</li>
<li><code>check-remote-image</code> — if <code>aetos/&lt;app&gt;:&lt;tag&gt;</code> already exists locally, exit
<code>78</code> and skip the build.</li>
<li><code>quality-gate</code> — install from <code>requirements.txt</code> and run
@@ -871,14 +780,14 @@ triggered on <code>tag</code>:</p>
index and the running container against duplicate publishes.</li>
<li>Do not drop the tag↔version guard. A package needs its <code>pyproject.toml</code>
version to equal the tag; a service needs a root <code>VERSION</code> file that equals
the tag. See <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a>.</li>
the tag. See <a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a>.</li>
<li>Do not rename the pipelines; tooling and runbooks key off
<code>build-and-publish-pypi</code> and <code>build-and-deploy-service</code>.</li>
</ul>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../09_infra_adaptors/">09 – Infra Adapters</a> · <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
<li><a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../30_infra_adaptors/">30 – Infra Adapters</a> · <a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a> · <a href="../93_library_registry/">93 – Library Registry</a></li>
</ul>

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../10_drone_pipelines/">
<link rel="next" href="../15_repository_inventory/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -110,7 +106,7 @@
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Versioning & Changelog
Versioning &amp; Changelog
</span>
</div>
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -401,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -423,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -445,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -466,30 +462,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Versioning & Changelog
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -500,134 +474,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-version-contract" class="md-nav__link">
<span class="md-ellipsis">
The version contract
</span>
</a>
<nav class="md-nav" aria-label="The version contract">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#pre-10-0yz" class="md-nav__link">
<span class="md-ellipsis">
Pre-1.0 (0.y.z)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#the-changelog" class="md-nav__link">
<span class="md-ellipsis">
The changelog
</span>
</a>
<nav class="md-nav" aria-label="The changelog">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#reconcile-before-releasing" class="md-nav__link">
<span class="md-ellipsis">
Reconcile before releasing
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#tagging-strategy-mr-based" class="md-nav__link">
<span class="md-ellipsis">
Tagging strategy (MR-based)
</span>
</a>
<nav class="md-nav" aria-label="Tagging strategy (MR-based)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#agent-assisted-tagging" class="md-nav__link">
<span class="md-ellipsis">
Agent-assisted tagging
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#release-checklist" class="md-nav__link">
<span class="md-ellipsis">
Release checklist
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#why-the-guard-matters" class="md-nav__link">
<span class="md-ellipsis">
Why the guard matters
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -639,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -661,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -683,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -705,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">
@@ -878,7 +724,7 @@ releases in a <a href="https://keepachangelog.com/en/1.1.0/">Keep a Changelog</a
The <strong>version file is the source of truth</strong>, the <strong>git tag must equal it</strong>, and
a Drone guard enforces that equality before anything is published. Releases are
cut manually; a tag is what triggers the pipeline.</p>
<p>This page pairs with <a href="../10_drone_pipelines/">10 – Drone Pipelines</a>: the version
<p>This page pairs with <a href="../90_drone_pipelines/">90 – Drone Pipelines</a>: the version
and tag contract is exactly what those two CI shapes consume.</p>
<hr />
<h2 id="the-version-contract">The version contract</h2>
@@ -1047,7 +893,7 @@ Drone guard turns that silent no-op into a hard failure.</p>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../10_drone_pipelines/">10 – Drone Pipelines</a> · <a href="../15_repository_inventory/">15 – Repository Inventory</a> · <a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
<li><a href="../90_drone_pipelines/">90 – Drone Pipelines</a> · <a href="../94_repository_inventory/">94 – Repository Inventory</a> · <a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../93_library_registry/">93 – Library Registry</a></li>
</ul>

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../15_repository_inventory/">
<link rel="next" href="../14_auth_integration/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -401,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -423,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -445,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -467,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -489,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -510,30 +506,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Genericity Matrix
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -544,80 +518,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tier-a-library-backed-use-never-reimplement" class="md-nav__link">
<span class="md-ellipsis">
Tier A — Library-backed (use; never reimplement)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tier-b-template-provided-copy-from-templatesapp-then-adapt" class="md-nav__link">
<span class="md-ellipsis">
Tier B — Template-provided (copy from templates/app, then adapt)
</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">
<a href="#tier-c-app-specific-must-be-authored" class="md-nav__link">
<span class="md-ellipsis">
Tier C — App-specific (must be authored)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-two-axes" class="md-nav__link">
<span class="md-ellipsis">
The two axes
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -629,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -651,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">

View File

@@ -11,8 +11,6 @@
<link rel="prev" href="../14_auth_integration/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -267,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -289,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -311,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -333,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -355,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -377,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -399,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -421,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -443,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -465,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -487,7 +485,7 @@
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -509,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -531,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -552,30 +550,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Library Registry
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../13_library_registry.md" class="md-nav__link">
@@ -586,71 +562,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#catalog" class="md-nav__link">
<span class="md-ellipsis">
Catalog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#how-to-choose" class="md-nav__link">
<span class="md-ellipsis">
How to choose
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#discovery" class="md-nav__link">
<span class="md-ellipsis">
Discovery
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</li>

View File

@@ -11,10 +11,6 @@
<link rel="prev" href="../11_versioning_and_changelog/">
<link rel="next" href="../12_genericity_matrix/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -269,7 +265,7 @@
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<a href="../02_layering_rules.md" class="md-nav__link">
@@ -291,7 +287,7 @@
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<a href="../03_composition_root.md" class="md-nav__link">
@@ -313,7 +309,7 @@
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<a href="../04_spec_first_routing.md" class="md-nav__link">
@@ -335,7 +331,7 @@
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<a href="../05_crud_template.md" class="md-nav__link">
@@ -357,7 +353,7 @@
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
@@ -379,7 +375,7 @@
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<a href="../07_worker_and_progress.md" class="md-nav__link">
@@ -401,7 +397,7 @@
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<a href="../08_human_in_loop.md" class="md-nav__link">
@@ -423,7 +419,7 @@
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<a href="../09_infra_adaptors.md" class="md-nav__link">
@@ -445,7 +441,7 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<a href="../10_drone_pipelines.md" class="md-nav__link">
@@ -467,7 +463,7 @@
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
@@ -488,30 +484,8 @@
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Repository Inventory
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<li class="md-nav__item">
<a href="../15_repository_inventory.md" class="md-nav__link">
@@ -522,80 +496,6 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#packages-type-a" class="md-nav__link">
<span class="md-ellipsis">
Packages (Type A)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#services-type-b" class="md-nav__link">
<span class="md-ellipsis">
Services (Type B)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#required-files-per-repo" class="md-nav__link">
<span class="md-ellipsis">
Required files per repo
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#reading-this-at-runtime" class="md-nav__link">
<span class="md-ellipsis">
Reading this at runtime
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -607,7 +507,7 @@
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<a href="../12_genericity_matrix.md" class="md-nav__link">
@@ -629,7 +529,7 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<a href="../14_auth_integration.md" class="md-nav__link">
@@ -651,7 +551,7 @@
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<a href="../13_library_registry.md" class="md-nav__link">
@@ -767,8 +667,8 @@
<p>The canonical list of Aetos repositories covered by the CI and versioning
standards, with the artifact each one produces, its version source, and its
Drone shape. Every entry maps to the contract in
<a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a> and the two CI
files in <a href="../10_drone_pipelines/">10 – Drone Pipelines</a>.</p>
<a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a> and the two CI
files in <a href="../90_drone_pipelines/">90 – Drone Pipelines</a>.</p>
<p>The same data is published machine-readably as the <code>registry://repos</code> MCP
resource (and the <code>list_repos</code> tool), sourced from <code>standards/src/repos.json</code>.
Paths are relative to the <code>aetos/</code> workspace root.</p>
@@ -898,7 +798,7 @@ merged, and the tag is created on the merged commit.</p>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../10_drone_pipelines/">10 – Drone Pipelines</a> · <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
<li><a href="../90_drone_pipelines/">90 – Drone Pipelines</a> · <a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a> · <a href="../93_library_registry/">93 – Library Registry</a></li>
</ul>

View File

@@ -232,6 +232,19 @@
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Home
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="." class="md-nav__link md-nav__link--active">
@@ -244,6 +257,88 @@
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#anatomy" class="md-nav__link">
<span class="md-ellipsis">
Anatomy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-1-domains" class="md-nav__link">
<span class="md-ellipsis">
Pillar 1 — Domains
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-2-openapi-spec-the-contract" class="md-nav__link">
<span class="md-ellipsis">
Pillar 2 — OpenAPI Spec (the contract)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-3-interfaces-infra" class="md-nav__link">
<span class="md-ellipsis">
Pillar 3 — Interfaces &amp; Infra
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-4-ui" class="md-nav__link">
<span class="md-ellipsis">
Pillar 4 — UI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pipelines-optional-layer" class="md-nav__link">
<span class="md-ellipsis">
Pipelines (optional layer)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#delivery-meta" class="md-nav__link">
<span class="md-ellipsis">
Delivery &amp; Meta
</span>
</a>
</li>
</ul>
</nav>
</li>
@@ -277,7 +372,7 @@
<li class="md-nav__item">
<a href="02_layering_rules/" class="md-nav__link">
<a href="02_layering_rules.md" class="md-nav__link">
@@ -299,7 +394,7 @@
<li class="md-nav__item">
<a href="03_composition_root/" class="md-nav__link">
<a href="03_composition_root.md" class="md-nav__link">
@@ -321,7 +416,7 @@
<li class="md-nav__item">
<a href="04_spec_first_routing/" class="md-nav__link">
<a href="04_spec_first_routing.md" class="md-nav__link">
@@ -343,7 +438,7 @@
<li class="md-nav__item">
<a href="05_crud_template/" class="md-nav__link">
<a href="05_crud_template.md" class="md-nav__link">
@@ -365,7 +460,7 @@
<li class="md-nav__item">
<a href="06_pipeline_scaffold/" class="md-nav__link">
<a href="06_pipeline_scaffold.md" class="md-nav__link">
@@ -387,7 +482,7 @@
<li class="md-nav__item">
<a href="07_worker_and_progress/" class="md-nav__link">
<a href="07_worker_and_progress.md" class="md-nav__link">
@@ -409,7 +504,7 @@
<li class="md-nav__item">
<a href="08_human_in_loop/" class="md-nav__link">
<a href="08_human_in_loop.md" class="md-nav__link">
@@ -431,7 +526,7 @@
<li class="md-nav__item">
<a href="09_infra_adaptors/" class="md-nav__link">
<a href="09_infra_adaptors.md" class="md-nav__link">
@@ -453,7 +548,7 @@
<li class="md-nav__item">
<a href="10_drone_pipelines/" class="md-nav__link">
<a href="10_drone_pipelines.md" class="md-nav__link">
@@ -475,7 +570,7 @@
<li class="md-nav__item">
<a href="11_versioning_and_changelog/" class="md-nav__link">
<a href="11_versioning_and_changelog.md" class="md-nav__link">
@@ -497,7 +592,7 @@
<li class="md-nav__item">
<a href="15_repository_inventory/" class="md-nav__link">
<a href="15_repository_inventory.md" class="md-nav__link">
@@ -519,7 +614,7 @@
<li class="md-nav__item">
<a href="12_genericity_matrix/" class="md-nav__link">
<a href="12_genericity_matrix.md" class="md-nav__link">
@@ -541,7 +636,7 @@
<li class="md-nav__item">
<a href="14_auth_integration/" class="md-nav__link">
<a href="14_auth_integration.md" class="md-nav__link">
@@ -563,7 +658,7 @@
<li class="md-nav__item">
<a href="13_library_registry/" class="md-nav__link">
<a href="13_library_registry.md" class="md-nav__link">
@@ -598,6 +693,77 @@
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#anatomy" class="md-nav__link">
<span class="md-ellipsis">
Anatomy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-1-domains" class="md-nav__link">
<span class="md-ellipsis">
Pillar 1 — Domains
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-2-openapi-spec-the-contract" class="md-nav__link">
<span class="md-ellipsis">
Pillar 2 — OpenAPI Spec (the contract)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-3-interfaces-infra" class="md-nav__link">
<span class="md-ellipsis">
Pillar 3 — Interfaces &amp; Infra
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pillar-4-ui" class="md-nav__link">
<span class="md-ellipsis">
Pillar 4 — UI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pipelines-optional-layer" class="md-nav__link">
<span class="md-ellipsis">
Pipelines (optional layer)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#delivery-meta" class="md-nav__link">
<span class="md-ellipsis">
Delivery &amp; Meta
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
@@ -615,26 +781,54 @@
<h1 id="standards-reference">Standards Reference</h1>
<p>The reference half of the Standards description: the enforceable rules,
patterns, and the internal library catalog that agents consult while coding.</p>
<p>The reference is organized as an <strong>anatomy</strong> — one entry checklist and four
pillars describing what an application is made of, plus delivery/meta pages
and an optional pipelines layer.</p>
<h2 id="anatomy">Anatomy</h2>
<ul>
<li><strong>Build Recipe</strong> — the ordered, agentic checklist for a new app; the entry
point that walks the whole anatomy.</li>
</ul>
<h2 id="pillar-1-domains">Pillar 1 — Domains</h2>
<ul>
<li><strong>Build Recipe</strong> — the ordered, agentic checklist for a new app.</li>
<li><strong>Layering Rules</strong> — the strict request → domain → repository layering and
the rules that enforce it.</li>
</ul>
<h2 id="pillar-2-openapi-spec-the-contract">Pillar 2 — OpenAPI Spec (the contract)</h2>
<ul>
<li><strong>Composition Root</strong> — a single composition point wiring the whole app.</li>
<li><strong>Spec-First Routing</strong> — the OpenAPI contract as the routing source of truth.</li>
<li><strong>CRUD Template</strong> — the standardized generic resource endpoint scaffold.</li>
<li><strong>Auth Integration</strong> — consuming the shared auth-server service (spec-driven,
no auth code).</li>
</ul>
<h2 id="pillar-3-interfaces-infra">Pillar 3 — Interfaces &amp; Infra</h2>
<ul>
<li><strong>Infra Adapters</strong> — how infrastructure connects without leaking into domain.</li>
</ul>
<h2 id="pillar-4-ui">Pillar 4 — UI</h2>
<ul>
<li><strong>UI: Auto-Rendered Admin &amp; Custom Pages</strong> — the <code>@aetoskia/react-openapi</code>
admin rendered from the spec's x- extensions, and <code>@aetoskia/react-auth</code>
for auth pages; the client template scaffold.</li>
</ul>
<h2 id="pipelines-optional-layer">Pipelines (optional layer)</h2>
<ul>
<li><strong>Pipeline Scaffold</strong> — the deterministic pipeline skeleton for multi-stage
jobs.</li>
<li><strong>Worker &amp; Progress</strong> — queued work, progress reporting, and visibility.</li>
<li><strong>Human-in-the-Loop</strong> — guardrails and approvals inside automations.</li>
<li><strong>Infra Adapters</strong> — how infrastructure connects without leaking into domain.</li>
<li><strong>Drone Pipelines</strong> — the two CI shapes: a PyPI package or a Docker deployment.</li>
<li><strong>Versioning &amp; Changelog</strong> — SemVer, the tag↔version contract, and the release
changelog.</li>
</ul>
<h2 id="delivery-meta">Delivery &amp; Meta</h2>
<ul>
<li><strong>Drone Pipelines</strong> — the two CI shapes: a PyPI/npm package or a Docker
deployment.</li>
<li><strong>Versioning &amp; Changelog</strong> — SemVer, the tag↔version contract, and the
release changelog.</li>
<li><strong>Repository Inventory</strong> — every covered repo, its type, version source, and
Drone shape.</li>
<li><strong>Genericity Matrix</strong> — what is library-backed, template-provided, or authored.</li>
<li><strong>Auth Integration</strong> — consuming the shared auth-server service (spec-driven,
no auth code).</li>
<li><strong>Genericity Matrix</strong> — what is library-backed, template-provided, or
authored.</li>
<li><strong>Library Registry</strong> — the catalog of internal libraries behind all of it.</li>
</ul>
<p>Read the <strong>Wiki</strong> for the philosophy and rationale; these pages are the

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"project": "standards",
"type": "standards",
"modules_count": 23,
"modules_count": 24,
"source": "hand-authored"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"module": "index",
"content": {
"path": "index",
"docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.\n- **Versioning & Changelog** — SemVer, the tag↔version contract, and the release\n changelog.\n- **Repository Inventory** — every covered repo, its type, version source, and\n Drone shape.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n",
"docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\nThe reference is organized as an **anatomy** — one entry checklist and four\npillars describing what an application is made of, plus delivery/meta pages\nand an optional pipelines layer.\n\n## Anatomy\n\n- **Build Recipe** — the ordered, agentic checklist for a new app; the entry\n point that walks the whole anatomy.\n\n## Pillar 1 — Domains\n\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n\n## Pillar 2 — OpenAPI Spec (the contract)\n\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n\n## Pillar 3 — Interfaces & Infra\n\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n\n## Pillar 4 — UI\n\n- **UI: Auto-Rendered Admin & Custom Pages** — the `@aetoskia/react-openapi`\n admin rendered from the spec's x- extensions, and `@aetoskia/react-auth`\n for auth pages; the client template scaffold.\n\n## Pipelines (optional layer)\n\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n\n## Delivery & Meta\n\n- **Drone Pipelines** — the two CI shapes: a PyPI/npm package or a Docker\n deployment.\n- **Versioning & Changelog** — SemVer, the tag↔version contract, and the\n release changelog.\n- **Repository Inventory** — every covered repo, its type, version source, and\n Drone shape.\n- **Genericity Matrix** — what is library-backed, template-provided, or\n authored.\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n",
"objects": {}
}
}

View File

@@ -2,7 +2,7 @@
"module": "repository_inventory",
"content": {
"path": "repository_inventory",
"docstring": "# Repository Inventory\n\n## Summary\n\nThe canonical list of Aetos repositories covered by the CI and versioning\nstandards, with the artifact each one produces, its version source, and its\nDrone shape. Every entry maps to the contract in\n[11 – Versioning & Changelog](11_versioning_and_changelog.md) and the two CI\nfiles in [10 – Drone Pipelines](10_drone_pipelines.md).\n\nThe same data is published machine-readably as the `registry://repos` MCP\nresource (and the `list_repos` tool), sourced from `standards/src/repos.json`.\nPaths are relative to the `aetos/` workspace root.\n\n---\n\n## Packages (Type A)\n\nA wheel + sdist published to the private index. Version source is\n`pyproject.toml`; Drone file is `python-lib.yaml` (tag-triggered).\n\n| Repo | Path | Docs | MCP server |\n|------|------|------|------------|\n| `dagpipe` | `dagpipe` | lib, wiki | `dagpipe:8006` |\n| `doc-forge` | `doc-forge` | lib, wiki | `docforge:8001` |\n| `hexa` | `hexa` | lib, wiki | `hexa:8008` |\n| `mail-intake` | `mail-intake` | lib, wiki | `mail_intake:8002` |\n| `mongo-ops` | `mongo-ops` | lib, wiki | `mongo_ops:8007` |\n| `omniread` | `omniread` | lib, wiki | `omniread:8003` |\n| `openapi-first` | `openapi-first` | lib, wiki | `openapi_first:8004` |\n| `py-jwt` | `py-jwt` | lib, wiki | `jwtlib:8005` |\n\n---\n\n## Services (Type B)\n\nA container image pushed to the registry and run on the Docker host. Version\nsource is the `VERSION` file; Drone file is `service-docker.yaml`.\n\n| Repo | Path | Trigger | Docs |\n|------|------|---------|------|\n| `auth-server` | `auth-server` | tag | api, lib, wiki |\n\nTag-triggered and enforces `tag == VERSION`.\n\n---\n\n## Required files per repo\n\n| Type | Version source | Changelog | Drone |\n|------|----------------|-----------|-------|\n| package | `pyproject.toml` | `CHANGELOG.md` | `python-lib.yaml` |\n| service | `VERSION` | `CHANGELOG.md` | `service-docker.yaml` |\n\nEvery covered repository must be able to satisfy `tag == version`:\nthe version file (never a commit id) is bumped in the release MR, the MR is\nmerged, and the tag is created on the merged commit.\n\n---\n\n## Reading this at runtime\n\n- Resource: `registry://repos`\n- Tool: `list_repos()`\n- Humans: this page (mirror of `standards/src/repos.json`).\n\n---\n\n## Related\n\n- [10 – Drone Pipelines](10_drone_pipelines.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)\n",
"docstring": "# Repository Inventory\n\n## Summary\n\nThe canonical list of Aetos repositories covered by the CI and versioning\nstandards, with the artifact each one produces, its version source, and its\nDrone shape. Every entry maps to the contract in\n[91 – Versioning & Changelog](91_versioning_and_changelog.md) and the two CI\nfiles in [90 – Drone Pipelines](90_drone_pipelines.md).\n\nThe same data is published machine-readably as the `registry://repos` MCP\nresource (and the `list_repos` tool), sourced from `standards/src/repos.json`.\nPaths are relative to the `aetos/` workspace root.\n\n---\n\n## Packages (Type A)\n\nA wheel + sdist published to the private index. Version source is\n`pyproject.toml`; Drone file is `python-lib.yaml` (tag-triggered).\n\n| Repo | Path | Docs | MCP server |\n|------|------|------|------------|\n| `dagpipe` | `dagpipe` | lib, wiki | `dagpipe:8006` |\n| `doc-forge` | `doc-forge` | lib, wiki | `docforge:8001` |\n| `hexa` | `hexa` | lib, wiki | `hexa:8008` |\n| `mail-intake` | `mail-intake` | lib, wiki | `mail_intake:8002` |\n| `mongo-ops` | `mongo-ops` | lib, wiki | `mongo_ops:8007` |\n| `omniread` | `omniread` | lib, wiki | `omniread:8003` |\n| `openapi-first` | `openapi-first` | lib, wiki | `openapi_first:8004` |\n| `py-jwt` | `py-jwt` | lib, wiki | `jwtlib:8005` |\n\n---\n\n## Services (Type B)\n\nA container image pushed to the registry and run on the Docker host. Version\nsource is the `VERSION` file; Drone file is `service-docker.yaml`.\n\n| Repo | Path | Trigger | Docs |\n|------|------|---------|------|\n| `auth-server` | `auth-server` | tag | api, lib, wiki |\n\nTag-triggered and enforces `tag == VERSION`.\n\n---\n\n## Required files per repo\n\n| Type | Version source | Changelog | Drone |\n|------|----------------|-----------|-------|\n| package | `pyproject.toml` | `CHANGELOG.md` | `python-lib.yaml` |\n| service | `VERSION` | `CHANGELOG.md` | `service-docker.yaml` |\n\nEvery covered repository must be able to satisfy `tag == version`:\nthe version file (never a commit id) is bumped in the release MR, the MR is\nmerged, and the tag is created on the merged commit.\n\n---\n\n## Reading this at runtime\n\n- Resource: `registry://repos`\n- Tool: `list_repos()`\n- Humans: this page (mirror of `standards/src/repos.json`).\n\n---\n\n## Related\n\n- [90 – Drone Pipelines](90_drone_pipelines.md) · [91 – Versioning & Changelog](91_versioning_and_changelog.md) · [93 – Library Registry](93_library_registry.md)\n",
"objects": {}
}
}

View File

@@ -0,0 +1,8 @@
{
"module": "ui_admin",
"content": {
"path": "ui_admin",
"docstring": "# UI: Auto-Rendered Admin & Custom Pages\n\n## Summary\n\nThe UI axis of an Aetos app is a **published React client**, not code written\nper application. Two npm packages carry the whole surface:\n\n| Package | Role |\n|---|---|\n| `@aetoskia/react-openapi` | Renders a complete admin panel (CRUD tables, forms, filters, sorting, pagination, uploads) **from the app's `openapi.yaml`** — zero UI code. |\n| `@aetoskia/react-auth` | Auth contexts and pages (login/register/me) wired to the auth-server, plus the axios client with token handling. |\n\nBoth publish to the private registry `https://npm.aetoskia.com`. The server\nspec is the single source of truth: the same `openapi.yaml` that drives\n`openapi-first` routing drives the admin UI, through **x- extension fields**\n(see below).\n\n---\n\n## Scaffold\n\n```bash\ncp -r <docs>/standards/src/templates/client apps/<your-app>-client\ncd apps/<your-app>-client\nnpm install # deps come from npm.aetoskia.com via .npmrc\nnpm run dev\n```\n\nThe client template is a Vite + MUI app with:\n\n- `src/openapi-config.ts` — points the renderer at the app's spec and the\n auth-server's spec;\n- `.env.example` — `VITE_API_BASE_URL` (app API) and `VITE_AUTH_BASE_URL`\n (auth-server);\n- a `Home` page that auto-lists every resource the spec declares, and a\n `Header`/`Footer`/theme scaffold.\n\nCustom pages are ordinary React routes layered on top; the auto-rendered admin\nis available per resource at `#/resource/<name>`.\n\n---\n\n## x- extensions: how the spec steers the UI\n\nThe admin renderer reads a catalog of OpenAPI extension fields (`x-resource`,\n`x-list-columns`, `x-display-format`, `x-label`, `x-order`, `x-hidden`,\n`x-sortable`, `x-filterable`, `x-autocomplete`, `x-fk`, `x-ui-type`,\n`x-upload`, `x-sse`, `x-profile`, and the auth wiring `x-server-url` /\n`x-login-path` / `x-me-path` / …) declared on schemas and properties.\n\n**The catalog is canonical in the react-openapi source, not in this doc.**\nRead it from the MCP bundle:\n\n```\ndocs://modules/react-openapi # module: specExtensions — the full catalog\n```\n\nThe app's own spec carries the per-resource values; the catalog explains what\neach field does. The template's `openapi.yaml` shows the convention on the\nexample `Item` and `Job` schemas.\n\n---\n\n## Golden rules\n\n1. **The spec owns the UI.** Add a resource or rename a column in\n `openapi.yaml`; the admin follows. Never fork the renderer per app.\n2. **Custom pages for workflows, auto-render for CRUD.** Anything that is\n \"list / create / edit / delete an entity\" is a spec concern; anything that\n is a bespoke workflow is a React page.\n3. **Auth is consumed, never re-implemented.** `@aetoskia/react-auth` holds\n tokens and calls the auth-server endpoints declared by the x- wiring; pages\n read the context.\n4. **Publish, don't vendor.** UI shared across apps belongs in\n `@aetoskia/react-openapi` or `@aetoskia/react-auth` (SemVer, tag-triggered\n npm release), not copy-pasted into app trees.\n\n---\n\n## Notes\n\n- The server template ships authless by default (see\n [22 – Auth Integration](22_auth_integration.md)); the client template pairs\n with it and degrades to \"no login\" until a security scheme is declared.\n- `react/vet` is the full-stack reference implementation: spec with x-\n extensions, server, and the consuming client in one tree.\n",
"objects": {}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"module": "wiki_development_releasing",
"content": {
"path": "wiki_development_releasing",
"docstring": "# Releasing & Versioning\n\nHow to cut a release of any Aetos repository. The rule lives in the reference\npage [Versioning & Changelog](../lib/11_versioning_and_changelog/); this is the\nrunbook you follow.\n\nEvery repository is one of two Drone shapes — a **PyPI package** or a **Docker\ndeployment** ([Drone Pipelines](../lib/10_drone_pipelines/)). Both release the\nsame way: the version bump and changelog promotion go **in the merge request**,\nand the tag is created on the merged commit.\n\n---\n\n## 🧾 Before you start\n\n- You have an open **merge request** holding the change, and `main` is green\n (the Drone quality gate passed on the last run).\n- You know whether this is a **breaking**, **feature**, or **fix** release.\n- The version file exists:\n - package → `version` in `pyproject.toml`\n - service → `VERSION` at the repo root\n\n---\n\n## 🚦 Pick the next version\n\nThe version file and the tag must be identical. For `0.y.z`, `0.y` is the major\nline (see the reference page); once at `1.0.0`, normal SemVer applies.\n\n| Change | `0.y.z` → | `1.0.0+` → |\n|--------|-----------|------------|\n| Breaking | `0.(y+1).0` | `(x+1).0.0` |\n| Feature | `0.y.(z+1)` | `x.(y+1).0` |\n| Fix | `0.y.(z+1)` | `x.y.(z+1)` |\n\n---\n\n## 🌿 Release in the MR\n\nThe version is known while the change is still a merge request, so the bump\nrides along with it. Do all edits **in the MR**:\n\n- **Reconcile the changelog first.** `[Unreleased]` is a **draft, not the source\n of truth**: diff the change against the last tag\n (`git log <last-tag>..HEAD`, `git diff --stat <last-tag>..HEAD`) and add an\n entry for every user-facing change that is missing. **Always show the proposed\n additions to the user and wait for approval** before promoting — ask rather\n than guess when an entry is ambiguous.\n\n- **Promote the changelog** in `CHANGELOG.md`: move the (now reconciled)\n `[Unreleased]` content under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a\n fresh empty `[Unreleased]` on top, and add the compare links — naming the\n **version/tag**, never a commit id:\n\n ```markdown\n [Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD\n [x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z\n ```\n\n- **Bump the version file** to the intended release version: `version = \"x.y.z\"`\n in `pyproject.toml` (packages) or the `VERSION` file (services).\n\n---\n\n## 🏷️ Tag the merged commit\n\n1. **Merge or squash** the MR into `main`.\n2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).\n3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.\n\n### 🤖 Agent-assisted tagging\n\nThe agent does not author or merge the MR and never tags before the merge. After\nyou confirm the MR is merged/squashed and tell it to proceed, the agent:\n\n1. Waits for that explicit go-ahead.\n2. Finds the commit that introduced the bump:\n - package:\n `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n - service:\n `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n3. Verifies that commit's version file equals `x.y.z`, then tags **only** that\n commit: `git tag x.y.z <sha>`.\n4. Does **not** push — it asks you to run `git push origin x.y.z`.\n\n---\n\n## 🤖 What Drone does\n\n- **Type A (package):** asserts `tag == pyproject version`, then skips if that\n version already exists on the index, runs the quality gate, builds, and\n uploads the wheel + sdist.\n- **Type B (service):** asserts `tag == VERSION`, resolves the tag, skips if the\n image already exists, runs the quality gate, builds, pushes, and restarts the\n container.\n\nIf the guard fails, nothing is published — fix the version file, amend the\ncommit, and move the tag **only** if it was never pushed.\n\n---\n\n## 🛟 Backfill / recovery\n\nIf an older tag never published (a past version-file mismatch, an index\noutage):\n\n- **Package:** run the `backfill-pypi-from-tags` pipeline (custom event). It\n checks out every tag, verifies `tag == pyproject version` at that commit,\n and uploads the ones missing from the index. Already-fixed history is never\n rewritten.\n- **Service:** rebuild the image for the missing tag with a fresh commit and a\n new patch version; do not move a published tag.\n\n---\n\n## 🔁 If a release is wrong\n\n- Never reuse or force-move a pushed tag.\n- Fix forward: bump to the next patch, promote the changelog, tag again.\n\n---\n\n## Related\n\n- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/11_versioning_and_changelog/) · [Drone Pipelines](../lib/10_drone_pipelines/)\n",
"docstring": "# Releasing & Versioning\n\nHow to cut a release of any Aetos repository. The rule lives in the reference\npage [Versioning & Changelog](../lib/91_versioning_and_changelog/); this is the\nrunbook you follow.\n\nEvery repository is one of two Drone shapes — a **PyPI package** or a **Docker\ndeployment** ([Drone Pipelines](../lib/90_drone_pipelines/)). Both release the\nsame way: the version bump and changelog promotion go **in the merge request**,\nand the tag is created on the merged commit.\n\n---\n\n## 🧾 Before you start\n\n- You have an open **merge request** holding the change, and `main` is green\n (the Drone quality gate passed on the last run).\n- You know whether this is a **breaking**, **feature**, or **fix** release.\n- The version file exists:\n - package → `version` in `pyproject.toml`\n - service → `VERSION` at the repo root\n\n---\n\n## 🚦 Pick the next version\n\nThe version file and the tag must be identical. For `0.y.z`, `0.y` is the major\nline (see the reference page); once at `1.0.0`, normal SemVer applies.\n\n| Change | `0.y.z` → | `1.0.0+` → |\n|--------|-----------|------------|\n| Breaking | `0.(y+1).0` | `(x+1).0.0` |\n| Feature | `0.y.(z+1)` | `x.(y+1).0` |\n| Fix | `0.y.(z+1)` | `x.y.(z+1)` |\n\n---\n\n## 🌿 Release in the MR\n\nThe version is known while the change is still a merge request, so the bump\nrides along with it. Do all edits **in the MR**:\n\n- **Reconcile the changelog first.** `[Unreleased]` is a **draft, not the source\n of truth**: diff the change against the last tag\n (`git log <last-tag>..HEAD`, `git diff --stat <last-tag>..HEAD`) and add an\n entry for every user-facing change that is missing. **Always show the proposed\n additions to the user and wait for approval** before promoting — ask rather\n than guess when an entry is ambiguous.\n\n- **Promote the changelog** in `CHANGELOG.md`: move the (now reconciled)\n `[Unreleased]` content under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a\n fresh empty `[Unreleased]` on top, and add the compare links — naming the\n **version/tag**, never a commit id:\n\n ```markdown\n [Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD\n [x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z\n ```\n\n- **Bump the version file** to the intended release version: `version = \"x.y.z\"`\n in `pyproject.toml` (packages) or the `VERSION` file (services).\n\n---\n\n## 🏷️ Tag the merged commit\n\n1. **Merge or squash** the MR into `main`.\n2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).\n3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.\n\n### 🤖 Agent-assisted tagging\n\nThe agent does not author or merge the MR and never tags before the merge. After\nyou confirm the MR is merged/squashed and tell it to proceed, the agent:\n\n1. Waits for that explicit go-ahead.\n2. Finds the commit that introduced the bump:\n - package:\n `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n - service:\n `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n3. Verifies that commit's version file equals `x.y.z`, then tags **only** that\n commit: `git tag x.y.z <sha>`.\n4. Does **not** push — it asks you to run `git push origin x.y.z`.\n\n---\n\n## 🤖 What Drone does\n\n- **Type A (package):** asserts `tag == pyproject version`, then skips if that\n version already exists on the index, runs the quality gate, builds, and\n uploads the wheel + sdist.\n- **Type B (service):** asserts `tag == VERSION`, resolves the tag, skips if the\n image already exists, runs the quality gate, builds, pushes, and restarts the\n container.\n\nIf the guard fails, nothing is published — fix the version file, amend the\ncommit, and move the tag **only** if it was never pushed.\n\n---\n\n## 🛟 Backfill / recovery\n\nIf an older tag never published (a past version-file mismatch, an index\noutage):\n\n- **Package:** run the `backfill-pypi-from-tags` pipeline (custom event). It\n checks out every tag, verifies `tag == pyproject version` at that commit,\n and uploads the ones missing from the index. Already-fixed history is never\n rewritten.\n- **Service:** rebuild the image for the missing tag with a fresh commit and a\n new patch version; do not move a published tag.\n\n---\n\n## 🔁 If a release is wrong\n\n- Never reuse or force-move a pushed tag.\n- Fix forward: bump to the next patch, promote the changelog, tag again.\n\n---\n\n## Related\n\n- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/91_versioning_and_changelog/) · [Drone Pipelines](../lib/90_drone_pipelines/)\n",
"objects": {}
}
}

View File

@@ -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\nrules — [Build Recipe](../lib/01_build_recipe/), [Layering](../lib/02_layering_rules/), [Composition\nRoot](../lib/03_composition_root/), [Spec-First\nRouting](../lib/04_spec_first_routing/), [CRUD\nTemplate](../lib/05_crud_template/), [Pipeline\nScaffold](../lib/06_pipeline_scaffold/), [Worker &\nProgress](../lib/07_worker_and_progress/), [Human-in-the-Loop](../lib/08_human_in_loop/),\n[Infra Adapters](../lib/09_infra_adaptors/), [Genericity\nMatrix](../lib/12_genericity_matrix/), [Library\nRegistry](../lib/13_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| [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",
"objects": {}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"module": "wiki_overview",
"content": {
"path": "wiki_overview",
"docstring": "# Overview\n\nThe blueprint is a **hexagonal (ports & adapters) application** with a\n**spec-first** web edge and a **single composition root**. It favors explicit\nwiring over implicit magic and existing internal libraries over hand-written\ninfrastructure. Start here to build the mental model; the enforceable details\nlive in the reference pages.\n\n---\n\n## 🏗️ Architecture\n\n```mermaid\nflowchart LR\n subgraph EDGE[\"interfaces/ — HTTP edge\"]\n OAS[\"openapi.yaml<br/>operationId contract\"]\n RT[\"<resource>/routes.py<br/>thin handlers\"]\n end\n\n subgraph CORE[\"domain/ — business core\"]\n MD[\"models\"]\n PT[\"ports (adapter binding seam)\"]\n SV[\"services\"]\n end\n\n subgraph INFRA[\"infra/ — adapters\"]\n RP[\"mongo repos (mongo-ops)\"]\n AD[\"cache · stores · llm · mail\"]\n DB[(\"MongoDB\")]\n end\n\n CR[\"init.py<br/>composition root\"]\n\n OAS --> RT\n RT --> SV\n SV --> PT\n SV --> RP\n PT -. depends on .- RP\n CR --- RT\n CR --- SV\n CR --- RP\n RP --> DB\n AD --- CR\n```\n\nThe rule holds at every edge: code in an outer ring may depend on a ring closer\nto the core, never the reverse. `domain/` never imports a web framework;\nonly its `ports/` seam reaches into `infra/` to bind adapters.\n\n> **Legend** — solid arrows are compile-time calls; dotted arrows are the\n> port/adapter binding, performed by the composition root at startup.\n\n---\n\n## 🔄 The one-way dependency rule\n\n| Ring | May import | Never imports |\n|-------------|------------------------------------|----------------------------------|\n| `interfaces/` | `domain`, framework glue (glue only) | business logic, `infra` directly |\n| `domain/` | its own models + its `ports/` seam (which binds `infra`/libraries) | web frameworks, `interfaces/` |\n| `infra/` | `domain` ports, drivers, libraries | — |\n| `init.py` | everything (the wiring point) | — |\n\n---\n\n## 🧭 Core principles\n\n1. **One-way dependencies.** `interfaces -> domain -> infra` is allowed; the\n reverse is forbidden.\n2. **Ports in the domain, adapters in infra.** A service depends on a port; the\n port's factory binds the concrete adapter, and the composition root calls\n those factories.\n3. **One composition root.** Nothing constructs its own dependencies. All\n wiring lives in one module (`init.py`) exposed as a flat resource registry.\n4. **Spec-first.** The HTTP contract is declared in `openapi.yaml`; route\n handlers are looked up by `operationId`. There are no decorator routers.\n5. **Reuse the libraries.** Orchestration, DI, Mongo repositories, ingestion,\n auth, and docs come from internal libraries — see the [Library\n Registry](../lib/13_library_registry/).\n6. **Explicit state machines.** Long-running work moves through explicit,\n durable statuses and can pause for human input.\n\n---\n\n## ⚙️ Runtime processes\n\nA standard app may run as up to three processes that share the same composition\nroot:\n\n| Process | Entry point | Responsibility |\n|---------|------------------------|-------------------------------------------|\n| API | `main.py` | serve HTTP; start/stop resources via lifespan |\n| Worker | `worker.py` | poll durable jobs, run pipelines, publish progress |\n| Scripts | `python -m scripts.<name>` | one-shot operator tasks |\n\n---\n\n## ➡️ Read Next\n\n- [02 – App Components](02_components.md) · [03 – Start an App](03_start_an_app.md)\n",
"docstring": "# Overview\n\nThe blueprint is a **hexagonal (ports & adapters) application** with a\n**spec-first** web edge and a **single composition root**. It favors explicit\nwiring over implicit magic and existing internal libraries over hand-written\ninfrastructure. Start here to build the mental model; the enforceable details\nlive in the reference pages.\n\n---\n\n## 🏗️ Architecture\n\n```mermaid\nflowchart LR\n subgraph EDGE[\"interfaces/ — HTTP edge\"]\n OAS[\"openapi.yaml<br/>operationId contract\"]\n RT[\"<resource>/routes.py<br/>thin handlers\"]\n end\n\n subgraph CORE[\"domain/ — business core\"]\n MD[\"models\"]\n PT[\"ports (adapter binding seam)\"]\n SV[\"services\"]\n end\n\n subgraph INFRA[\"infra/ — adapters\"]\n RP[\"mongo repos (mongo-ops)\"]\n AD[\"cache · stores · llm · mail\"]\n DB[(\"MongoDB\")]\n end\n\n CR[\"init.py<br/>composition root\"]\n\n OAS --> RT\n RT --> SV\n SV --> PT\n SV --> RP\n PT -. depends on .- RP\n CR --- RT\n CR --- SV\n CR --- RP\n RP --> DB\n AD --- CR\n```\n\nThe rule holds at every edge: code in an outer ring may depend on a ring closer\nto the core, never the reverse. `domain/` never imports a web framework;\nonly its `ports/` seam reaches into `infra/` to bind adapters.\n\n> **Legend** — solid arrows are compile-time calls; dotted arrows are the\n> port/adapter binding, performed by the composition root at startup.\n\n---\n\n## 🔄 The one-way dependency rule\n\n| Ring | May import | Never imports |\n|-------------|------------------------------------|----------------------------------|\n| `interfaces/` | `domain`, framework glue (glue only) | business logic, `infra` directly |\n| `domain/` | its own models + its `ports/` seam (which binds `infra`/libraries) | web frameworks, `interfaces/` |\n| `infra/` | `domain` ports, drivers, libraries | — |\n| `init.py` | everything (the wiring point) | — |\n\n---\n\n## 🧭 Core principles\n\n1. **One-way dependencies.** `interfaces -> domain -> infra` is allowed; the\n reverse is forbidden.\n2. **Ports in the domain, adapters in infra.** A service depends on a port; the\n port's factory binds the concrete adapter, and the composition root calls\n those factories.\n3. **One composition root.** Nothing constructs its own dependencies. All\n wiring lives in one module (`init.py`) exposed as a flat resource registry.\n4. **Spec-first.** The HTTP contract is declared in `openapi.yaml`; route\n handlers are looked up by `operationId`. There are no decorator routers.\n5. **Reuse the libraries.** Orchestration, DI, Mongo repositories, ingestion,\n auth, and docs come from internal libraries — see the [Library\n Registry](../lib/93_library_registry/).\n6. **Explicit state machines.** Long-running work moves through explicit,\n durable statuses and can pause for human input.\n\n---\n\n## ⚙️ Runtime processes\n\nA standard app may run as up to three processes that share the same composition\nroot:\n\n| Process | Entry point | Responsibility |\n|---------|------------------------|-------------------------------------------|\n| API | `main.py` | serve HTTP; start/stop resources via lifespan |\n| Worker | `worker.py` | poll durable jobs, run pipelines, publish progress |\n| Scripts | `python -m scripts.<name>` | one-shot operator tasks |\n\n---\n\n## ➡️ Read Next\n\n- [02 – App Components](02_components.md) · [03 – Start an App](03_start_an_app.md)\n",
"objects": {}
}
}

View File

@@ -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/04_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, resource=..., family=...)`, authoring only the\n 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 <docs>/standards/src/templates/app apps/<your-app>\n```\n\n---\n\n## 📚 Reference\n\n- [Build Recipe](../lib/01_build_recipe/) — the deterministic checklist\n- [Genericity Matrix](../lib/12_genericity_matrix/) — Tier A / B / C\n- [Library Registry](../lib/13_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, resource=..., family=...)`, authoring only the\n 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 <docs>/standards/src/templates/app apps/<your-app>\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",
"objects": {}
}
}

View File

@@ -4,16 +4,16 @@
"resource": "docs://modules/build_recipe",
"title": "Build Recipe"
},
{
"module": "layering_rules",
"resource": "docs://modules/layering_rules",
"title": "Layering Rules"
},
{
"module": "composition_root",
"resource": "docs://modules/composition_root",
"title": "Composition Root"
},
{
"module": "layering_rules",
"resource": "docs://modules/layering_rules",
"title": "Layering Rules"
},
{
"module": "spec_first_routing",
"resource": "docs://modules/spec_first_routing",
@@ -24,6 +24,21 @@
"resource": "docs://modules/crud_template",
"title": "CRUD Template"
},
{
"module": "auth_integration",
"resource": "docs://modules/auth_integration",
"title": "Auth Integration"
},
{
"module": "infra_adaptors",
"resource": "docs://modules/infra_adaptors",
"title": "Infra Adapters"
},
{
"module": "ui_admin",
"resource": "docs://modules/ui_admin",
"title": "UI: Auto-Rendered Admin & Custom Pages"
},
{
"module": "pipeline_scaffold",
"resource": "docs://modules/pipeline_scaffold",
@@ -39,11 +54,6 @@
"resource": "docs://modules/human_in_loop",
"title": "Human-in-the-Loop"
},
{
"module": "infra_adaptors",
"resource": "docs://modules/infra_adaptors",
"title": "Infra Adapters"
},
{
"module": "drone_pipelines",
"resource": "docs://modules/drone_pipelines",
@@ -64,11 +74,6 @@
"resource": "docs://modules/library_registry",
"title": "Library Registry"
},
{
"module": "auth_integration",
"resource": "docs://modules/auth_integration",
"title": "Auth Integration"
},
{
"module": "repository_inventory",
"resource": "docs://modules/repository_inventory",

View File

@@ -1,6 +1,8 @@
DEBUG=false
REPO_BACKEND=memory
MONGO_URI=mongodb://localhost:27017
MONGO_DB=app_template
PORT=8000
WORKER_POLL_SECONDS=30
WORKER_MAX_RETRIES=3
AUTH_SERVER=http://localhost:8000

View File

@@ -3,6 +3,23 @@
App configuration loaded from environment — a Settings dataclass with defaults
plus a singleton settings instance populated from OS environment variables at
import time.
---
The template reads these knobs:
- ``repo_backend`` — ``memory`` (default, zero-dependency process-local store)
or ``mongo`` (persistent, via the shared mongo-ops connection manager).
- ``mongo_uri`` / ``mongo_db`` — connection targets when the Mongo backend is
selected.
- ``port`` — the port uvicorn binds (keep in sync with the OpenAPI
``servers:`` entry and the client's ``VITE_API_BASE_URL``).
- ``worker_poll_seconds`` / ``worker_max_retries`` — background worker cadence.
``AUTH_SERVER`` is intentionally *not* re-exported here: ``openapi_first``
resolves ``{AUTH_SERVER}`` straight from the process environment when the
application is built, so the env var remains the single source of truth for
the generated bearer-JWT introspection dependency.
"""
import os
from dataclasses import dataclass
@@ -14,23 +31,30 @@ class Settings:
Attributes:
debug (bool): Enable debug behavior. Defaults to False.
repo_backend (str): Persistence flavour, ``memory`` or ``mongo``.
Defaults to ``memory``.
mongo_uri (str): MongoDB connection URI. Defaults to mongodb://localhost:27017.
mongo_db (str): MongoDB database name. Defaults to app_template.
port (int): Local HTTP port. Defaults to 8000.
worker_poll_seconds (int): Worker poll interval in seconds. Defaults to 30.
worker_max_retries (int): Maximum job retries before skipping. Defaults to 3.
"""
debug: bool = False
repo_backend: str = "memory"
mongo_uri: str = "mongodb://localhost:27017"
mongo_db: str = "app_template"
port: int = 8000
worker_poll_seconds: int = 30
worker_max_retries: int = 3
settings = Settings(
debug=os.environ.get("DEBUG", "").lower() in ("1", "true", "yes"),
repo_backend=os.environ.get("REPO_BACKEND", "memory").lower(),
mongo_uri=os.environ.get("MONGO_URI", "mongodb://localhost:27017"),
mongo_db=os.environ.get("MONGO_DB", "app_template"),
port=int(os.environ.get("PORT", "8000")),
worker_poll_seconds=int(os.environ.get("WORKER_POLL_SECONDS", "30")),
worker_max_retries=int(os.environ.get("WORKER_MAX_RETRIES", "3")),
)

View File

@@ -9,10 +9,12 @@ class Item(BaseModel):
"""A single countable resource.
Attributes:
id (int | None): Server-assigned identifier. Defaults to None.
name (str): Item name.
quantity (int): Item quantity. Defaults to 1.
"""
id: int | None = None
name: str
quantity: int = Field(default=1)

View File

@@ -1,21 +1,25 @@
"""# Summary
Mongo persistence for items: document schema and repository factory.
Items persistence ports — integer-id document and repository factory.
"""
from mongo_ops import BaseDocument, BaseRepository
from mongo_ops import BaseDocument
from pydantic import Field
from domain.item.models import Item
from infra.db.repos.mongo import _repo_factory as mongo_repo_factory
from infra.db.repos import create_repo
class ItemDocument(Item, BaseDocument):
"""Mongo document form of :class:`Item`."""
"""Persisted item document. The integer ``id`` shadows the ObjectId."""
id: int | None = Field(default=None, validation_alias="_id")
def create_item_repo() -> BaseRepository:
"""Create the Mongo-backed item repository.
def create_item_repo():
"""Create the item repository selected by ``settings.repo_backend``.
Returns:
BaseRepository: Repository wired to the ``items`` collection.
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
``items`` collection/store.
"""
return mongo_repo_factory("items", ItemDocument)
return create_repo("items", ItemDocument)

View File

@@ -11,6 +11,7 @@ class Job(BaseModel):
"""A unit of background work.
Attributes:
id (int | None): Server-assigned identifier. Defaults to None.
name (str): Job name.
status (str): Lifecycle status. Defaults to "pending".
retry_count (int): Times the job failed. Defaults to 0.
@@ -18,6 +19,7 @@ class Job(BaseModel):
payload (dict | None): Arbitrary job payload. Defaults to None.
"""
id: int | None = None
name: str
status: Literal["pending", "processing", "done", "failed"] = "pending"
retry_count: int = Field(default=0)

View File

@@ -1,21 +1,25 @@
"""# Summary
Mongo persistence for jobs: document schema and repository factory.
Jobs persistence ports — integer-id document and repository factory.
"""
from mongo_ops import BaseDocument, BaseRepository
from mongo_ops import BaseDocument
from pydantic import Field
from domain.job.models import Job
from infra.db.repos.mongo import _repo_factory as mongo_repo_factory
from infra.db.repos import create_repo
class JobDocument(Job, BaseDocument):
"""Mongo document form of :class:`Job`."""
"""Persisted job document. The integer ``id`` shadows the ObjectId."""
id: int | None = Field(default=None, validation_alias="_id")
def create_job_repo() -> BaseRepository:
"""Create the Mongo-backed job repository.
def create_job_repo():
"""Create the job repository selected by ``settings.repo_backend``.
Returns:
BaseRepository: Repository wired to the ``jobs`` collection.
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
``jobs`` collection/store.
"""
return mongo_repo_factory("jobs", JobDocument)
return create_repo("jobs", JobDocument)

View File

@@ -0,0 +1,31 @@
"""# Summary
Repository factory — picks the in-memory or Mongo integer-id adapter based
on ``settings.repo_backend``.
---
Every domain slice calls :func:`create_repo` so the backend choice lives in
one place: ``REPO_BACKEND=memory`` (default) yields the zero-dependency
process-local store; ``REPO_BACKEND=mongo`` yields the persistent adapter.
"""
from typing import Any
from config import settings
from infra.db.repos.memory import IntIdMemoryRepository
from infra.db.repos.mongo_int import IntIdMongoRepository
def create_repo(collection: str, document_cls: type[Any]):
"""Create the integer-id repository selected by ``settings.repo_backend``.
Args:
collection (str): Collection / store name for the resource.
document_cls (type[Any]): Document model bound to the repository.
Returns:
IntIdMemoryRepository | IntIdMongoRepository: The configured repository.
"""
if settings.repo_backend == "mongo":
return IntIdMongoRepository(collection, document_cls)
return IntIdMemoryRepository(document_cls)

View File

@@ -0,0 +1,161 @@
"""# Summary
In-memory integer-id repository — the zero-dependency persistence adapter.
---
One process-local dictionary per resource, keyed by auto-incrementing integer
``id``. The wire contract (``openapi.yaml``) declares integer primary keys,
so this adapter speaks integers end-to-end and is the default backend
(``REPO_BACKEND=memory``).
The method surface mirrors the mongo-ops repository duck-type consumed by
``infra.adapters.service.CrudsServiceAdaptor``: ``create``, ``get_by_id``,
``get_many``, ``update``, ``patch``, ``delete``.
Notes:
Not thread-safe and not durable — deliberately. Swap to the Mongo
adapter via ``REPO_BACKEND=mongo`` without touching domain code.
"""
from typing import Any
from pydantic import BaseModel
class IntIdMemoryRepository:
"""Dictionary-backed repository with auto-increment integer ids.
Attributes:
document_cls (type[BaseModel]): Document model instantiated for
every stored record.
"""
def __init__(self, document_cls: type[BaseModel]):
"""Initialize an empty store for ``document_cls``.
Args:
document_cls (type[BaseModel]): Document model bound to this repo.
"""
self.document_cls = document_cls
self._docs: dict[int, BaseModel] = {}
self._seq = 0
def _to_doc(self, data: Any) -> BaseModel:
"""Coerce ``data`` into a document instance.
Args:
data (Any): Document instance or field mapping.
Returns:
BaseModel: Document built from ``data``.
"""
if isinstance(data, self.document_cls):
return data
return self.document_cls(**dict(data))
async def create(self, data: Any) -> BaseModel:
"""Persist a new document with a freshly allocated integer id.
Args:
data (Any): Document instance or field mapping (id ignored).
Returns:
BaseModel: The stored document with ``id`` assigned.
"""
doc = self._to_doc(data)
self._seq += 1
doc.id = self._seq
self._docs[doc.id] = doc
return doc
async def get_by_id(self, id: int) -> BaseModel | None:
"""Fetch one document by integer id.
Args:
id (int): Document identifier.
Returns:
BaseModel | None: The stored document, or ``None`` when absent.
"""
return self._docs.get(int(id))
async def get_many(self, filter: dict | None = None, **_: Any) -> list[BaseModel]:
"""List documents, optionally filtered by top-level equality.
Args:
filter (dict | None): Equality filter on document fields.
Returns:
list[BaseModel]: Matching documents in insertion order.
"""
docs = list(self._docs.values())
if filter:
docs = [d for d in docs if all(getattr(d, k, None) == v for k, v in filter.items())]
return docs
async def update(self, id: int, data: Any) -> BaseModel | None:
"""Replace a document by integer id.
Args:
id (int): Document identifier.
data (Any): Replacement document or field mapping.
Returns:
BaseModel | None: The stored document, or ``None`` when absent.
"""
if int(id) not in self._docs:
return None
doc = self._to_doc(data)
doc.id = int(id)
self._docs[doc.id] = doc
return doc
async def patch(self, id: int, data: dict) -> BaseModel | None:
"""Partially update a document by integer id.
Args:
id (int): Document identifier.
data (dict): Fields to merge into the stored document.
Returns:
BaseModel | None: The stored document, or ``None`` when absent.
"""
current = self._docs.get(int(id))
if current is None:
return None
merged = current.model_dump()
merged.update(data)
doc = self.document_cls(**merged)
doc.id = int(id)
self._docs[doc.id] = doc
return doc
async def delete(self, id: int) -> bool:
"""Delete a document by integer id.
Args:
id (int): Document identifier.
Returns:
bool: ``True`` when a document was removed.
"""
return self._docs.pop(int(id), None) is not None
async def delete_many(self, filter: dict | None = None) -> int:
"""Delete every document (optionally filtered by equality).
Args:
filter (dict | None): Equality filter on document fields.
Returns:
int: Number of documents removed.
"""
victims = await self.get_many(filter)
for doc in victims:
self._docs.pop(doc.id, None)
return len(victims)
def clear(self) -> None:
"""Remove every document and reset the id counter."""
self._docs.clear()
self._seq = 0

View File

@@ -0,0 +1,184 @@
"""# Summary
MongoDB integer-id repository — the persistent persistence adapter.
---
The wire contract declares integer primary keys, while mongo-ops'
``BaseRepository`` allocates ``ObjectId`` keys. This adapter therefore talks
to the shared ``MongoConnectionManager`` database directly (the Tier A
connection lifecycle is reused; only the id strategy differs) and stores
records with auto-incrementing integer ``_id`` values allocated from a
``_counters`` collection.
The method surface mirrors the in-memory adapter exactly, so
``CrudsServiceAdaptor`` and the domain services are backend-agnostic.
"""
from typing import Any
from mongo_ops import MongoConnectionManager
from pydantic import BaseModel
class IntIdMongoRepository:
"""Motor-backed repository with auto-increment integer ids.
Attributes:
collection (str): MongoDB collection name.
document_cls (type[BaseModel]): Document model bound to this repo.
"""
def __init__(self, collection: str, document_cls: type[BaseModel]):
"""Bind the repository to a collection and document model.
Args:
collection (str): Collection name to operate on.
document_cls (type[BaseModel]): Document model persisted here.
"""
self.collection = collection
self.document_cls = document_cls
@property
def _col(self):
"""AsyncIOMotorCollection: The live collection handle."""
return MongoConnectionManager._database[self.collection]
async def _next_id(self) -> int:
"""Allocate the next integer id from the shared counters collection.
Returns:
int: A monotonically increasing identifier.
"""
counters = MongoConnectionManager._database["_counters"]
doc = await counters.find_one_and_update(
{"_id": self.collection},
{"$inc": {"seq": 1}},
upsert=True,
return_document=True,
)
return int(doc["seq"])
def _to_doc(self, data: Any) -> BaseModel:
"""Coerce ``data`` into a document instance.
Args:
data (Any): Document instance or field mapping.
Returns:
BaseModel: Document built from ``data``.
"""
if isinstance(data, self.document_cls):
return data
return self.document_cls(**dict(data))
def _to_payload(self, doc: BaseModel, id: int | None = None) -> dict:
"""Serialize a document into a Mongo-ready payload.
Args:
doc (BaseModel): Document to serialize.
id (int | None): Explicit ``_id``; taken from the doc when omitted.
Returns:
dict: Mapping with an integer ``_id`` key.
"""
payload = doc.model_dump()
payload.pop("id", None)
payload["_id"] = id if id is not None else doc.id
return payload
async def create(self, data: Any) -> BaseModel:
"""Persist a new document with a freshly allocated integer id.
Args:
data (Any): Document instance or field mapping (id ignored).
Returns:
BaseModel: The stored document with ``id`` assigned.
"""
doc = self._to_doc(data)
new_id = await self._next_id()
await self._col.insert_one(self._to_payload(doc, new_id))
doc.id = new_id
return doc
async def get_by_id(self, id: int) -> BaseModel | None:
"""Fetch one document by integer id.
Args:
id (int): Document identifier.
Returns:
BaseModel | None: The stored document, or ``None`` when absent.
"""
raw = await self._col.find_one({"_id": int(id)})
return self.document_cls.model_validate(raw) if raw else None
async def get_many(self, filter: dict | None = None, **_: Any) -> list[BaseModel]:
"""List documents, optionally filtered by top-level equality.
Args:
filter (dict | None): Equality filter on document fields.
Returns:
list[BaseModel]: Matching documents in insertion order.
"""
cursor = self._col.find(filter or {})
return [self.document_cls.model_validate(raw) async for raw in cursor]
async def update(self, id: int, data: Any) -> BaseModel | None:
"""Replace a document by integer id.
Args:
id (int): Document identifier.
data (Any): Replacement document or field mapping.
Returns:
BaseModel | None: The stored document, or ``None`` when absent.
"""
doc = self._to_doc(data)
payload = self._to_payload(doc, int(id))
result = await self._col.replace_one({"_id": int(id)}, payload)
if result.matched_count == 0:
return None
doc.id = int(id)
return doc
async def patch(self, id: int, data: dict) -> BaseModel | None:
"""Partially update a document by integer id.
Args:
id (int): Document identifier.
data (dict): Fields to merge into the stored document.
Returns:
BaseModel | None: The stored document, or ``None`` when absent.
"""
updates = {k: v for k, v in data.items() if k != "id"}
result = await self._col.update_one({"_id": int(id)}, {"$set": updates})
if result.matched_count == 0:
return None
return await self.get_by_id(id)
async def delete(self, id: int) -> bool:
"""Delete a document by integer id.
Args:
id (int): Document identifier.
Returns:
bool: ``True`` when a document was removed.
"""
result = await self._col.delete_one({"_id": int(id)})
return result.deleted_count > 0
async def delete_many(self, filter: dict | None = None) -> int:
"""Delete every document (optionally filtered by equality).
Args:
filter (dict | None): Equality filter on document fields.
Returns:
int: Number of documents removed.
"""
result = await self._col.delete_many(filter or {})
return result.deleted_count

View File

@@ -1,8 +1,15 @@
"""# Summary
Infrastructure initialization — connects MongoDB and wires all repos and services.
Shared bootstrap for both the FastAPI app and the worker, guarded against
repeated initialization via a module-level flag.
Composition root — connects MongoDB (when selected), wires all repos and
services, and returns the resources dict. Shared bootstrap for the FastAPI
app, the worker and the seed/drop scripts, guarded against repeated
initialization via a module-level flag.
---
The ``memory`` backend skips the Mongo connection entirely — repos are
process-local dictionaries, so a plain ``uvicorn main:app`` runs with zero
external services.
"""
from typing import Any
@@ -18,18 +25,21 @@ _initialized: bool = False
async def init() -> dict[str, Any]:
"""Connect MongoDB, wire all repos and services, and return the resources dict.
"""Wire all repos and services, and return the resources dict.
Idempotent: repeated calls short-circuit and return the cached resources.
Only the ``mongo`` backend performs a network connection here.
Returns:
Dict[str, Any]: Named services keyed by resource name.
Dict[str, Any]: Named services keyed by resource name
(``<resource>_svc`` and ``<resource>_repo``).
"""
global _resources, _initialized
if _initialized:
return _resources
await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)
if settings.repo_backend == "mongo":
await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)
item_repo = create_item_repo()
job_repo = create_job_repo()
@@ -44,8 +54,9 @@ async def init() -> dict[str, Any]:
async def shutdown() -> None:
"""Disconnect MongoDB and reset the resources cache."""
"""Disconnect MongoDB (if connected) and reset the resources cache."""
global _initialized
await shutdown_database()
if settings.repo_backend == "mongo":
await shutdown_database()
_resources.clear()
_initialized = False

View File

@@ -0,0 +1,146 @@
"""# Summary
Shared interface helpers — service resolution, serialization, pagination and
foreign-key expansion used by every route module.
---
FK fields on the wire may arrive either as a bare integer id (from an
``x-fk`` picker) or as an already-expanded object. The wire contract always
*returns* fully expanded objects, so create/update handlers normalize the
input through :func:`expand_ref` before persisting.
"""
from fastapi import HTTPException, Request
def svc(request: Request, name: str):
"""Resolve a CRUD service from app state by resource name.
Args:
request (Request): Incoming request whose app.state carries resources.
name (str): Resource key suffix, e.g. ``"parent"`` → ``parent_svc``.
Returns:
Any: The named CRUD service.
"""
return request.app.state.db_resources[f"{name}_svc"]
def dump(doc) -> dict:
"""Serialize a document to a JSON-ready mapping with an integer id.
Args:
doc (Any): Pydantic document to serialize.
Returns:
dict: JSON-mode mapping (``by_alias`` so ``_id`` maps back to ``id``).
"""
return doc.model_dump(mode="json", by_alias=True)
def paginate(items: list, limit: int, offset: int) -> dict:
"""Slice ``items`` into the ``{total, items}`` pagination envelope.
A ``limit`` of 0 means "all remaining items" (used by react-openapi for
FK option lists and counts).
Args:
items (list): Full result list.
limit (int): Page size; ``0`` returns everything after ``offset``.
offset (int): Number of items to skip.
Returns:
dict: ``{"total": int, "items": list}``.
"""
offset = max(offset, 0)
page = items[offset:] if limit == 0 else items[offset : offset + limit]
return {"total": len(items), "items": page}
async def expand_ref(svc_obj, value, label: str) -> dict | None:
"""Normalize a foreign-key reference into a fully expanded object.
Args:
svc_obj (Any): Target resource CRUD service used for lookups.
value (dict | int | None): Bare id, ``{"id": N}`` stub, or full object.
label (str): Human label used in 422 error messages.
Returns:
dict | None: Expanded object, or ``None`` when ``value`` is ``None``.
Raises:
HTTPException: 422 when the reference cannot be resolved to a document.
"""
if value is None:
return None
if isinstance(value, dict):
if set(value.keys()) == {"id"}:
value = value["id"]
else:
return value
if isinstance(value, int):
doc = await svc_obj.read(value)
if doc is None:
raise HTTPException(status_code=422, detail=f"{label} {value} not found")
return dump(doc)
raise HTTPException(status_code=422, detail=f"invalid {label} reference")
async def expand_ref_list(svc_obj, value, label: str) -> list[dict]:
"""Normalize a list foreign-key reference into expanded objects.
Args:
svc_obj (Any): Target resource CRUD service used for lookups.
value (list | None): List of bare ids, stubs, or full objects.
label (str): Human label used in 422 error messages.
Returns:
list[dict]: Expanded objects.
Raises:
HTTPException: 422 when any reference cannot be resolved.
"""
if value is None:
return []
if not isinstance(value, list):
raise HTTPException(status_code=422, detail=f"invalid {label} list")
results = []
for entry in value:
results.append(await expand_ref(svc_obj, entry, label))
return results
async def read_or_404(svc_obj, id: int, label: str):
"""Read a document by id or raise a 404 HTTPException.
Args:
svc_obj (Any): CRUD service to read from.
id (int): Document identifier.
label (str): Human label used in the 404 detail message.
Returns:
Any: The found document.
Raises:
HTTPException: 404 when no document matches ``id``.
"""
doc = await svc_obj.read(id)
if doc is None:
raise HTTPException(status_code=404, detail=f"{label} not found")
return doc
async def delete_or_404(svc_obj, id: int, label: str) -> None:
"""Delete a document by id or raise a 404 HTTPException.
Args:
svc_obj (Any): CRUD service to delete from.
id (int): Document identifier.
label (str): Human label used in the 404 detail message.
Raises:
HTTPException: 404 when no document matches ``id``.
"""
ok = await svc_obj.delete(id)
if not ok:
raise HTTPException(status_code=404, detail=f"{label} not found")

View File

@@ -2,41 +2,89 @@
Item route handlers — names must match openapi.yaml operationIds.
"""
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi import Request, Response
from domain.item.models import Item
from interfaces._common import delete_or_404, dump, paginate, read_or_404, svc
def svc(request: Request):
"""Resolve the item CRUD service from app state."""
return request.app.state.db_resources["item_svc"]
async def list_items(request: Request, limit: int = 20, offset: int = 0) -> dict:
"""List items (paginated). ``limit=0`` returns all remaining items.
Args:
request (Request): Incoming request.
limit (int): Page size. Defaults to 20.
offset (int): Items to skip. Defaults to 0.
Returns:
dict: ``{"total": int, "items": list[Item]}``.
"""
docs = await svc(request, "item").search({})
return paginate([dump(d) for d in docs], limit, offset)
async def create_item(request: Request, body: dict):
"""Create an item from the request body."""
data = body.model_dump() if hasattr(body, "model_dump") else dict(body)
doc = await svc(request).create(Item(**data))
return JSONResponse(status_code=201, content=doc.model_dump(mode="json", by_alias=True))
async def create_item(request: Request, body: dict, response: Response) -> dict:
"""Create an item.
Args:
request (Request): Incoming request.
body (dict): Item fields (``id`` ignored).
response (Response): Response object; set to 201 on success.
Returns:
dict: The created item.
"""
response.status_code = 201
body.pop("id", None)
doc = await svc(request, "item").create(body)
return dump(doc)
async def get_item(request: Request, id: str):
"""Fetch one item by id."""
doc = await svc(request).read(id)
if doc is None:
return JSONResponse(status_code=404, content={"detail": "item not found"})
return doc.model_dump(mode="json", by_alias=True)
async def get_item(request: Request, id: int) -> dict:
"""Fetch one item by id.
Args:
request (Request): Incoming request.
id (int): Item identifier.
Returns:
dict: The item document.
Raises:
HTTPException: 404 when the item does not exist.
"""
return dump(await read_or_404(svc(request, "item"), id, "item"))
async def list_items(request: Request):
"""List all items."""
docs = await svc(request).search({})
return [d.model_dump(mode="json", by_alias=True) for d in docs]
async def update_item(request: Request, id: int, body: dict) -> dict:
"""Replace an item by id.
Args:
request (Request): Incoming request.
id (int): Item identifier.
body (dict): Full replacement payload.
Returns:
dict: The updated item.
Raises:
HTTPException: 404 when the item does not exist.
"""
await read_or_404(svc(request, "item"), id, "item")
body.pop("id", None)
doc = await svc(request, "item").update(id, body)
return dump(doc)
async def delete_item(request: Request, id: str):
"""Delete one item by id."""
ok = await svc(request).delete(id)
if not ok:
return JSONResponse(status_code=404, content={"detail": "item not found"})
return {"deleted": True}
async def delete_item(request: Request, id: int, response: Response) -> None:
"""Delete an item by id.
Args:
request (Request): Incoming request.
id (int): Item identifier.
response (Response): Response object; set to 204 on success.
Raises:
HTTPException: 404 when the item does not exist.
"""
await delete_or_404(svc(request, "item"), id, "item")
response.status_code = 204

View File

@@ -2,33 +2,89 @@
Job route handlers — names must match openapi.yaml operationIds.
"""
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi import Request, Response
from domain.job.models import Job
from interfaces._common import delete_or_404, dump, paginate, read_or_404, svc
def svc(request: Request):
"""Resolve the job CRUD service from app state."""
return request.app.state.db_resources["job_svc"]
async def list_jobs(request: Request, limit: int = 20, offset: int = 0) -> dict:
"""List jobs (paginated). ``limit=0`` returns all remaining items.
Args:
request (Request): Incoming request.
limit (int): Page size. Defaults to 20.
offset (int): Items to skip. Defaults to 0.
Returns:
dict: ``{"total": int, "items": list[Job]}``.
"""
docs = await svc(request, "job").search({})
return paginate([dump(d) for d in docs], limit, offset)
async def create_job(request: Request, body: dict):
"""Create a job from the request body."""
data = body.model_dump() if hasattr(body, "model_dump") else dict(body)
doc = await svc(request).create(Job(**data))
return JSONResponse(status_code=201, content=doc.model_dump(mode="json", by_alias=True))
async def create_job(request: Request, body: dict, response: Response) -> dict:
"""Create a job.
Args:
request (Request): Incoming request.
body (dict): Job fields (``id`` ignored).
response (Response): Response object; set to 201 on success.
Returns:
dict: The created job.
"""
response.status_code = 201
body.pop("id", None)
doc = await svc(request, "job").create(body)
return dump(doc)
async def get_job(request: Request, id: str):
"""Fetch one job by id."""
doc = await svc(request).read(id)
if doc is None:
return JSONResponse(status_code=404, content={"detail": "job not found"})
return doc.model_dump(mode="json", by_alias=True)
async def get_job(request: Request, id: int) -> dict:
"""Fetch one job by id.
Args:
request (Request): Incoming request.
id (int): Job identifier.
Returns:
dict: The job document.
Raises:
HTTPException: 404 when the job does not exist.
"""
return dump(await read_or_404(svc(request, "job"), id, "job"))
async def list_jobs(request: Request):
"""List all jobs."""
docs = await svc(request).search({})
return [d.model_dump(mode="json", by_alias=True) for d in docs]
async def update_job(request: Request, id: int, body: dict) -> dict:
"""Replace a job by id.
Args:
request (Request): Incoming request.
id (int): Job identifier.
body (dict): Full replacement payload.
Returns:
dict: The updated job.
Raises:
HTTPException: 404 when the job does not exist.
"""
await read_or_404(svc(request, "job"), id, "job")
body.pop("id", None)
doc = await svc(request, "job").update(id, body)
return dump(doc)
async def delete_job(request: Request, id: int, response: Response) -> None:
"""Delete a job by id.
Args:
request (Request): Incoming request.
id (int): Job identifier.
response (Response): Response object; set to 204 on success.
Raises:
HTTPException: 404 when the job does not exist.
"""
await delete_or_404(svc(request, "job"), id, "job")
response.status_code = 204

View File

@@ -7,3 +7,4 @@ re-exports every route handler from the subpackages into one flat namespace.
# ruff: noqa: F403
from interfaces.item.routes import *
from interfaces.job.routes import *
from interfaces.system.routes import *

View File

@@ -0,0 +1,3 @@
"""# Summary
System interfaces."""

View File

@@ -0,0 +1,13 @@
"""# Summary
System route handlers — health probe and other non-resource endpoints.
"""
async def health() -> dict:
"""Liveness probe — public, unauthenticated.
Returns:
dict: ``{"status": "ok", "service": "app-template"}``.
"""
return {"status": "ok", "service": "app-template"}

View File

@@ -3,6 +3,12 @@
FastAPI application bootstrap — uses OpenAPIFirstApp for spec-driven routing.
Handlers are looked up by operationId in interfaces.openapi, so no router tables
are declared here; the lifespan initializes and shuts down app resources.
---
``load_dotenv()`` runs before the app is constructed so ``{AUTH_SERVER}`` in
``openapi.yaml``'s bearer-JWT security scheme resolves from ``.env`` at build
time (openapi_first resolves env placeholders when the spec is loaded).
"""
# ruff: noqa: E402
import logging
@@ -12,8 +18,6 @@ from dotenv import load_dotenv
from openapi_first.app import OpenAPIFirstApp
from starlette.middleware.cors import CORSMiddleware
from init import init, shutdown
load_dotenv()
logging.basicConfig(
@@ -22,6 +26,7 @@ logging.basicConfig(
)
import interfaces.openapi as openapi_routes
from init import init, shutdown
logger = logging.getLogger(__name__)
@@ -47,7 +52,7 @@ async def lifespan(app: OpenAPIFirstApp):
app = OpenAPIFirstApp(
openapi_path="openapi.yaml",
routes_module=openapi_routes,
title="Item API",
title="App Template API",
version="0.1.0",
lifespan=lifespan,
)

View File

@@ -1,161 +1,451 @@
openapi: 3.0.3
openapi: 3.1.0
info:
title: Item API
title: App Template API
version: 0.1.0
paths:
/items:
get:
operationId: list_items
summary: List items
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Item"
post:
operationId: create_item
summary: Create an item
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ItemInput"
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
/items/{id}:
get:
operationId: get_item
summary: Get an item
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
"404":
description: Not found
delete:
operationId: delete_item
summary: Delete an item
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: Deleted
"404":
description: Not found
/jobs:
get:
operationId: list_jobs
summary: List jobs
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Job"
post:
operationId: create_job
summary: Create a job
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/JobInput"
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/Job"
/jobs/{id}:
get:
operationId: get_job
summary: Get a job
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Job"
"404":
description: Not found
description: |
App template API — OpenAPI-first backend pairing with the client template
and the @aetoskia/react-openapi admin panel renderer. Resources are driven
by OpenAPI x- extension fields; auth is bearer JWT introspected against
the auth-server. Item and Job are example domains — replace them with
your own, keeping the x- conventions documented in the react-openapi MCP
bundle (module react-openapi, object specExtensions).
servers:
- url: http://localhost:8000
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-server-url: "{AUTH_SERVER}"
x-introspect-path: "/introspect"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"
schemas:
Item:
type: object
required: [name, quantity]
x-resource: items
x-primary-key: id
x-display-format: "{name} × {quantity}"
x-list-columns: [name, quantity]
properties:
id:
type: string
name:
type: string
quantity:
type: integer
ItemInput:
type: object
required: [name]
properties:
readOnly: true
x-order: 0
x-hidden: { form: true, list: true }
x-label: "ID"
name:
type: string
x-order: 1
x-label: "Name"
x-description: "Item name"
x-filterable: true
x-sortable: true
x-autocomplete: token
quantity:
type: integer
default: 1
x-order: 2
x-label: "Quantity"
x-description: "How many units"
x-sortable: true
required: [id, name]
Job:
type: object
required: [name, status]
x-resource: jobs
x-primary-key: id
x-display-format: "{name} [{status}]"
x-list-columns: [name, status, retry_count]
properties:
id:
type: string
type: integer
readOnly: true
x-order: 0
x-hidden: { form: true, list: true }
x-label: "ID"
name:
type: string
x-order: 1
x-label: "Name"
x-description: "Job name"
x-filterable: true
x-autocomplete: token
status:
type: string
enum: [pending, processing, done, failed]
x-order: 2
x-label: "Status"
x-description: "Lifecycle status"
x-filterable: true
retry_count:
type: integer
readOnly: true
x-order: 3
x-label: "Retries"
error_message:
type: string
readOnly: true
x-order: 4
x-label: "Last Error"
payload:
type: object
JobInput:
x-order: 5
x-label: "Payload"
x-description: "Arbitrary job payload"
required: [id, name, status]
ErrorBody:
type: object
required: [name]
properties:
name:
detail:
type: string
payload:
type: object
required: [detail]
HTTPValidationError:
type: object
properties:
detail:
type: array
items:
$ref: '#/components/schemas/ValidationError'
ValidationError:
type: object
properties:
loc:
type: array
items:
type: string
msg:
type: string
type:
type: string
required: [loc, msg, type]
responses:
Unauthorized:
description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
Forbidden:
description: Insufficient permissions
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
ValidationError:
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
InternalServerError:
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
paths:
/health:
get:
summary: Liveness probe (public, unauthenticated)
operationId: health
security: []
responses:
'200':
description: Service is alive
content:
application/json:
schema:
type: object
properties:
status:
type: string
service:
type: string
/items:
get:
summary: List items (paginated)
operationId: list_items
parameters:
- in: query
name: limit
schema: {type: integer, default: 20}
- in: query
name: offset
schema: {type: integer, default: 0}
responses:
'200':
description: Paginated list of items
content:
application/json:
schema:
type: object
properties:
total:
type: integer
items:
type: array
items:
$ref: '#/components/schemas/Item'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
post:
summary: Create an item
operationId: create_item
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
responses:
'201':
description: Item created
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
/items/{id}:
get:
operationId: get_item
parameters:
- name: id
in: path
required: true
schema: {type: integer}
responses:
'200':
description: Single item
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
put:
operationId: update_item
parameters:
- name: id
in: path
required: true
schema: {type: integer}
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
responses:
'200':
description: Item updated
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
delete:
operationId: delete_item
parameters:
- name: id
in: path
required: true
schema: {type: integer}
responses:
'204':
description: Item deleted
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
/jobs:
get:
summary: List jobs (paginated)
operationId: list_jobs
parameters:
- in: query
name: limit
schema: {type: integer, default: 20}
- in: query
name: offset
schema: {type: integer, default: 0}
responses:
'200':
description: Paginated list of jobs
content:
application/json:
schema:
type: object
properties:
total:
type: integer
items:
type: array
items:
$ref: '#/components/schemas/Job'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
post:
summary: Create a job
operationId: create_job
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
responses:
'201':
description: Job created
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
/jobs/{id}:
get:
operationId: get_job
parameters:
- name: id
in: path
required: true
schema: {type: integer}
responses:
'200':
description: Single job
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
put:
operationId: update_job
parameters:
- name: id
in: path
required: true
schema: {type: integer}
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
responses:
'200':
description: Job updated
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'
delete:
operationId: delete_job
parameters:
- name: id
in: path
required: true
schema: {type: integer}
responses:
'204':
description: Job deleted
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'500':
$ref: '#/components/responses/InternalServerError'

View File

@@ -5,7 +5,7 @@ Item-sync pipeline — the demo pipeline axis of the blueprint.
Runs on the worker, consumes a pending job, and materializes its payload as an
item. Steps are plain async functions here; once the chain branches or needs
deterministic replay, wrap the steps in a dagpipe DAG (see standards
"06_pipeline_scaffold").
"50_pipeline_scaffold").
"""
import logging

View File

@@ -1,5 +1,5 @@
"""# Summary
Shared pipeline scaffolding — ports, loader, and registry reused by every
pipeline family. See standards "06_pipeline_scaffold".
pipeline family. See standards "50_pipeline_scaffold".
"""

View File

@@ -8,14 +8,25 @@ import interfaces.openapi as routes_module
def test_openapi_app_builds():
"""The spec-first app must build and plant each route handler."""
module = importlib.import_module(routes_module.__name__)
for operation in ("create_item", "get_item", "list_items", "delete_item",
"create_job", "get_job", "list_jobs"):
for operation in (
"health",
"list_items",
"create_item",
"get_item",
"update_item",
"delete_item",
"list_jobs",
"create_job",
"get_job",
"update_job",
"delete_job",
):
assert hasattr(module, operation)
app = OpenAPIFirstApp(
openapi_path="openapi.yaml",
routes_module=module,
title="Item API",
title="App Template API",
version="0.1.0",
)
assert app is not None

View File

@@ -7,7 +7,7 @@ the API axis, workers consume them on a fixed cadence.
Notes:
- This process is separate from the API process. Any in-memory event bus is per
process — if the pipeline must surface live progress to the API, swap the bus
adaptor for a broker-backed one (see standards 07_worker_and_progress).
adaptor for a broker-backed one (see standards 51_worker_and_progress).
"""
import asyncio
import logging

View File

@@ -0,0 +1,89 @@
---
kind: pipeline
type: docker
name: build-and-publish-npm
platform:
os: linux
arch: arm64
workspace:
path: /drone/src
steps:
- name: check-version
image: node:22-alpine
environment:
NPM_REPO_URL:
from_secret: NPM_REPO_URL
NPM_USERNAME:
from_secret: NPM_USERNAME
NPM_PASSWORD:
from_secret: NPM_PASSWORD
commands:
- apk add --no-cache curl
- PACKAGE_NAME=$(node -p "require('./package.json').name")
- VERSION=$(node -p "require('./package.json').version")
- |
if [ "$DRONE_TAG" != "$VERSION" ]; then
echo "Tag '$DRONE_TAG' does not match package.json version '$VERSION'; refusing to publish."
exit 1
fi
- echo "registry=$NPM_REPO_URL" > /tmp/.npmrc
- echo "_auth=$(echo -n "$NPM_USERNAME:$NPM_PASSWORD" | base64 | tr -d '\n')" >> /tmp/.npmrc
- echo "Checking if $PACKAGE_NAME@$VERSION exists on $NPM_REPO_URL ..."
- |
if npm view "$PACKAGE_NAME@$VERSION" version --userconfig /tmp/.npmrc >/dev/null 2>&1; then
echo "$PACKAGE_NAME@$VERSION already exists; skipping build."
exit 78
else
echo "New version detected: $PACKAGE_NAME@$VERSION"
fi
- name: quality-gate
image: node:22-alpine
environment:
NPM_REPO_URL:
from_secret: NPM_REPO_URL
NPM_USERNAME:
from_secret: NPM_USERNAME
NPM_PASSWORD:
from_secret: NPM_PASSWORD
commands:
- |
if [ -z "$NPM_REPO_URL" ] || [ -z "$NPM_USERNAME" ] || [ -z "$NPM_PASSWORD" ]; then
echo "NPM_REPO_URL / NPM_USERNAME / NPM_PASSWORD are not set" >&2
exit 1
fi
- echo "registry=$NPM_REPO_URL" > .npmrc
- echo "_auth=$(echo -n "$NPM_USERNAME:$NPM_PASSWORD" | base64 | tr -d '\n')" >> .npmrc
- npm install
- echo "Running quality gate..."
- npm run quality-gate
- name: build-package
image: node:22-alpine
commands:
- npm install
- echo "Building npm package..."
- npm run build
- ls -l dist
- name: publish-to-private-npm
image: node:22-alpine
environment:
NPM_REPO_URL:
from_secret: NPM_REPO_URL
NPM_USERNAME:
from_secret: NPM_USERNAME
NPM_PASSWORD:
from_secret: NPM_PASSWORD
commands:
- echo "registry=$NPM_REPO_URL" > .npmrc
- echo "_auth=$(echo -n "$NPM_USERNAME:$NPM_PASSWORD" | base64 | tr -d '\n')" >> .npmrc
- echo "Publishing to private npm at $NPM_REPO_URL ..."
- npm publish --access restricted
trigger:
event:
- tag

View File

@@ -1,7 +1,7 @@
"""MkDocs hook: rebase cross-kind links to the repository root.
The standards wiki links to the sibling library reference with
source-relative paths such as ``../lib/13_library_registry/``. MkDocs only
source-relative paths such as ``../lib/93_library_registry/``. MkDocs only
rewrites links whose target lives inside ``docs_dir``, so those links are
emitted verbatim and, under ``use_directory_urls``, resolve into
``/standards/wiki/lib/...`` and break.

View File

@@ -6,6 +6,11 @@ The ordered, deterministic checklist for generating a new application of the
standard shape. Every step is fixed; only the resource model (step 2), the
codegen pass (step 3), and the pipeline family (step 8) need app-specific work.
The reference pages behind this recipe are organized as an anatomy: the
domains layer, the OpenAPI contract that drives routing *and* the admin UI,
the interfaces/infra seam, the auto-rendered UI client, and an optional
pipelines layer — see the [Standards Reference](index.md) index.
This page is served by the `standards` MCP as `docs://modules/build_recipe`. The
`start_app` prompt on that server collects the two axes and hands off here; pull
exact files with `get_app_component`.
@@ -21,6 +26,14 @@ cp -r <docs>/standards/src/templates/app apps/<your-app>
cd apps/<your-app>
```
Pair it with the React client template when a UI is needed (auto-rendered
admin from the spec — see [40 – UI](40_ui_admin.md)):
```bash
cp -r <docs>/standards/src/templates/client apps/<your-app>-client
cd apps/<your-app>-client && npm install
```
Install the internal libraries — editable local checkouts (preferred in
development) or the private index (CI / fresh machines). Credentials are
environment-only; never commit them.
@@ -194,4 +207,4 @@ Rules of the road:
## Related
- [02 – Layering Rules](02_layering_rules.md) · [05 – CRUD Template](05_crud_template.md) · [06 – Pipeline Scaffold](06_pipeline_scaffold.md) · [12 – Genericity Matrix](12_genericity_matrix.md)
- [10 – Layering Rules](10_layering_rules.md) · [21 – CRUD Template](21_crud_template.md) · [22 – Auth Integration](22_auth_integration.md) · [40 – UI](40_ui_admin.md) · [50 – Pipeline Scaffold](50_pipeline_scaffold.md) · [92 – Genericity Matrix](92_genericity_matrix.md)

View File

@@ -0,0 +1,86 @@
# UI: Auto-Rendered Admin & Custom Pages
## Summary
The UI axis of an Aetos app is a **published React client**, not code written
per application. Two npm packages carry the whole surface:
| Package | Role |
|---|---|
| `@aetoskia/react-openapi` | Renders a complete admin panel (CRUD tables, forms, filters, sorting, pagination, uploads) **from the app's `openapi.yaml`** — zero UI code. |
| `@aetoskia/react-auth` | Auth contexts and pages (login/register/me) wired to the auth-server, plus the axios client with token handling. |
Both publish to the private registry `https://npm.aetoskia.com`. The server
spec is the single source of truth: the same `openapi.yaml` that drives
`openapi-first` routing drives the admin UI, through **x- extension fields**
(see below).
---
## Scaffold
```bash
cp -r <docs>/standards/src/templates/client apps/<your-app>-client
cd apps/<your-app>-client
npm install # deps come from npm.aetoskia.com via .npmrc
npm run dev
```
The client template is a Vite + MUI app with:
- `src/openapi-config.ts` — points the renderer at the app's spec and the
auth-server's spec;
- `.env.example` — `VITE_API_BASE_URL` (app API) and `VITE_AUTH_BASE_URL`
(auth-server);
- a `Home` page that auto-lists every resource the spec declares, and a
`Header`/`Footer`/theme scaffold.
Custom pages are ordinary React routes layered on top; the auto-rendered admin
is available per resource at `#/resource/<name>`.
---
## x- extensions: how the spec steers the UI
The admin renderer reads a catalog of OpenAPI extension fields (`x-resource`,
`x-list-columns`, `x-display-format`, `x-label`, `x-order`, `x-hidden`,
`x-sortable`, `x-filterable`, `x-autocomplete`, `x-fk`, `x-ui-type`,
`x-upload`, `x-sse`, `x-profile`, and the auth wiring `x-server-url` /
`x-login-path` / `x-me-path` / …) declared on schemas and properties.
**The catalog is canonical in the react-openapi source, not in this doc.**
Read it from the MCP bundle:
```
docs://modules/react-openapi # module: specExtensions — the full catalog
```
The app's own spec carries the per-resource values; the catalog explains what
each field does. The template's `openapi.yaml` shows the convention on the
example `Item` and `Job` schemas.
---
## Golden rules
1. **The spec owns the UI.** Add a resource or rename a column in
`openapi.yaml`; the admin follows. Never fork the renderer per app.
2. **Custom pages for workflows, auto-render for CRUD.** Anything that is
"list / create / edit / delete an entity" is a spec concern; anything that
is a bespoke workflow is a React page.
3. **Auth is consumed, never re-implemented.** `@aetoskia/react-auth` holds
tokens and calls the auth-server endpoints declared by the x- wiring; pages
read the context.
4. **Publish, don't vendor.** UI shared across apps belongs in
`@aetoskia/react-openapi` or `@aetoskia/react-auth` (SemVer, tag-triggered
npm release), not copy-pasted into app trees.
---
## Notes
- The server template ships authless by default (see
[22 – Auth Integration](22_auth_integration.md)); the client template pairs
with it and degrades to "no login" until a security scheme is declared.
- `react/vet` is the full-stack reference implementation: spec with x-
extensions, server, and the consuming client in one tree.

View File

@@ -28,7 +28,7 @@ For internal libraries. The file contains **two pipelines**:
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the version in
`pyproject.toml`; then read `name` / `version` and query
`$PIP_REPO_URL/simple/<package>/`; if that version already exists, exit
`78` and skip the whole build (see [11 – Versioning & Changelog](11_versioning_and_changelog.md)).
`78` and skip the whole build (see [91 – Versioning & Changelog](91_versioning_and_changelog.md)).
- `quality-gate` — install the package with its `[dev]` extras from the
private index, then `black --check`, `ruff check`, `mypy`, `pydoclint`,
`pytest`.
@@ -57,7 +57,7 @@ triggered on `tag`:
- `fetch-tags` — resolve the latest tag into `LATEST_TAG.txt`; fail if there
are no tags.
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the repo-root
`VERSION` file (see [11 – Versioning & Changelog](11_versioning_and_changelog.md)).
`VERSION` file (see [91 – Versioning & Changelog](91_versioning_and_changelog.md)).
- `check-remote-image` — if `aetos/<app>:<tag>` already exists locally, exit
`78` and skip the build.
- `quality-gate` — install from `requirements.txt` and run
@@ -92,7 +92,7 @@ Placeholders to fill in per repo:
index and the running container against duplicate publishes.
- Do not drop the tag↔version guard. A package needs its `pyproject.toml`
version to equal the tag; a service needs a root `VERSION` file that equals
the tag. See [11 – Versioning & Changelog](11_versioning_and_changelog.md).
the tag. See [91 – Versioning & Changelog](91_versioning_and_changelog.md).
- Do not rename the pipelines; tooling and runbooks key off
`build-and-publish-pypi` and `build-and-deploy-service`.
@@ -100,4 +100,4 @@ Placeholders to fill in per repo:
## Related
- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)
- [01 – Build Recipe](01_build_recipe.md) · [30 – Infra Adapters](30_infra_adaptors.md) · [91 – Versioning & Changelog](91_versioning_and_changelog.md) · [93 – Library Registry](93_library_registry.md)

View File

@@ -9,7 +9,7 @@ The **version file is the source of truth**, the **git tag must equal it**, and
a Drone guard enforces that equality before anything is published. Releases are
cut manually; a tag is what triggers the pipeline.
This page pairs with [10 – Drone Pipelines](10_drone_pipelines.md): the version
This page pairs with [90 – Drone Pipelines](90_drone_pipelines.md): the version
and tag contract is exactly what those two CI shapes consume.
---
@@ -178,4 +178,4 @@ Drone guard turns that silent no-op into a hard failure.
## Related
- [10 – Drone Pipelines](10_drone_pipelines.md) · [15 – Repository Inventory](15_repository_inventory.md) · [01 – Build Recipe](01_build_recipe.md) · [13 – Library Registry](13_library_registry.md)
- [90 – Drone Pipelines](90_drone_pipelines.md) · [94 – Repository Inventory](94_repository_inventory.md) · [01 – Build Recipe](01_build_recipe.md) · [93 – Library Registry](93_library_registry.md)

View File

@@ -5,8 +5,8 @@
The canonical list of Aetos repositories covered by the CI and versioning
standards, with the artifact each one produces, its version source, and its
Drone shape. Every entry maps to the contract in
[11 – Versioning & Changelog](11_versioning_and_changelog.md) and the two CI
files in [10 – Drone Pipelines](10_drone_pipelines.md).
[91 – Versioning & Changelog](91_versioning_and_changelog.md) and the two CI
files in [90 – Drone Pipelines](90_drone_pipelines.md).
The same data is published machine-readably as the `registry://repos` MCP
resource (and the `list_repos` tool), sourced from `standards/src/repos.json`.
@@ -68,4 +68,4 @@ merged, and the tag is created on the merged commit.
## Related
- [10 – Drone Pipelines](10_drone_pipelines.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)
- [90 – Drone Pipelines](90_drone_pipelines.md) · [91 – Versioning & Changelog](91_versioning_and_changelog.md) · [93 – Library Registry](93_library_registry.md)

View File

@@ -3,25 +3,55 @@
The reference half of the Standards description: the enforceable rules,
patterns, and the internal library catalog that agents consult while coding.
- **Build Recipe** — the ordered, agentic checklist for a new app.
The reference is organized as an **anatomy** — one entry checklist and four
pillars describing what an application is made of, plus delivery/meta pages
and an optional pipelines layer.
## Anatomy
- **Build Recipe** — the ordered, agentic checklist for a new app; the entry
point that walks the whole anatomy.
## Pillar 1 — Domains
- **Layering Rules** — the strict request → domain → repository layering and
the rules that enforce it.
## Pillar 2 — OpenAPI Spec (the contract)
- **Composition Root** — a single composition point wiring the whole app.
- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.
- **CRUD Template** — the standardized generic resource endpoint scaffold.
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
no auth code).
## Pillar 3 — Interfaces & Infra
- **Infra Adapters** — how infrastructure connects without leaking into domain.
## Pillar 4 — UI
- **UI: Auto-Rendered Admin & Custom Pages** — the `@aetoskia/react-openapi`
admin rendered from the spec's x- extensions, and `@aetoskia/react-auth`
for auth pages; the client template scaffold.
## Pipelines (optional layer)
- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage
jobs.
- **Worker & Progress** — queued work, progress reporting, and visibility.
- **Human-in-the-Loop** — guardrails and approvals inside automations.
- **Infra Adapters** — how infrastructure connects without leaking into domain.
- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.
- **Versioning & Changelog** — SemVer, the tag↔version contract, and the release
changelog.
## Delivery & Meta
- **Drone Pipelines** — the two CI shapes: a PyPI/npm package or a Docker
deployment.
- **Versioning & Changelog** — SemVer, the tag↔version contract, and the
release changelog.
- **Repository Inventory** — every covered repo, its type, version source, and
Drone shape.
- **Genericity Matrix** — what is library-backed, template-provided, or authored.
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
no auth code).
- **Genericity Matrix** — what is library-backed, template-provided, or
authored.
- **Library Registry** — the catalog of internal libraries behind all of it.
Read the **Wiki** for the philosophy and rationale; these pages are the

View File

@@ -68,7 +68,7 @@ When a family needs its own stages, copy `pipelines/example/`:
The shared loader wires the root through the DI library (`hexa.build`) and merges
the pattern fragments; the registry resolves a `(kind, variant, mode)` triple to
a root class. See standards "06_pipeline_scaffold".
a root class. See standards "50_pipeline_scaffold".
## What to keep, what to replace

View File

@@ -5,7 +5,7 @@ Item-sync pipeline — the demo pipeline axis of the blueprint.
Runs on the worker, consumes a pending job, and materializes its payload as an
item. Steps are plain async functions here; once the chain branches or needs
deterministic replay, wrap the steps in a dagpipe DAG (see standards
"06_pipeline_scaffold").
"50_pipeline_scaffold").
"""
import logging

View File

@@ -1,5 +1,5 @@
"""# Summary
Shared pipeline scaffolding — ports, loader, and registry reused by every
pipeline family. See standards "06_pipeline_scaffold".
pipeline family. See standards "50_pipeline_scaffold".
"""

View File

@@ -7,7 +7,7 @@ the API axis, workers consume them on a fixed cadence.
Notes:
- This process is separate from the API process. Any in-memory event bus is per
process — if the pipeline must surface live progress to the API, swap the bus
adaptor for a broker-backed one (see standards 07_worker_and_progress).
adaptor for a broker-backed one (see standards 51_worker_and_progress).
"""
import asyncio
import logging

View File

@@ -76,7 +76,7 @@ only its `ports/` seam reaches into `infra/` to bind adapters.
handlers are looked up by `operationId`. There are no decorator routers.
5. **Reuse the libraries.** Orchestration, DI, Mongo repositories, ingestion,
auth, and docs come from internal libraries — see the [Library
Registry](../lib/13_library_registry/).
Registry](../lib/93_library_registry/).
6. **Explicit state machines.** Long-running work moves through explicit,
durable statuses and can pause for human input.

View File

@@ -22,7 +22,7 @@ Gather these before starting — the `start_app` MCP prompt asks for them:
the families and their input formats (or "none").
4. **Auth needed now?** Default **no**. Auth is deferred (`py-jwt` primitives
only), and an authless app is just a spec with no `security` keys — see
[Spec-First Routing → Authless apps](../lib/04_spec_first_routing/#authless-apps).
[Spec-First Routing → Authless apps](../lib/20_spec_first_routing/#authless-apps).
5. **Integrations.** Mongo, ingestion formats, email, LLM provider.
If you can state axes 1 and 2, the rest is mechanical.
@@ -57,8 +57,8 @@ cp -r <docs>/standards/src/templates/app apps/<your-app>
## 📚 Reference
- [Build Recipe](../lib/01_build_recipe/) — the deterministic checklist
- [Genericity Matrix](../lib/12_genericity_matrix/) — Tier A / B / C
- [Library Registry](../lib/13_library_registry/) — the Tier A catalog
- [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
---

View File

@@ -1,11 +1,11 @@
# Releasing & Versioning
How to cut a release of any Aetos repository. The rule lives in the reference
page [Versioning & Changelog](../lib/11_versioning_and_changelog/); this is the
page [Versioning & Changelog](../lib/91_versioning_and_changelog/); this is the
runbook you follow.
Every repository is one of two Drone shapes — a **PyPI package** or a **Docker
deployment** ([Drone Pipelines](../lib/10_drone_pipelines/)). Both release the
deployment** ([Drone Pipelines](../lib/90_drone_pipelines/)). Both release the
same way: the version bump and changelog promotion go **in the merge request**,
and the tag is created on the merged commit.
@@ -122,4 +122,4 @@ outage):
## Related
- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/11_versioning_and_changelog/) · [Drone Pipelines](../lib/10_drone_pipelines/)
- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/91_versioning_and_changelog/) · [Drone Pipelines](../lib/90_drone_pipelines/)

View File

@@ -9,7 +9,7 @@ portal runs each one as a FastMCP **streamable-http** app bound to
Connect your agent to the **`standards`** server first: it answers
`find_library`, `list_repos`, and the `start_app` prompt, so the agent reaches
for an internal library instead of reimplementing one (see the
[Library Registry](../lib/13_library_registry/)).
[Library Registry](../lib/93_library_registry/)).
---
@@ -356,7 +356,7 @@ Client Configuration → Copy HTTP Stream Config**.
## 📚 Reference
- [Library Registry](../lib/13_library_registry/) — the catalog each server
- [Library Registry](../lib/93_library_registry/) — the catalog each server
mirrors, and where `find_library` resolves
- [Start an App](03_start_an_app.md) — connect `standards`, then invoke
`start_app`
@@ -366,4 +366,4 @@ Client Configuration → Copy HTTP Stream Config**.
## Related
- [Start an App](03_start_an_app.md) · [Testing & Quality](04_development/01_testing.md) · [Library Registry](../lib/13_library_registry/)
- [Start an App](03_start_an_app.md) · [Testing & Quality](04_development/01_testing.md) · [Library Registry](../lib/93_library_registry/)

View File

@@ -41,15 +41,19 @@ the same shape underneath.
| [Testing & Quality](04_development/01_testing.md) | Test layout and acceptance gates |
**Reference** (separate site, mirrors the MCP bundle): the enforceable
rules — [Build Recipe](../lib/01_build_recipe/), [Layering](../lib/02_layering_rules/), [Composition
Root](../lib/03_composition_root/), [Spec-First
Routing](../lib/04_spec_first_routing/), [CRUD
Template](../lib/05_crud_template/), [Pipeline
Scaffold](../lib/06_pipeline_scaffold/), [Worker &
Progress](../lib/07_worker_and_progress/), [Human-in-the-Loop](../lib/08_human_in_loop/),
[Infra Adapters](../lib/09_infra_adaptors/), [Genericity
Matrix](../lib/12_genericity_matrix/), [Library
Registry](../lib/13_library_registry/).
anatomy — start at the [Build Recipe](../lib/01_build_recipe/), then the
pillars: domains ([Layering](../lib/10_layering_rules/)), the OpenAPI
contract ([Composition Root](../lib/02_composition_root/),
[Spec-First Routing](../lib/20_spec_first_routing/), [CRUD
Template](../lib/21_crud_template/), [Auth
Integration](../lib/22_auth_integration/)), interfaces & infra ([Infra
Adapters](../lib/30_infra_adaptors/)), UI ([Auto-Rendered
Admin](../lib/40_ui_admin/)), the optional pipelines layer ([Pipeline
Scaffold](../lib/50_pipeline_scaffold/), [Worker &
Progress](../lib/51_worker_and_progress/), [Human-in-the-Loop](../lib/52_human_in_loop/)),
and delivery/meta ([Genericity
Matrix](../lib/92_genericity_matrix/), [Library
Registry](../lib/93_library_registry/)).
---

View File

@@ -772,7 +772,7 @@ port/adapter binding, performed by the composition root at startup.</p>
<li><strong>Spec-first.</strong> The HTTP contract is declared in <code>openapi.yaml</code>; route
handlers are looked up by <code>operationId</code>. There are no decorator routers.</li>
<li><strong>Reuse the libraries.</strong> Orchestration, DI, Mongo repositories, ingestion,
auth, and docs come from internal libraries — see the <a href="../../lib/13_library_registry/">Library
auth, and docs come from internal libraries — see the <a href="../../lib/93_library_registry/">Library
Registry</a>.</li>
<li><strong>Explicit state machines.</strong> Long-running work moves through explicit,
durable statuses and can pause for human input.</li>

View File

@@ -693,7 +693,7 @@ machine-facing checklist lives in the reference page
the families and their input formats (or "none").</li>
<li><strong>Auth needed now?</strong> Default <strong>no</strong>. Auth is deferred (<code>py-jwt</code> primitives
only), and an authless app is just a spec with no <code>security</code> keys — see
<a href="../../lib/04_spec_first_routing/#authless-apps">Spec-First Routing → Authless apps</a>.</li>
<a href="../../lib/20_spec_first_routing/#authless-apps">Spec-First Routing → Authless apps</a>.</li>
<li><strong>Integrations.</strong> Mongo, ingestion formats, email, LLM provider.</li>
</ol>
<p>If you can state axes 1 and 2, the rest is mechanical.</p>
@@ -721,8 +721,8 @@ machine-facing checklist lives in the reference page
<h2 id="reference">📚 Reference</h2>
<ul>
<li><a href="../../lib/01_build_recipe/">Build Recipe</a> — the deterministic checklist</li>
<li><a href="../../lib/12_genericity_matrix/">Genericity Matrix</a> — Tier A / B / C</li>
<li><a href="../../lib/13_library_registry/">Library Registry</a> — the Tier A catalog</li>
<li><a href="../../lib/92_genericity_matrix/">Genericity Matrix</a> — Tier A / B / C</li>
<li><a href="../../lib/93_library_registry/">Library Registry</a> — the Tier A catalog</li>
<li><a href="../02_components/">App Components</a> — the component model</li>
</ul>
<hr />

View File

@@ -723,10 +723,10 @@
<h1 id="releasing-versioning">Releasing &amp; Versioning</h1>
<p>How to cut a release of any Aetos repository. The rule lives in the reference
page <a href="../../../lib/11_versioning_and_changelog/">Versioning &amp; Changelog</a>; this is the
page <a href="../../../lib/91_versioning_and_changelog/">Versioning &amp; Changelog</a>; this is the
runbook you follow.</p>
<p>Every repository is one of two Drone shapes — a <strong>PyPI package</strong> or a <strong>Docker
deployment</strong> (<a href="../../../lib/10_drone_pipelines/">Drone Pipelines</a>). Both release the
deployment</strong> (<a href="../../../lib/90_drone_pipelines/">Drone Pipelines</a>). Both release the
same way: the version bump and changelog promotion go <strong>in the merge request</strong>,
and the tag is created on the merged commit.</p>
<hr />
@@ -851,7 +851,7 @@ outage):</p>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../01_testing/">Testing &amp; Quality Gates</a> · <a href="../../../lib/11_versioning_and_changelog/">Versioning &amp; Changelog</a> · <a href="../../../lib/10_drone_pipelines/">Drone Pipelines</a></li>
<li><a href="../01_testing/">Testing &amp; Quality Gates</a> · <a href="../../../lib/91_versioning_and_changelog/">Versioning &amp; Changelog</a> · <a href="../../../lib/90_drone_pipelines/">Drone Pipelines</a></li>
</ul>

View File

@@ -738,7 +738,7 @@ portal runs each one as a FastMCP <strong>streamable-http</strong> app bound to
<p>Connect your agent to the <strong><code>standards</code></strong> server first: it answers
<code>find_library</code>, <code>list_repos</code>, and the <code>start_app</code> prompt, so the agent reaches
for an internal library instead of reimplementing one (see the
<a href="../../lib/13_library_registry/">Library Registry</a>).</p>
<a href="../../lib/93_library_registry/">Library Registry</a>).</p>
<hr />
<h2 id="run-the-servers-locally">▶️ Run the servers locally</h2>
<p>From the <code>docs</code> checkout:</p>
@@ -1328,7 +1328,7 @@ Client Configuration → Copy HTTP Stream Config</strong>.</p>
<hr />
<h2 id="reference">📚 Reference</h2>
<ul>
<li><a href="../../lib/13_library_registry/">Library Registry</a> — the catalog each server
<li><a href="../../lib/93_library_registry/">Library Registry</a> — the catalog each server
mirrors, and where <code>find_library</code> resolves</li>
<li><a href="../03_start_an_app/">Start an App</a> — connect <code>standards</code>, then invoke
<code>start_app</code></li>
@@ -1337,7 +1337,7 @@ Client Configuration → Copy HTTP Stream Config</strong>.</p>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../03_start_an_app/">Start an App</a> · <a href="../04_development/01_testing/">Testing &amp; Quality</a> · <a href="../../lib/13_library_registry/">Library Registry</a></li>
<li><a href="../03_start_an_app/">Start an App</a> · <a href="../04_development/01_testing/">Testing &amp; Quality</a> · <a href="../../lib/93_library_registry/">Library Registry</a></li>
</ul>

View File

@@ -718,15 +718,19 @@ Query <code>find_library</code> before writing any infrastructure.</p>
</tbody>
</table>
<p><strong>Reference</strong> (separate site, mirrors the MCP bundle): the enforceable
rules — <a href="../lib/01_build_recipe/">Build Recipe</a>, <a href="../lib/02_layering_rules/">Layering</a>, <a href="../lib/03_composition_root/">Composition
Root</a>, <a href="../lib/04_spec_first_routing/">Spec-First
Routing</a>, <a href="../lib/05_crud_template/">CRUD
Template</a>, <a href="../lib/06_pipeline_scaffold/">Pipeline
Scaffold</a>, <a href="../lib/07_worker_and_progress/">Worker &amp;
Progress</a>, <a href="../lib/08_human_in_loop/">Human-in-the-Loop</a>,
<a href="../lib/09_infra_adaptors/">Infra Adapters</a>, <a href="../lib/12_genericity_matrix/">Genericity
Matrix</a>, <a href="../lib/13_library_registry/">Library
Registry</a>.</p>
anatomy — start at the <a href="../lib/01_build_recipe/">Build Recipe</a>, then the
pillars: domains (<a href="../lib/10_layering_rules/">Layering</a>), the OpenAPI
contract (<a href="../lib/02_composition_root/">Composition Root</a>,
<a href="../lib/20_spec_first_routing/">Spec-First Routing</a>, <a href="../lib/21_crud_template/">CRUD
Template</a>, <a href="../lib/22_auth_integration/">Auth
Integration</a>), interfaces &amp; infra (<a href="../lib/30_infra_adaptors/">Infra
Adapters</a>), UI (<a href="../lib/40_ui_admin/">Auto-Rendered
Admin</a>), the optional pipelines layer (<a href="../lib/50_pipeline_scaffold/">Pipeline
Scaffold</a>, <a href="../lib/51_worker_and_progress/">Worker &amp;
Progress</a>, <a href="../lib/52_human_in_loop/">Human-in-the-Loop</a>),
and delivery/meta (<a href="../lib/92_genericity_matrix/">Genericity
Matrix</a>, <a href="../lib/93_library_registry/">Library
Registry</a>).</p>
<hr />
<h2 id="related-resources">🔗 Related Resources</h2>
<ul>

File diff suppressed because one or more lines are too long