diff --git a/docs/architecture/data-flow-explainer.html b/docs/architecture/data-flow-explainer.html index 306e520..8f6752c 100644 --- a/docs/architecture/data-flow-explainer.html +++ b/docs/architecture/data-flow-explainer.html @@ -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 {
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.
+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)
+
+ captureException fires)| Layer | Captures | Doesn't capture |
|---|---|---|
| Repository | +Infra / Payload errors that originate here | +Bubbled errors | +
| Use case | +Business-rule violations originated in this body | +Errors from repos (already captured) | +
| Controller | +InputParseError from safeParse failure |
+ Anything else | +
defineErrorMiddleware |
+ Nothing — maps domain → TRPCError only | +— | +
Every error captured by SentryLogger.captureException gets a non-enumerable __sentryReported = true 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.
sendDefaultPii: false — every Sentry.init(). CI grep gate.beforeSend scrubber strips email / password / token / cookie / authorization / ipaddress keys (substring match).beforeSendTransaction scrubber strips PII query params from URLs.setUser accepts only { id }. Stripping wrapper warns in dev when other keys passed.[redacted-ip].Plan 10 added two new symbols to the per-feature container — TRACER and LOGGER — bound by a separate Rule 0 in bindAll() that's orthogonal to the repo binding mode. The DSN env var decides Sentry vs Noop; USE_DEV_SEED / NODE_ENV decide real vs mock repos.
INSTRUMENTATION_SYMBOLS.TRACERBound by either bindNoopInstrumentation or bindSentryInstrumentation to NoopTracer or SentryTracer. Decided by Rule 0: DSN env present → Sentry; otherwise Noop.
INSTRUMENTATION_SYMBOLS.LOGGERSame rule, same lifecycle. NoopLogger in the absence of a DSN; SentryLogger when DSN is set. The Sentry adapter applies the __sentryReported double-report guard internally — call sites don't manage the flag.
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
+
+ Why per-feature containers also get the binding: repository classes resolve TRACER/LOGGER through the container; controllers and use cases receive instrumentation via the bind-time wrapper instead.
+