shoc-backend/docs/adr/0002-sentry-observability.md
2026-09-15 16:08:14 -03:00

79 lines
4.2 KiB
Markdown

# ADR 0002 — Sentry observability: release identity and redaction-safe telemetry fields
- Status: Accepted
- Date: 2026-09-03
- Scope: `Api.SeaHavenIndustries` (observability layer only; no behavior changes)
## Context
The backend reports to Sentry (SH-298). Events arrived without a stable release
identity, without service/commit attribution, and background jobs each hand-rolled
their own transaction setup. HTTP request metadata risked leaking PII through
route values, query strings, or identity claims.
## Decision
1. **Release identity.** `SentryObservability.ResolveRelease(Assembly)` accepts an
informational version shaped `shoc-backend@<40 hex commit sha>`, tolerates the
.NET SDK's matching `+<same sha>` suffix for compatibility, canonicalizes the
result to lowercase, and otherwise resolves to `local-development`.
`scripts/package-elastic-beanstalk.sh` resolves
the commit from `APP_COMMIT_SHA` → `GITHUB_SHA` → `git rev-parse HEAD`, fails
when `CI=true` cannot produce a valid 40-hex sha, and publishes with
`-p:SourceRevisionId`, `-p:InformationalVersion=shoc-backend@<sha>`,
`-p:IncludeSourceRevisionInInformationalVersion=false`,
`-p:DebugType=portable`, `-p:DebugSymbols=true`.
2. **Default tags.** `service=shoc-backend` and `app.commit=<sha | local-development>`
on `SentryOptions.DefaultTags`. All events use the same commit-addressed
`shoc-backend@<sha>` release.
3. **HTTP requests.** `SentryRequestMetadataMiddleware` runs **after**
routing/authentication and **before** authorization. It sets on the current
scope only the fields below. Non-controller endpoints omit `code.function`.
4. **Background jobs.** All six worker transaction blocks (five files) use
`SentryObservability.BeginBackgroundTransaction`, which pushes a scope, starts
the transaction, sets the scope transaction, and returns a handle with
`FinishOk` / `FinishCancelled` / `FinishError(Exception)` that cannot
double-finish. Existing status/error behavior is preserved.
5. **Last-mile privacy.** Request-body extraction is disabled. Global before-send
processors clear request data, query strings, cookies, headers, environment
values, and miscellaneous request values from both error events and
transactions, retain only an opaque user ID, and attach native trace and
transaction IDs as searchable transaction tags. Root and child span data use
an allowlist for function, operation, actor, route template, method/status,
protocol, database operation, and service host; raw URLs, query values,
network addresses, and arbitrary extras are removed.
## Field / redaction contract
Allowed request fields (template values only — never resolved route values):
| Field | Source | Rule |
| --- | --- | --- |
| `actor.type` | auth state | `authenticated` / `anonymous` / `system` |
| `operation.type` | static | `http.server` / `background_job` |
| `code.function` | `ControllerActionDescriptor` | `ControllerName.ActionName` only |
| `http.method` | request | HTTP verb |
| `http.route` | `RouteEndpoint.RoutePattern.RawText` | route template; omitted when unresolved |
| `trace_id` / `transaction_id` | active Sentry span/transaction | native Sentry ids |
| scope `User.Id` | `ClaimTypes.NameIdentifier` only | set only when authenticated |
Never intentionally sent: query strings, headers, bodies, cookies, usernames,
emails, IP addresses, route values, credentials, or token values. The application
retains stack traces, exception types/messages, safe tags, operation status, and
route templates because those are required to identify the failing function and
operation.
## Consequences
- Release health maps 1:1 to a commit sha; local builds are clearly labeled.
- One redaction contract to audit, enforced by
`SentryObservabilityTests`/`SentryPipelineContractTests`.
- ASP.NET Core request transactions and the SDK's registered outgoing HTTP
instrumentation cover inbound API calls and the platform's typed procurement
client; explicit root transactions cover every hosted worker loop.
## Alternatives considered
- Per-worker transaction setup (rejected: duplication drifted across five files).
- Sentry's default request payload with PII scrubbing (rejected: default-deny is
safer than default-capture-then-scrub).