docs(architecture): refresh explainers and spec to the shipped system
Some checks failed
CI / typecheck + lint + boundaries + test + build (push) Has been cancelled
CI / Playwright e2e (push) Has been cancelled
CI / Storybook smoke tests + visual regression (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Coverage snapshot / snapshot (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Sentry PII guard (R31) / pii-guard (push) Has been cancelled
Mutation testing (nightly) / mutate (push) Has been cancelled
Library trace revalidation (weekly) / revalidate (push) Has been cancelled

Bring docs/architecture/ in line with the current repo:

- feature-conformance-explainer.html: drop the "proposed / not yet
  implemented" framing — the system is shipped. Four enforcement points
  become five (adds `pnpm fallow` as the whole-codebase audit). Manifest
  playground shows `coverage`, `analyticsEvents`, `rateLimit`,
  `requiresConsent`. Milestone / anchor / open-question sections kept
  but marked historical.
- agent-first-workflow-and-conformance.md: four → five enforcement
  layers; layer table gains the Fallow row.
- di-explainer.html: bind-production sample rewritten to show
  wireUseCase() + assertFeatureConformance() + the full wrapper stack
  (span → capture → audit? → analytics? → consent? → rateLimit?).
- data-flow-explainer.html: same bind-production refresh for the
  data-flow narrative.
- audit-and-compliance-explainer.html: AuditAction enum 6 → 10 values
  (CONSENT_GRANT / WITHDRAW / RESTRICT / UNRESTRICT);
  BindProductionContext example gains analytics, consentFactory,
  rateLimit.
- vertical-feature-spec.md: §5 layout lists the 8 optional cores plus
  core-testing; §9.5 hedges the turbo.json snippet against the live
  file; §10.4 drops the dated "360 tests" metric for the ADR-020
  coverage architecture; §11 gains a historical lead-in pointing at
  docs/decisions/ as the canonical 25-ADR set.
This commit is contained in:
2026-05-23 14:06:03 +02:00
parent b455ae8018
commit 0748f9e5ed
6 changed files with 251 additions and 51 deletions

View File

@@ -1703,16 +1703,56 @@ footer .colophon {
<h3>blog/di/<em>bind-production.ts</em></h3>
<p>Called from each app's bootstrap (<code>apps/web-next/src/server/bind-production.ts</code>) with the <code>ctx</code> object built once by the aggregator. <code>BindProductionContext</code> is imported from <code>@repo/core-shared/di</code>.</p>
<pre class="code" data-lang="typescript // packages/blog/src/di/bind-production.ts"><span class="k">export function</span> <span class="n">bindProductionBlog</span>(<span class="n">ctx</span>: <span class="t">BindProductionContext</span>): <span class="t">void</span> {
<span class="c">// bus, realtime, realtimeRegistry, auditLog are optional — present only when the corresponding optional package is scaffolded</span>
<span class="k">const</span> { <span class="n">config</span>, <span class="n">tracer</span>, <span class="n">logger</span>, <span class="n">bus</span>, <span class="n">queue</span>, <span class="n">realtime</span>, <span class="n">realtimeRegistry</span>, <span class="n">auditLog</span> } = <span class="n">ctx</span>;
<span class="c">// bus, realtime, realtimeRegistry, auditLog are optional — present only when</span>
<span class="c">// the corresponding optional package is scaffolded</span>
<span class="k">const</span> { <span class="n">config</span>, <span class="n">tracer</span>, <span class="n">logger</span>, <span class="n">bus</span>, <span class="n">queue</span> } = <span class="n">ctx</span>;
<span class="c">// 1) Swap the mock repo for the real Payload-backed impl</span>
<span class="k">if</span> (<span class="n">blogContainer</span>.<span class="n">isBound</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IArticlesRepository</span>)) {
<span class="n">blogContainer</span>.<span class="n">unbind</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IArticlesRepository</span>);
}
<span class="n">blogContainer</span>
.<span class="n">bind</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IArticlesRepository</span>)
.<span class="n">toConstantValue</span>(<span class="k">new</span> <span class="t">ArticlesRepository</span>(<span class="n">config</span>));
<span class="c">// Use cases + controllers stay untouched.</span>
<span class="c">// They'll resolve through the new repo automatically.</span>
<span class="k">const</span> <span class="n">repo</span> = <span class="k">new</span> <span class="t">ArticlesRepository</span>(<span class="n">config</span>, <span class="n">tracer</span>, <span class="n">logger</span>);
<span class="n">blogContainer</span>.<span class="n">bind</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IArticlesRepository</span>).<span class="n">toConstantValue</span>(<span class="n">repo</span>);
<span class="c">// 2) Wire each use case via wireUseCase — composes</span>
<span class="c">// withSpan → withCapture → withAudit → withAnalytics → withConsent → withRateLimit → factory(deps)</span>
<span class="c">// and binds the brand-stacked closure to the symbol. One call per use case.</span>
<span class="k">const</span> <span class="n">wrappedGetArticles</span> = <span class="n">wireUseCase</span>({
<span class="n">container</span>: <span class="n">blogContainer</span>,
<span class="n">symbol</span>: <span class="n">BLOG_SYMBOLS</span>.<span class="n">IGetArticlesUseCase</span>,
<span class="n">factory</span>: <span class="n">getArticlesUseCase</span>,
<span class="n">deps</span>: [<span class="n">repo</span>],
<span class="n">feature</span>: <span class="s">"blog"</span>, <span class="n">layer</span>: <span class="s">"use-case"</span>, <span class="n">name</span>: <span class="s">"getArticles"</span>,
<span class="n">tracer</span>, <span class="n">logger</span>,
});
<span class="c">// ... wireUseCase calls for the other use cases ...</span>
<span class="c">// 3) Wrap each controller — controllers still use withSpan + withCapture manually,</span>
<span class="c">// because they take the already-wired use case as their dep.</span>
<span class="k">if</span> (<span class="n">blogContainer</span>.<span class="n">isBound</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IGetArticlesController</span>)) {
<span class="n">blogContainer</span>.<span class="n">unbind</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IGetArticlesController</span>);
}
<span class="n">blogContainer</span>.<span class="n">bind</span>(<span class="n">BLOG_SYMBOLS</span>.<span class="n">IGetArticlesController</span>).<span class="n">toConstantValue</span>(
<span class="n">withSpan</span>(<span class="n">tracer</span>, { <span class="n">name</span>: <span class="s">"blog.getArticles"</span>, <span class="n">op</span>: <span class="s">"controller"</span> },
<span class="n">withCapture</span>(<span class="n">logger</span>, { <span class="n">feature</span>: <span class="s">"blog"</span>, <span class="n">layer</span>: <span class="s">"controller"</span>, <span class="n">name</span>: <span class="s">"blog.getArticles"</span> },
<span class="n">getArticlesController</span>(<span class="n">wrappedGetArticles</span>),
),
),
);
<span class="c">// bus + queue are passed through; generated event/job handlers consume them at the anchors below.</span>
<span class="k">void</span> <span class="n">bus</span>; <span class="k">void</span> <span class="n">queue</span>;
<span class="c">// &lt;gen:event-handlers&gt;</span>
<span class="c">// &lt;gen:jobs&gt;</span>
<span class="c">// 4) Self-asserting tail — refuses to boot if any use-case binding is missing a required brand</span>
<span class="c">// (withSpan / withCapture / withAudit / withAnalytics / withConsent / withRateLimit) or drifts from the manifest.</span>
<span class="n">assertFeatureConformance</span>(
<span class="n">blogContainer</span>,
<span class="n">blogManifest</span>,
{ <span class="n">getArticles</span>: <span class="n">BLOG_SYMBOLS</span>.<span class="n">IGetArticlesUseCase</span>, <span class="c">/* … */</span> },
<span class="n">ctx</span>,
);
}</pre>
</div>
</div>