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:
@@ -1401,6 +1401,53 @@ footer .colophon {
|
|||||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
.fade-in { animation: fadeIn 0.32s ease; }
|
.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) ─────────────────────────────────────────────── */
|
/* ─── Responsive (basic) ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
@@ -1427,13 +1474,14 @@ footer .colophon {
|
|||||||
|
|
||||||
<div class="contents-row">
|
<div class="contents-row">
|
||||||
<div class="contents-label">Contents</div>
|
<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="#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="#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="#di"><span class="num">03</span>Dependency injection</a></li>
|
||||||
<li><a href="#contracts"><span class="num">04</span>Mocks, contracts & factories</a></li>
|
<li><a href="#contracts"><span class="num">04</span>Mocks, contracts & factories</a></li>
|
||||||
<li><a href="#tradeoffs"><span class="num">05</span>Tradeoffs by part</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 & error capture</a></li>
|
||||||
|
<li><a href="#verdict"><span class="num">07</span>The verdict</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -2305,10 +2353,71 @@ footer .colophon {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ─── 06 ─ VERDICT ─────────────────────────────────────────────────── -->
|
<!-- ─── 06 ─ TRACING & ERROR CAPTURE ─────────────────────────────────── -->
|
||||||
<section id="verdict" style="border-bottom: 0;">
|
<section id="tracing">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<div class="section-num">§ 06</div>
|
<div class="section-num">§ 06</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="section-title">Tracing & <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 (R31–R38, 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>
|
<div>
|
||||||
<h2 class="section-title">Do we <em>need</em> them?</h2>
|
<h2 class="section-title">Do we <em>need</em> them?</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -641,11 +641,43 @@ footer .colophon {
|
|||||||
font-size: 10.5px; color: var(--ink-3); letter-spacing: 0.04em;
|
font-size: 10.5px; color: var(--ink-3); letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── §08 Instrumentation symbols (Plan 10) ──────────────────────── */
|
||||||
|
|
||||||
|
.instrumentation-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 36px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.trace-h3 {
|
||||||
|
font-family: "Fraunces", serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
margin: 24px 0 12px;
|
||||||
|
color: var(--paper);
|
||||||
|
}
|
||||||
|
.trace-h3:first-child { margin-top: 0; }
|
||||||
|
.trace-p {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
max-width: 60ch;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.trace-tree {
|
||||||
|
background: var(--ink-2, rgba(0,0,0,.32));
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
margin: 0 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Responsive ─────────────────────────────────────────────────── */
|
/* ─── Responsive ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.frame { padding: 0 32px; }
|
.frame { padding: 0 32px; }
|
||||||
.cast-grid, .bindings-grid, .modes-state-grid, .bypass-grid { grid-template-columns: 1fr; }
|
.cast-grid, .bindings-grid, .modes-state-grid, .bypass-grid, .instrumentation-grid { grid-template-columns: 1fr; }
|
||||||
ol.contents { grid-template-columns: repeat(2, 1fr); }
|
ol.contents { grid-template-columns: repeat(2, 1fr); }
|
||||||
.title-block { grid-template-columns: 1fr; gap: 32px; }
|
.title-block { grid-template-columns: 1fr; gap: 32px; }
|
||||||
.conditions-head, .conditions-row { grid-template-columns: 1fr; gap: 4px; }
|
.conditions-head, .conditions-row { grid-template-columns: 1fr; gap: 4px; }
|
||||||
@@ -673,6 +705,7 @@ footer .colophon {
|
|||||||
<li><a href="#modes"><span class="num">05</span>Three modes</a></li>
|
<li><a href="#modes"><span class="num">05</span>Three modes</a></li>
|
||||||
<li><a href="#conditions"><span class="num">06</span>Conditions</a></li>
|
<li><a href="#conditions"><span class="num">06</span>Conditions</a></li>
|
||||||
<li><a href="#tests"><span class="num">07</span>Tests bypass</a></li>
|
<li><a href="#tests"><span class="num">07</span>Tests bypass</a></li>
|
||||||
|
<li><a href="#instrumentation"><span class="num">08</span>Instrumentation symbols</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -1126,6 +1159,39 @@ footer .colophon {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── 08 ─ INSTRUMENTATION SYMBOLS (Plan 10) ────────────────────────── -->
|
||||||
|
<section id="instrumentation">
|
||||||
|
<div class="section-head">
|
||||||
|
<div class="section-num">§ 08</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="section-title">Instrumentation <em>symbols</em>.</h2>
|
||||||
|
<p class="section-blurb">Plan 10 added two new symbols to the per-feature container — <code>TRACER</code> and <code>LOGGER</code> — bound by a separate Rule 0 in <code>bindAll()</code> that's <strong>orthogonal</strong> to the repo binding mode. The DSN env var decides Sentry vs Noop; <code>USE_DEV_SEED</code> / <code>NODE_ENV</code> decide real vs mock repos.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="instrumentation-grid">
|
||||||
|
<div>
|
||||||
|
<h3 class="trace-h3"><code>INSTRUMENTATION_SYMBOLS.TRACER</code></h3>
|
||||||
|
<p class="trace-p">Bound by either <code>bindNoopInstrumentation</code> or <code>bindSentryInstrumentation</code> to <code>NoopTracer</code> or <code>SentryTracer</code>. Decided by Rule 0: DSN env present → Sentry; otherwise Noop.</p>
|
||||||
|
|
||||||
|
<h3 class="trace-h3"><code>INSTRUMENTATION_SYMBOLS.LOGGER</code></h3>
|
||||||
|
<p class="trace-p">Same rule, same lifecycle. <code>NoopLogger</code> in the absence of a DSN; <code>SentryLogger</code> when DSN is set. The Sentry adapter applies the <code>__sentryReported</code> double-report guard internally — call sites don't manage the flag.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="trace-h3">Wiring path</h3>
|
||||||
|
<pre class="trace-tree"><code>bindAll()
|
||||||
|
└─ resolveInstrumentation() ← Rule 0 (DSN check)
|
||||||
|
└─ 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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="frame">
|
<footer class="frame">
|
||||||
|
|||||||
Reference in New Issue
Block a user