Skip to content

Observability

Operational signals explain failures; durable product records explain usage and security decisions. They have separate storage and retention boundaries.

flowchart TB
    app["Kesita app · Node"] -->|"Azure Monitor exporter"| traces["Configured trace backend"]
    core["Legal Core · Go"] -->|"OTLP / HTTP exporter"| collector["Configured OTLP endpoint"]
    collector -->|"Deployment-managed delivery"| traces
    app -->|"Structured JSON"| logs["Container stdout → log backend"]
    core -->|"Structured JSON"| logs
    app -->|"Usage, audit and security writes"| db[("Product PostgreSQL")]
    db -->|"Authorized server-side queries"| admin["Admin usage and audit views"]

Trace export requires configuration; the diagram does not imply a collector or backend is provisioned. Structured logging remains enabled when trace export is disabled. Admin product views read durable records; a telemetry-reader adapter is still planned.

SignalStorePurpose
HTTP traces, dependencies, exceptionsConfigured telemetry backend; Azure Monitor / Application Insights is an Azure optionSRE debugging, latency, error grouping
Structured logsContainer stdout → configured log backend; Log Analytics on AzureCorrelated operational logs
LLM usage, estimated cost, latencyPostgreSQL llm_usage_eventProvider/model/profile receipts, detailed cache usage and versioned micro-USD estimates; invoice and commercial-credit reconciliation remain pending
AuditPostgreSQL audit_logProduct audit records; tamper-evidence not verified
Security enforcementPostgreSQL security_eventRecorded enforcement decisions; not proof of provisioned alert delivery

Ephemeral telemetry must never be the billing or compliance source of truth.

The data operations view at /operations/data on the admin host belongs to Kesita, not Azure. It reads aggregate queue, lease, cleanup and accounting state through a platform-admin-only database capability, and probes database, blob and Legal Core availability. Observations carry timestamps and become stale after 60 seconds.

Product workers report process presence every 20 seconds, including when idle. The view counts workers observed in the last 90 seconds; graceful shutdown removes presence and crashed processes age out. This proves recent process contact, not successful job execution or provider health. Maintenance-paused processes do not connect to the database or report presence. Corpus-worker presence and indexing freshness are not yet included. Legal summaries must come through Legal Core, not direct SQL. Infrastructure cost ingestion is a separate optional integration.

Add a narrow telemetry-reader adapter only when latency/error summaries are needed. It owns provider credentials and queries; admin receives stable types, timestamps, and explicit unknown/stale states. Bound and cache reads, enforce platform-admin access, and link to the backend for full log/trace exploration.

OpenTelemetry makes instrumentation vendor-neutral, but supplies neither storage nor a standard read API. Keep export configuration in shared observability packages and deployment configuration. Do not build a general cloud abstraction or self-host a monitoring stack just for portability. External uptime checks and alert delivery must survive an outage of Kesita and its database.

Existing usage summaries, shared logging/tracing, health endpoints and the data operations snapshot are implemented. The telemetry-reader adapter is not; see Deployment for the runtime boundary. Custom OTel/Prometheus RED metrics are not wired; slos.yaml is a specification, not proof of provisioned alerts.

  • service.name — deployable name (kesita-app, legal-core-api, …)
  • service.version — git sha or package version
  • deployment.environmentdevelopment | staging | production
  • cloud.provider — actual deployment provider, e.g. azure; omit locally
  • cloud.region — actual deployment region, e.g. westeurope; omit locally
sequenceDiagram
    participant Client
    participant App as Kesita app
    participant Core as Legal Core
    participant DB as Product PostgreSQL
    Client->>App: HTTP request, optional x-request-id
    Note over App: Honor or generate request ID
    App->>Core: Internal HTTP, traceparent / tracestate
    Note over App,Core: Trace context links service spans
    Core-->>App: Service response
    opt Operation records LLM usage
        App->>DB: Usage event with active traceId / spanId
    end
    App-->>Client: HTTP response, x-request-id

The request ID identifies an ingress request; the trace ID links instrumented work across services. Persisted trace IDs help investigations, but the associated trace may be sampled out or expire before the product record.

  • Ingress: x-request-id (generated if absent)
  • Distributed trace: W3C traceparent / tracestate
  • Domain records: optional traceId / spanId on LlmUsageEvent and audit metadata
  • All outbound HTTP to internal services injects W3C traceparent
  • Responses should echo x-request-id

Never in traces, logs, or metrics:

  • Passwords, session tokens, CRON_SECRET, provider API keys
  • Full legal document text, pratica content, email bodies
  • LLM prompts and completions (default off; content capture requires explicit opt-in)
  • Payment webhook payloads (when billing is enabled)
  • Raw Authorization / X-API-Key headers
  • Raw search queries beyond bounded hashes

Allowed with care:

  • Opaque IDs: userId, organizationId, job_id, traceId
  • Route templates: /api/aix/assignments/:id/events, not raw URLs with tokens
  • Aggregated counts: tokens, latency ms, error types
  • Hashed query fingerprints for abuse detection

Server-side APM is operational only. Product analytics, if added later, must be a separate opt-in pipeline with its own subprocessors entry.

ServicePortHealth
kesita-app3000GET /api/health/live, /ready, /deep
legal-core-api8080GET /v1/health/live, /v1/health/ready

Disable trace export with KESITA_TELEMETRY_ENABLED=false (structured JSON logs stay on). Machine-readable SLOs live in slos.yaml.

Use logger-only mode for ordinary local development. To inspect exported traces, enable telemetry and configure the exporter supported by that service: Node uses APPLICATIONINSIGHTS_CONNECTION_STRING; Go uses OTEL_EXPORTER_OTLP_ENDPOINT. Setting an OTLP endpoint alone does not configure the current Node initializer. See Configuration for authoritative environment templates and Local development for lifecycle controls.

  • @kesita/observability — Node/Next.js helpers
  • packages/observability-go — Go OTel + slog