--- arc42: "8 — Crosscutting Concepts" status: stub --- ## 8.1 Logging - Format: structured JSON in production (`python-json-logger`); human-readable in dev - Log levels: DEBUG (dev only), INFO (operational events), WARNING (recoverable anomalies), ERROR (failures needing attention) - What is logged per component: | Component | INFO events | WARNING events | ERROR events | |---|---|---|---| | API | request method+path+status+duration | validation failure (BSM) | DB connection failure | | Spider | check start/end, service_id, liveness result, duration | response time > 3s | fetch timeout, DB write failure | | Portal | form submission received | — | API call failure | - **Never log:** email addresses, API keys, DB credentials, any PII - Request IDs: generate UUID per request, include in all log lines for that request ## 8.2 Error Handling - All API errors return structured JSON: `{ "error": "string", "detail": "string", "code": "APIX-ERR-XXX" }` - HTTP status codes: - `400` — malformed request (not JSON, missing content-type) - `422` — BSM validation failure (Pydantic; include field-level errors) - `401` — missing or invalid API key on write endpoints - `404` — service not found - `429` — rate limit exceeded - `500` — internal server error (never expose stack trace to client) - Spider errors are logged but do not crash the scheduler; failed service → `liveness=unreachable` ## 8.3 Security Hygiene (MVP-grade) | Control | Implementation | What this is NOT | |---|---|---| | HTTPS | Caddy auto-TLS; HTTP redirects to HTTPS | Not HSTS with long max-age (add post-MVP) | | Write endpoint auth | `X-API-Key` header checked against env var | Not per-user keys (add post-MVP) | | Rate limiting on writes | Caddy `rate_limit` directive: 10 req/min per IP on `/api/register` | Not full DDoS protection | | No secrets in Git | `.env.example` only; `.env` in `.gitignore` | Not secret scanning CI (add post-MVP) | | No PII in logs | Enforced by convention; no log of `registrant_email` field | Not automated PII detection | | Non-root containers | All Dockerfiles use `USER appuser` | Not read-only filesystem (add post-MVP) | ## 8.4 BSM Validation - Validation layer: Pydantic v2 model in `models/bsm.py` - Required fields (per Internet-Draft): `name`, `version`, `description`, `capabilities[]`, `endpoint`, `contact_email` - Optional fields validated if present: `olevel`, `slevel`, `pricing`, `regulatory` - On validation failure: `422` with field-level error list - Re-registration (same `endpoint` URL): treated as update (UPSERT); BSM version must be >= existing version - Schema version stored with each record; enables future migration ## 8.5 Liveness Check - **"Live"** = HTTP 2xx response within 5 seconds from the registered `endpoint` URL - **"Degraded"** = HTTP 2xx but response time > 3 seconds - **"Unreachable"** = timeout, connection refused, or non-2xx response - Status transitions: any state → any state on each check (no hysteresis in MVP) - Check frequency: 15 min in prod, 2 min in dev - `last_checked_at` timestamp always exposed in API response ## 8.6 Idempotency - `POST /api/register` with the same `endpoint` URL: UPSERT (update BSM, reset liveness to `pending`) - Spider re-check: always overwrites previous liveness status — idempotent by design - DB migrations (Liquibase): each changeset is forward-only; re-running skips already-applied changesets (Liquibase tracks applied changesets in `DATABASECHANGELOG` table) ## 8.7 Internationalisation (i18n) See ADR-013 for the full decision and rationale. **Locale resolution order (highest priority first):** 1. `apix-locale` cookie (set by the language switcher via `POST /locale`) 2. `Accept-Language` request header (browser preference) 3. Default: `en` (English) **String externalisation:** - All user-visible strings in Qute templates are referenced via `{inject:msg.}` — not hardcoded - `Messages.java` (`@MessageBundle`) declares all keys; Quarkus compiler verifies usage at build time - `messages.properties` — English; `messages_de.properties` — German; adding a locale requires only a new properties file - Keys follow the pattern `
.` (e.g. `nav.register`, `service.oLevel.label`, `admin.pending.title`) **Help / tour content:** - Tour titles, step headings, and step body text are defined in `HelpContentService` using `Messages` keys, resolved to the request locale - The resolved tour data is serialized to JSON and embedded in each page as `window.PAGE_TOURS` + `window.PAGE_HELP` — no client-side translation lookup at runtime - Adding a translated tour step requires only adding the key to `Messages.java` + both properties files **What is not localised in MVP:** - Error messages from Bean Validation (return as-is in EN; acceptable for API-layer errors) - Log messages (always EN) - BSM content submitted by registrants (stored as-is; not translated) **Language switcher:** - `
` with `` in the base layout - `POST /locale`: validates lang against `["en", "de"]`; sets `apix-locale` cookie (path `/`, SameSite=Lax, HttpOnly); redirects to `Referer` header - Language switcher is rendered in the base layout; available on every portal page ## 8.8 Human-Readable Service Detail (Index Level 2 Entry) The machine-readable service entry (`GET /api/services/{id}` returning JSON) and the human-readable portal page (`GET /services/{id}` returning HTML) represent the same data. The HTML version is designed for a human making a go/no-go decision about using a service — not for a machine parsing a schema. **Design principle:** answer four questions in order, above the fold where possible: 1. **Who is this?** — name, description 2. **Can I trust them?** — O-level with plain-English explanation, liveness uptime, last-verified date 3. **What exactly does it do?** — capabilities, pricing 4. **How do I call it?** — endpoint, spec links, example snippet **Trust level presentation:** - O-level and S-level are never shown as bare codes (O-2, S-1) to human visitors — always rendered as `badge + level name + 2-sentence explanation` - The explanation is locale-resolved from `Messages` (keys `service.oLevel.N.description`) — not hardcoded in the template - O-level badge color conveys confidence tier at a glance: grey (O-0), blue (O-1/O-2/O-3), green (O-4/O-5) - "Reference entry by BSF" badge is shown prominently when `isReferenceEntry=true` — prevents a human from mistaking a BSF-registered third-party service for one that has self-registered **Liveness presentation:** - Status displayed as colored dot + label (LIVE / DEGRADED / UNREACHABLE) — not as an enum string - Uptime percentage and average response time are formatted human values ("98.4%", "142 ms") computed by `ServiceDetailViewModelFactory`, not raw floats - Last-checked timestamp shown relative ("8 minutes ago") with absolute ISO date in a `` tooltip — humans read relative time faster; machines read absolute **Separation of concerns:** - `ServiceDetailViewModelFactory` (portal module) owns all human-readable computation: relative timestamps, color class selection, O-level description lookup, GLEIF LEI URL construction - The Qute template (`service.html`) contains no business logic — it renders what the view model provides - The registry API is not changed for this feature; the portal fetches the existing full-detail endpoint and enriches the response client-side in the portal **Integration section (collapsible):** - The raw endpoint URL and a minimal HTTP example are provided for developers who discover the service through the portal rather than via agent query - Link to `GET /api/services/{id}` (machine-readable JSON) is included — a developer can use the portal as a discovery UI and then switch to the machine API - This collapsible is closed by default to keep the human trust signals prominent