docs: instrumentation conventions in CLAUDE.md / AGENTS.md / vertical-feature-spec.md
Adds the seven Plan 10 conventions to CLAUDE.md (interfaces in core-shared, spans at bind time, throw-site capture, PII rules, three Sentry projects, orthogonal binding). Adds an "Instrumentation conventions" section to AGENTS.md with repo constructor/method patterns, capture-rules table, boundary allowlist, and test rules. Appends §16 "Instrumentation & error capture" to vertical-feature-spec.md (the spec already has 15 sections, so appending rather than slotting in as §10 to avoid renumbering). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
77
AGENTS.md
77
AGENTS.md
@@ -348,6 +348,83 @@ Actual function names: `bindProductionAuth`, `bindProductionBlog`, `bindProducti
|
||||
|
||||
---
|
||||
|
||||
## Instrumentation conventions
|
||||
|
||||
**Symbols (in `core-shared/instrumentation/symbols.ts`):**
|
||||
- `INSTRUMENTATION_SYMBOLS.TRACER` — bound to `ITracer` (`NoopTracer` / `SentryTracer`)
|
||||
- `INSTRUMENTATION_SYMBOLS.LOGGER` — bound to `ILogger` (`NoopLogger` / `SentryLogger`)
|
||||
|
||||
**Repository constructor signature (every feature):**
|
||||
|
||||
```ts
|
||||
constructor(
|
||||
config: SanitizedConfig,
|
||||
tracer: ITracer = new NoopTracer(),
|
||||
logger: ILogger = new NoopLogger(),
|
||||
)
|
||||
```
|
||||
|
||||
**Repository method body (every public async method):**
|
||||
|
||||
```ts
|
||||
return this.tracer.startSpan(
|
||||
{ name: "<entity>.<method>", op: "repository", attributes: { /* ... */ } },
|
||||
async (span) => {
|
||||
try {
|
||||
const result = await /* payload op */;
|
||||
span.setAttribute("count", /* ... */);
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.logger.captureException(err, {
|
||||
tags: { feature: "<feature>", repo: "<entity>", method: "<method>" },
|
||||
});
|
||||
span.setStatus("error", err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
**Use case + controller spans (applied at DI bind time):**
|
||||
|
||||
```ts
|
||||
const wrappedUC = withSpan(
|
||||
tracer,
|
||||
{ name: "blog.getArticles", op: "use-case" },
|
||||
getArticlesUseCase(repo),
|
||||
);
|
||||
const wrappedCtrl = withSpan(
|
||||
tracer,
|
||||
{ name: "blog.getArticles", op: "controller" },
|
||||
getArticlesController(wrappedUC),
|
||||
);
|
||||
```
|
||||
|
||||
**Capture rules:**
|
||||
|
||||
| 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 | — |
|
||||
|
||||
**Boundary rule (eslint-enforced, R40):**
|
||||
Feature packages MUST NOT `import "@sentry/*"`. Allowlist:
|
||||
- `**/instrumentation/sentry/**` (core-shared)
|
||||
- `**/instrumentation/di/bind-sentry-instrumentation.{ts,test.ts}`
|
||||
- `**/setup/no-sentry.{ts,js}` + the test guard
|
||||
- `apps/*/instrumentation*.{ts,mjs,js}`
|
||||
- `apps/*/next.config.{mjs,ts,js}`
|
||||
- `apps/*/vite.config.{ts,mjs,js}`
|
||||
|
||||
**Test rules:**
|
||||
- Default to `NoopTracer` / `NoopLogger` (constructor defaults)
|
||||
- Assert spans/captures by injecting `RecordingTracer` / `RecordingLogger` from `@repo/core-testing/instrumentation`
|
||||
- Real `@sentry/*` SDK MUST NOT initialize during tests (guarded by `core-testing/setup/no-sentry.ts`)
|
||||
|
||||
---
|
||||
|
||||
## Specification & Guides
|
||||
|
||||
- **Vertical Feature Spec** — `docs/architecture/vertical-feature-spec.md` — full design, rationale, decision log
|
||||
|
||||
Reference in New Issue
Block a user