# Domain Entities — U4 Observability Integration **No persisted entity.** U4 adds no table, no migration and no database column. It adds two configuration sections, one anonymous endpoint, and build-time frontend values. --- ## Concept Relationships ```mermaid graph TD obsconfig["Observability configuration
backend"] dsn["Sentry DSN"] env["Environment name"] release["Release identifier"] logging["Structured logger"] corr["Correlation identifier"] scrubber["Credential scrubber"] sentrybe["Sentry client
backend"] tunnel["Tunnel endpoint"] vite["Vite build-time values
frontend"] sentryfe["Sentry client
frontend"] umami["Umami script component"] apicfg["API base URL resolution"] obsconfig --> dsn obsconfig --> env obsconfig --> release dsn --> sentrybe dsn --> tunnel env --> sentrybe release --> sentrybe logging --> corr logging --> sentrybe scrubber -->|"filters before send"| sentrybe vite --> sentryfe vite --> umami vite --> apicfg sentryfe -->|"posts envelopes to"| tunnel tunnel -->|"forwards to DSN host only"| sentrybe classDef cfg fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000; classDef backend fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000; classDef frontend fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000; classDef guard fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000; class obsconfig,dsn,env,release,vite cfg; class logging,corr,sentrybe,tunnel backend; class sentryfe,umami,apicfg frontend; class scrubber guard; ``` Text alternative: backend configuration supplies the Sentry DSN, environment and release; the structured logger attaches a correlation identifier and feeds Sentry through a credential scrubber; the frontend reads build-time values and routes its error envelopes through the same-origin tunnel, which forwards only to the configured DSN host. --- ## Backend configuration — `Observability` section | Field | Type | Default | Purpose | |---|---|---|---| | `SentryDsn` | string | empty | Absent means Sentry is skipped entirely (BR-U4-08) | | `Environment` | string | falls back to `ASPNETCORE_ENVIRONMENT` | Tag distinguishing test from production | | `TunnelMaxPayloadBytes` | int | fixed default | Upper bound enforced by the tunnel (BR-U4-17) | | `TracesSampleRate` | double | conservative default | Performance sampling; kept low so the free plan is not exhausted | **Not configurable, deliberately**: the scrub list (BR-U4-13) and the tunnel's destination host (BR-U4-16). Both are security-critical, and making either configurable would create a way to switch the protection off — the scrub list by omission, the destination by turning the endpoint into a request-forgery primitive. **Secret handling**: a Sentry DSN is not a secret in the usual sense — it identifies a project and permits event submission, and the frontend's copy is visible in the page source. It still comes from environment variables in production, consistent with D-16, rather than being committed. --- ## Correlation identifier | Aspect | Detail | |---|---| | Purpose | Ties every log entry and Sentry event from one request together (SECURITY-03) | | Scope | One request | | Presence | On **every** log entry, not only on errors | | Mechanism | **Not fixed here** — `HttpContext.TraceIdentifier` versus W3C `traceparent` is OPEN-01, decided in this unit's NFR Design | Recorded as an entity because the choice affects the shape of every log entry, and NFR Design will settle it rather than leaving it to code generation. --- ## Credential scrubber Not persisted; a filter applied to every outbound Sentry event. | Removed | Reason | |---|---| | `Cookie` header, entire | Carries the `refreshToken`. Removing one cookie by rewriting the header is error-prone | | `Authorization` header | Bearer token | | `X-Master-Api-Key` header | Master/slave shared secret | | Request body | Login and password-change bodies carry passwords | | Retained | Reason | |---|---| | Method, path, query string | Diagnostic value. Note the invitation-token caveat below | | User agent | Browser-specific failures | | IP address | Attack-pattern recognition | | Authenticated username | Whose session hit the problem | | Correlation identifier | Ties the event to its log entries | **Invitation-token caveat**: `/api/v1/Invitation/validate?token=…` puts a token in the query string, which is retained. It is single-use and time-limited rather than a standing credential, and the value of knowing which endpoint was called outweighs it. Documented so the trade-off is visible. --- ## Security event types Six event shapes, not persisted — emitted as structured log entries that also become Sentry events. | Event | Level | Context | |---|---|---| | Failed login | Warning | Endpoint, whether the account exists, correlation ID | | Authorization denied | Warning | Endpoint, required policy, role held, correlation ID | | Master API key rejected | Warning | Endpoint, calling host, correlation ID | | Admin bypass rejected | Warning | Path, rejection reason class, correlation ID | | Rate limit triggered | Warning | Limiter name, endpoint, correlation ID | | Migration failure | Critical | Exception, attempt count, correlation ID | All six sit at `Warning` or above, so they cross the Sentry event threshold (BR-U4-06) by construction rather than by coincidence. **Rejection reason classes** for the admin bypass — `InvalidSignature`, `Expired`, `WrongIssuer`, `NotAdmin`, `Malformed` — never the token itself. The distinction matters: `InvalidSignature` suggests forgery, while `Expired` is usually an administrator with a stale tab. --- ## Frontend build-time values | Variable | Purpose | Absent behaviour | |---|---|---| | `VITE_API_BASE_URL` | API origin | Same-origin (BR-U4-22) | | `VITE_SENTRY_DSN` | Frontend Sentry project | Sentry skipped (BR-U4-26) | | `VITE_APP_ENV` | Environment tag | Untagged | | `VITE_UMAMI_SCRIPT_URL` | Umami script origin | Umami not loaded | | `VITE_UMAMI_WEBSITE_ID` | Per-environment website ID | Nothing rendered (BR-U4-28) | | `VITE_APP_TITLE` | Existing; unchanged | Defaults to `SlpModularCms` | **Why these force two build artifacts** (D-15): Vite bakes them into the bundle at build time, so one `dist/` cannot carry both the test and production website IDs or environment tags. The CI workflow therefore produces one artifact per environment. ### Frontend config model change `AppConfig.apiBaseUrl` gains one new legal value: the empty string, meaning same-origin. Its Zod schema becomes "empty string **or** valid absolute URL" — relaxed by exactly one case, not loosened to accept anything (BR-U4-24). --- ## Persistence Summary | Question | Answer | |---|---| | New tables? | None | | New migrations? | None | | New configuration sections? | One backend section (`Observability`); frontend variables are build-time | | New endpoints? | One — the anonymous Sentry tunnel | | Secrets stored? | None. The DSN is not a standing credential and comes from environment variables | | Data sent to third parties? | Error events to Sentry, page views to self-hosted Umami. Credentials scrubbed per BR-U4-13 |