docs(html): update §06 + §08 with verified layer-by-layer instrumentation usage

After the user pushed back on intent-vs-shipped state, I greped each layer
and updated both explainers to reflect actual code, not documented intent.

data-flow-explainer.html §06 — "Tracing & error capture":
- New "Where instrumentation actually lives" table — per-layer breakdown
  of inline span / inline capture / composed-via-wrapper, with a verifiable
  grep showing only repos have inline calls.
- New "The wrapper sandwich" code block showing the actual repo body
  shape next to the bind-time withSpan(withCapture(...)) composition.
- Capture-rules table refined to reflect the R44 fix that just landed:
  use cases capture business-rule errors and output-schema failures
  (not bubbled-from-repo); controllers capture safeParse failures
  (not bubbled-from-use-case); the __sentryReported flag is what makes
  this safe.
- Double-report-guard paragraph now mentions withCapture, SentryLogger,
  and RecordingLogger all check the flag (not just SentryLogger).

di-explainer.html §08 — "Instrumentation symbols":
- Wiring path updated from "withSpan(tracer, ...)" to
  "withSpan(withCapture(...))" to reflect the post-merge wiring.
- New "Two wrappers, applied as a sandwich" table comparing what each
  wrapper does and where it fires; closing note that repos can't use
  the wrapper because they own per-call attributes.

Also bundled: a 1-line aesthetic SVG noise tweak in di-explainer.html
(opacity='0.25', baseFrequency 0.85→0.95) that was sitting in the
working tree before this session — preserved across the Plan 10 merge
via stash/pop and now committed alongside the doc update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 00:43:42 +02:00
parent f0775d6ecc
commit 01707e801b
2 changed files with 126 additions and 11 deletions

View File

@@ -50,7 +50,7 @@ body {
body::before {
content: "";
position: fixed; inset: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><filter id='n'><feTurbulence baseFrequency='0.85' numOctaves='3'/><feColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.07 0 0 0 0 0.05 0 0 0 0 0.10 0'/></filter><rect width='200' height='200' filter='url(%23n)'/></svg>");
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200' opacity='0.25'><filter id='n'><feTurbulence baseFrequency='0.95' numOctaves='3'/><feColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.07 0 0 0 0 0.05 0 0 0 0 0.10 0'/></filter><rect width='200' height='200' filter='url(%23n)'/></svg>");
pointer-events: none;
opacity: 0.45;
mix-blend-mode: multiply;
@@ -1185,11 +1185,32 @@ footer .colophon {
└─ Noop or Sentry binders ← bind to sharedContainer
└─ bindProductionX(config, tracer, logger)
└─ feature container also binds TRACER + LOGGER
└─ withSpan(tracer, ...) at every use case + controller</code></pre>
└─ withSpan(withCapture(...)) at every use case + controller</code></pre>
<p class="trace-p"><strong>Why per-feature containers also get the binding:</strong> repository classes resolve TRACER/LOGGER through the container; controllers and use cases receive instrumentation via the bind-time wrapper instead.</p>
</div>
</div>
<h3 class="trace-h3" style="margin-top: 36px;">Two wrappers, applied as a sandwich</h3>
<p class="trace-p"><code>withSpan</code> and <code>withCapture</code> are higher-order functions that take a <code>(args) =&gt; Promise&lt;R&gt;</code> and return the same shape. The binders compose them: <code>withSpan(withCapture(factory(deps)))</code>. Span is outermost so an errored span's timing reflects the capture-and-rethrow.</p>
<table class="capture-rules">
<thead>
<tr><th>Wrapper</th><th>What it does</th><th>Where it fires</th></tr>
</thead>
<tbody>
<tr>
<td><code>withSpan(tracer, opts, fn)</code></td>
<td>Calls <code>tracer.startSpan(opts, () =&gt; fn(...))</code>. Pure delegation — no error handling of its own; status-on-error logic lives in the tracer impl.</td>
<td>Around every use case + controller, at DI bind time</td>
</tr>
<tr>
<td><code>withCapture(logger, tags, fn)</code></td>
<td>On throw: checks <code>__sentryReported</code>; if not set, calls <code>logger.captureException(err, { tags })</code>, marks the flag, re-throws. If already set, just re-throws.</td>
<td>Around every use case + controller, inside the span wrapper</td>
</tr>
</tbody>
</table>
<p class="trace-p">Repositories are different — they call <code>this.tracer.startSpan</code> + <code>this.logger.captureException</code> inline per method, because they own the per-call attributes (count, IDs, slugs) that the wrapper has no way to know.</p>
</section>
</main>