Six docs lagged after the post-merge R44 fix added withCapture + reported-flag.ts. They mentioned withSpan only and described the capture story as if it were inline in use-case / controller bodies. This commit aligns them with what shipped. CLAUDE.md (Key Conventions): - "Spans applied at DI bind time" → "Spans + capture composed at DI bind time" with the withSpan(withCapture(factory)) sandwich and the outermost-span ordering note. - "Capture at throw sites only" expanded to mention the __sentryReported flag, the three flag-checking sites (withCapture, SentryLogger, RecordingLogger), and where the helper lives. AGENTS.md (Instrumentation conventions): - Use case + controller wrapping example shows the full sandwich. - Capture-rules table now explicitly says "via withCapture" for use cases and controllers, and "flag set, withCapture bails" for the bubbled cases. packages/core-shared/AGENTS.md: - "with-span.ts" entry split into a paired with-span + with-capture block, including the actual sandwich code. - New entry for reported-flag.ts explaining the helper and why RecordingLogger inlines the check (boundary rule). - Barrel re-export list updated. docs/architecture/vertical-feature-spec.md (§16): - The bind-production line now describes the withSpan(withCapture(...)) sandwich, the outermost-span rationale, and the bubbled-error bail. docs/architecture/dependency-flow.md (TRACER/LOGGER subsection): - bindAll diagram updated: real repo line annotates inline calls; use case + controller lines show withSpan(withCapture(...)). docs/guides/tdd-workflow.md (Asserting spans and captures): - Direct-injection example shows the binder sandwich. - Capture-assertion example explains the flag-bail behaviour and links to the new tests/r44-no-double-capture.test.ts e2e example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.5 KiB
AGENTS.md — core-shared
Generic, reusable primitives with zero business knowledge. This package is the foundation for all other packages and exports utilities, Payload field/hook definitions, and tRPC initialization.
Responsibilities
- Generic primitives — environment helpers, date utilities, type guards
- Payload utilities — field definitions (slug, SEO), blocks (CTA), access controls (is-admin), hooks (slugify, publish timestamp)
- tRPC platform —
initTRPC.create(), shared context factory, procedure builders - No business domain knowledge — no awareness of articles, users, media, or any feature
Must NOT import
- Any feature package (
@repo/auth,@repo/blog, etc.) - Any app package
- Framework-specific code (Next.js, TanStack React Query)
Public exports
From package.json:
.— all utilities, Payload exports, tRPC init./payload— Payload field/hook/block utilities only./trpc/init— tRPCinitTRPCinstance + builders. Plan 9: also exportst(the rawinitTRPC.create({...})instance) so feature packages can build their own procedures viat.procedure.use(...)./trpc/context— tRPC context factory only./trpc/define-error-middleware— Plan 9: factory that builds a tRPC middleware translating domain errors toTRPCError. TakesReadonlyArray<readonly [ErrorCtor, TRPC_ERROR_CODE_KEY]>tuples; usesinstanceofdiscrimination; preserves the original error as.cause. Owned by features: each feature passes its own constructors in viaintegrations/api/procedures.ts. core-shared never enumerates feature-specific error classes — this stays boundary-clean
Test conventions
- Tests colocated:
src/lib/slug-field.ts→src/lib/slug-field.test.ts - Vitest environment:
node - Alias:
@/resolves tosrc/ - Run:
pnpm test --filter @repo/core-shared
Covered areas:
- Slug field generation + validation
- Payload hooks (publish-at timestamp, slugify-if-missing)
- Access control helpers
src/instrumentation/
Two interfaces: ITracer (in tracer.interface.ts) and ILogger (in logger.interface.ts).
Three implementation pairs:
NoopTracer/NoopLogger— pass-through. Default everywhere.SentryTracer/SentryLogger— adapters over@sentry/nextjs. Live insentry/subfolder. Thesentry/subfolder is the only path inpackages/permitted to import@sentry/*(R40), with the additional exception ofinstrumentation/di/bind-sentry-instrumentation.{ts,test.ts}.RecordingTracer/RecordingLogger— in@repo/core-testing/instrumentation, not here.
with-span.ts + with-capture.ts: two higher-order helpers used at DI binding time to wrap use case + controller factory results. The binders apply them as a sandwich — withSpan outermost, withCapture between span and factory:
const wrapped = withSpan(
tracer,
{ name: "blog.getArticles", op: "use-case" },
withCapture(
logger,
{ feature: "blog", layer: "use-case", name: "blog.getArticles" },
factory(deps),
),
);
withSpan is pure delegation to tracer.startSpan. withCapture catches thrown errors, calls logger.captureException(err, { tags }), marks the __sentryReported flag, and re-throws — but bails if the flag is already set so the inner-most layer wins.
reported-flag.ts: small module exporting markReported(err) and isReported(err). Used by withCapture and SentryLogger. RecordingLogger carries an inlined copy (tooling → core import is disallowed by the boundary rule).
Symbols: INSTRUMENTATION_SYMBOLS.TRACER, INSTRUMENTATION_SYMBOLS.LOGGER (both Symbol.for(...) so cross-realm equality holds).
sentry/scrub.ts: PII scrubbers used by every Sentry.init() call across the monorepo. Substring-based key matching catches derived names (userEmail, accessToken, apiKey, ipAddress). IPv4/IPv6 are also redacted from string values via the [redacted-ip] token.
sentry/init-server.ts + init-client.ts: centralized init helpers (Next.js flavor) that hard-code R31 (sendDefaultPii: false), R32/R33 (scrubbers), R34/R35 (replay mask flags), R37 (sample-rate defaults). Apps call these from instrumentation.ts / instrumentation-client.ts.
sentry/init-server-node.ts + init-client-react.ts: Vite/non-Next variants used by apps/web-tanstack. Same R31/R32/R33/R34/R35/R37 posture; uses @sentry/node + @sentry/react instead of @sentry/nextjs.
di/bind-noop-instrumentation.ts + bind-sentry-instrumentation.ts: bind TRACER + LOGGER symbols to a Container. Returns the resolved instances so callers can use them without container lookup.
Subpath exports (package.json#exports):
./instrumentation— barrel (interfaces + Noops + withSpan + withCapture + reported-flag helpers + symbols + binders + node/react init helpers)./instrumentation/sentry/init-server— Next.js server init helper./instrumentation/sentry/init-client— Next.js client init helper./instrumentation/sentry/init-server-node—@sentry/nodeserver init (TanStack Start)./instrumentation/sentry/init-client-react—@sentry/reactclient init (TanStack Start)./instrumentation/sentry/scrub—beforeSend/beforeSendTransaction(used by per-app PII test)
Boundaries:
core-shared/instrumentation/sentry/**MAY import from@sentry/*.- Everything else in
packages/core-shared/src/MUST NOT. - The eslint rule in
core-eslint/base.jsenforces the broader monorepo boundary (R40).