# 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` — tRPC `initTRPC` instance + builders; also exports `t` (the raw `initTRPC.create({...})` instance) so feature packages can build their own procedures via `t.procedure.use(...)` - `./trpc/context` — tRPC context factory only - `./trpc/define-error-middleware` — factory that builds a tRPC middleware translating domain errors to `TRPCError`. Takes `ReadonlyArray` tuples; uses `instanceof` discrimination; preserves the original error as `.cause`. **Owned by features:** each feature passes its own constructors in via `integrations/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 to `src/` - 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/ **Substrate:** OpenTelemetry SDK (ADR-017). Sentry is the exporter via `@sentry/opentelemetry`. Feature packages depend only on the interfaces below — no Sentry or OTel SDK imports. **Three interfaces:** `ITracer` (`tracer.interface.ts`), `ILogger` (`logger.interface.ts`), `IMetrics` (`metrics.interface.ts`). **Three implementation pairs:** - `NoopTracer` / `NoopLogger` / `NoopMetrics` — pass-through. Default everywhere. - `OtelTracer` / `OtelLogger` / `OtelMetrics` — emit via OTel API (`@opentelemetry/api`, `@opentelemetry/api-logs`). Live in `otel/` subfolder. **The `otel/` subfolder is the only path in `packages/` permitted to import `@opentelemetry/sdk-*` or `@sentry/opentelemetry`** (R52). - `RecordingTracer` / `RecordingLogger` / `RecordingMetrics` — 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: ```ts 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 `OtelLogger`. `RecordingLogger` carries an inlined copy (tooling → core import is disallowed by the boundary rule). **Symbols:** `INSTRUMENTATION_SYMBOLS.ITracer`, `INSTRUMENTATION_SYMBOLS.ILogger`, `INSTRUMENTATION_SYMBOLS.IMetrics` (all `Symbol.for(...)` so cross-realm equality holds). **`otel/pii-fields.ts`:** vendor-neutral PII substring list (`PII_KEY_SUBSTRINGS`, `PII_QUERY_PARAM_SUBSTRINGS`). Used by `otel/pii-scrub-processor.ts` (server) and imported by `sentry/init-client*.ts` (browser). **`otel/pii-scrub-processor.ts`:** `PiiScrubSpanProcessor` + `PiiScrubLogRecordProcessor`. Registered FIRST in the OTel processor chain so all downstream exporters (Sentry) see scrubbed data. Replaces Sentry's `beforeSend`/`beforeSendTransaction` hooks on the server side (ADR-017 §7). **`sentry/init-server.ts` + `sentry/init-client.ts`:** centralized init helpers (Next.js flavor). `init-server.ts` calls `Sentry.init` with `sendDefaultPii: false` (R31) — no `beforeSend` hook (PII scrubbed at OTel layer). `init-client.ts` retains `beforeSend`/`beforeSendTransaction` because browser does not use the OTel pipeline. **`sentry/init-server-node.ts` + `sentry/init-client-react.ts`:** Vite/non-Next variants used by `apps/web-tanstack`. Same posture as their Next.js counterparts. **`di/bind-noop-instrumentation.ts` + `bind-otel-instrumentation.ts`:** bind ITracer + ILogger + IMetrics symbols to a Container. Returns the resolved instances so callers can use them without container lookup. `bindSentryInstrumentation` kept as a deprecated alias for one release. **Subpath exports** (`package.json#exports`): - `./instrumentation` — barrel (interfaces + Noops + OtelTracer/OtelLogger + withSpan + withCapture + reported-flag + symbols + binders) - `./instrumentation/otel` — OTel init helper + resource builder barrel - `./instrumentation/otel/init-server-node` — `initOtelServerNode` (app bootstrap, server-side OTel SDK) - `./instrumentation/sentry/init-server` — Next.js server Sentry.init helper - `./instrumentation/sentry/init-client` — Next.js browser Sentry.init helper - `./instrumentation/sentry/init-server-node` — `@sentry/node` server init (TanStack Start) - `./instrumentation/sentry/init-client-react` — `@sentry/react` browser init (TanStack Start) **Boundaries:** - `core-shared/instrumentation/otel/**` MAY import from `@opentelemetry/sdk-*` and `@sentry/opentelemetry`. - `core-shared/instrumentation/sentry/**` MAY import from `@sentry/*` (browser init files). - Everything else in `packages/core-shared/src/` MUST NOT. - ESLint rules R40 + R52 in `core-eslint/base.js` enforce the broader monorepo boundary.