docs(html): data-flow-explainer §06 tracing + di-explainer §08 instrumentation symbols

Inserts a new §06 "Tracing & error capture" between the existing
"Tradeoffs by part" (§05) and the verdict (which moves to §07).
Includes the trace tree (HTTP → tRPC → controller → use-case → repo
→ Payload), capture-rules table (where captureException fires per
layer + the no-capture rule for defineErrorMiddleware), the
__sentryReported double-report guard explanation, and the R31–R38
PII rule list. Adds matching CSS (.trace-tree / .capture-rules /
.pii-rules) tuned to the existing dark-paper palette and Fraunces
heading font. Updates the contents nav from 6 → 7 columns.

di-explainer.html gets a new §08 "Instrumentation symbols" with cards
for TRACER and LOGGER, the bindAll() Rule-0 wiring path, and a note
on why feature containers also bind the symbols. Two-column grid
collapses to one on narrow viewports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 20:37:46 +02:00
parent 85513ccbf9
commit 0a248d5be0
2 changed files with 180 additions and 5 deletions

View File

@@ -1401,6 +1401,53 @@ footer .colophon {
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.fade-in { animation: fadeIn 0.32s ease; }
/* ─── §06 Tracing & error capture (Plan 10) ─────────────────────────── */
.trace-h3 {
font-family: "Fraunces", serif;
font-weight: 500;
font-size: 1.1rem;
margin: 32px 0 14px;
color: var(--paper);
}
.trace-p {
margin: 0 0 18px;
max-width: 70ch;
}
.trace-tree {
background: var(--ink-2, rgba(0,0,0,.32));
padding: 1.25rem 1.5rem;
border-radius: 4px;
overflow-x: auto;
font-family: "JetBrains Mono", monospace;
font-size: 0.85rem;
line-height: 1.6;
margin: 0 0 18px;
}
.capture-rules {
border-collapse: collapse;
width: 100%;
margin: 0 0 28px;
}
.capture-rules th, .capture-rules td {
padding: 0.6rem 0.85rem;
text-align: left;
border-bottom: 1px solid var(--rule, rgba(255,255,255,.12));
}
.capture-rules th {
font-family: "Fraunces", serif;
font-weight: 500;
}
.pii-rules {
display: grid;
gap: 0.5rem;
padding-left: 1.2rem;
margin: 0 0 14px;
}
.pii-rules li {
font-size: 0.95rem;
}
/* ─── Responsive (basic) ─────────────────────────────────────────────── */
@media (max-width: 1100px) {
@@ -1427,13 +1474,14 @@ footer .colophon {
<div class="contents-row">
<div class="contents-label">Contents</div>
<ol class="contents" style="grid-template-columns: repeat(6, 1fr);">
<ol class="contents" style="grid-template-columns: repeat(7, 1fr);">
<li><a href="#anatomy"><span class="num">01</span>Feature anatomy</a></li>
<li><a href="#flow"><span class="num">02</span>Request flow</a></li>
<li><a href="#di"><span class="num">03</span>Dependency injection</a></li>
<li><a href="#contracts"><span class="num">04</span>Mocks, contracts &amp; factories</a></li>
<li><a href="#tradeoffs"><span class="num">05</span>Tradeoffs by part</a></li>
<li><a href="#verdict"><span class="num">06</span>The verdict</a></li>
<li><a href="#tracing"><span class="num">06</span>Tracing &amp; error capture</a></li>
<li><a href="#verdict"><span class="num">07</span>The verdict</a></li>
</ol>
</div>
</header>
@@ -2305,10 +2353,71 @@ footer .colophon {
</div>
</section>
<!-- ─── 06 ─ VERDICT ─────────────────────────────────────────────────── -->
<section id="verdict" style="border-bottom: 0;">
<!-- ─── 06 ─ TRACING & ERROR CAPTURE ─────────────────────────────────── -->
<section id="tracing">
<div class="section-head">
<div class="section-num">§ 06</div>
<div>
<h2 class="section-title">Tracing &amp; <em>error capture</em>.</h2>
<p class="section-blurb">Every request produces a nested span tree: tRPC procedure → controller → use case → repository → Payload op. Errors are captured at the throw site closest to the cause, never at the boundary that translates them.</p>
</div>
</div>
<h3 class="trace-h3">The trace tree (one tRPC request)</h3>
<pre class="trace-tree"><code>HTTP transaction (auto, @sentry/nextjs)
└── tRPC procedure span (auto, sentry trpc integration)
└── controller span (op="controller", DI-wrapped)
└── use-case span (op="use-case", DI-wrapped)
└── repository span (op="repository", explicit startSpan)
└── Payload Local API call (auto, @sentry/node http)</code></pre>
<h3 class="trace-h3">Capture rules (where <code>captureException</code> fires)</h3>
<table class="capture-rules">
<thead>
<tr><th>Layer</th><th>Captures</th><th>Doesn't capture</th></tr>
</thead>
<tbody>
<tr>
<td>Repository</td>
<td>Infra / Payload errors that originate here</td>
<td>Bubbled errors</td>
</tr>
<tr>
<td>Use case</td>
<td>Business-rule violations originated in this body</td>
<td>Errors from repos (already captured)</td>
</tr>
<tr>
<td>Controller</td>
<td><code>InputParseError</code> from safeParse failure</td>
<td>Anything else</td>
</tr>
<tr>
<td><code>defineErrorMiddleware</code></td>
<td><strong>Nothing</strong> — maps domain → TRPCError only</td>
<td></td>
</tr>
</tbody>
</table>
<h3 class="trace-h3">Double-report guard</h3>
<p class="trace-p">Every error captured by <code>SentryLogger.captureException</code> gets a non-enumerable <code>__sentryReported = true</code> property. A second capture call for the same error returns early. This means each error surfaces in Sentry exactly once, regardless of how many layers it passes through.</p>
<h3 class="trace-h3">PII rules (R31R38, non-negotiable)</h3>
<ul class="pii-rules">
<li><code>sendDefaultPii: false</code> — every <code>Sentry.init()</code>. CI grep gate.</li>
<li>Replay <strong>default-masks all text + inputs + media</strong>. Allowlist starts empty.</li>
<li><code>beforeSend</code> scrubber strips email / password / token / cookie / authorization / ipaddress keys (substring match).</li>
<li><code>beforeSendTransaction</code> scrubber strips PII query params from URLs.</li>
<li><code>setUser</code> accepts only <code>{ id }</code>. Stripping wrapper warns in dev when other keys passed.</li>
<li>IPv4/IPv6 in event payload string values redacted to <code>[redacted-ip]</code>.</li>
</ul>
</section>
<!-- ─── 07 ─ VERDICT ─────────────────────────────────────────────────── -->
<section id="verdict" style="border-bottom: 0;">
<div class="section-head">
<div class="section-num">§ 07</div>
<div>
<h2 class="section-title">Do we <em>need</em> them?</h2>
</div>