Repository navigation
feat(otel,ai): TimescaleDB OTEL metrics + streaming agentic AI chat, API CLI tool, trace UI, alarms - #173
Merged
Conversation
…arity Bring the default TimescaleDB OTEL-metrics backend to parity with the ClickHouse backend (PR #158): persist and query the full decoded metric record, with efficient day-one indexing. - batch_insert_metrics: store all 31 columns (start_time, temporality, is_monotonic, flags, description, exponential-histogram fields, summary quantiles, exemplars) as bound params. - query_metrics: temporality-aware histogram reconstruction (delta = element-wise bucket-count sum via WITH ORDINALITY; cumulative = latest snapshot per series), NULL-safe group_by join (IS NOT DISTINCT FROM), and Prometheus-style histogram quantile interpolation (p50/p95/p99) instead of a mean fallback. - Migration m20260629_000001: nullable columns + composite (project_id, metric_name, service_name, timestamp DESC) btree + GIN (attributes jsonb_path_ops) indexes; idempotent, reversible, verified safe on compressed hypertables. - Reads stay on the raw hypertable, NOT the 1min/1hr continuous aggregates: the CAGGs lag materialization and would silently drop recent buckets from charts. Raw + composite index + chunk pruning is correct and fast. Tests: 4 unit (histogram_quantile) + 11 integration vs real TimescaleDB (aggregations, label filter, group_by, histogram summary, delta element-wise sum, cumulative latest snapshot, quantile interpolation, NULL-label group_by, rate-per-sec, compressed-chunk DDL safety, bad-label rejection).
…, mobile fixes
AI chat (temps-ai-chat / temps-ai-gateway / temps-ai):
- single-pass streaming with inline tool calls via AiService::chat_stream_turn
(gateway accumulates OpenAI tool_call deltas by index); replaces the
non-streaming gather loop so prose streams token-by-token with live tool cards
- stop/cancel: a dropped SSE connection halts generation (drops the AI stream)
and persists the partial turn
- ordered render parts (text/tool segments) persisted so reload == live
- temps-ai-api-tools: read-only `temps` virtual CLI over the platform API
(--help discovery, auto project scope); structured {operation,status,data} result
- add temps_ai* crates to the tracing EnvFilter allowlist
Chat UI (web):
- token streaming, interleaved tool cards, syntax-highlighted args/results,
soft line-breaks, clickable links (new tab), Stop button
- draft + active-chat persistence, project-aware "new chat", page context,
favicon + context-type avatar in the chat header
Observability:
- trace detail: per-span logs/events, service-coloured split waterfall with a
wide side detail (focused drawer on mobile); logs<->trace correlation
- alarms UI + unified alarm/log (ADR-024/025); AI provider default model
- otel: fixed LogRecord.flags wire type; store all log severities
Mobile/shell: dvh app shell, body scroll lock, viewport fixes
Review fixes for #173: - security: add project_scope_guard! to list_metric_label_keys / list_metric_label_values so a project-scoped deployment token can't enumerate another project's metric labels (no-op for user/API-key/session auth) - validate_label_key now returns OtelError::Validation (HTTP 400) instead of Storage (500) — a malformed client label key is a client error - docs: update temps-ai-chat api_tools module doc to the shipped single `temps` CLI tool (was describing the removed search_api/describe_api/call_api trio)
Resolves the 2 generated-SDK merge conflicts by regenerating from the merged backend so the client has both main's and this branch's endpoints (alarms, OTel metric label discovery, AI provider default_model, chat message parts).
dviejokfs
added a commit
that referenced
this pull request
Jun 30, 2026
CI runs clippy across the whole workspace with -D warnings. A pre-existing unnecessary_sort_by lint in temps-otel (merged from main via #173) failed the advisory clippy job on this branch. Replace the manual comparator closure with sort_by_key. No behaviour change.
dviejokfs
added a commit
that referenced
this pull request
Jun 30, 2026
…B load (#174) * perf(proxy): cache IP block-list and geolocation to cut per-request DB load The proxy issued a Postgres query on every request from two hot-path spots, saturating the connection pool under load (~114 busy connections at 1k req/s): - IpAccessControlService::is_blocked ran a COUNT(*) against ip_access_control in early_request_filter for every request. - IpAddressService::get_or_create_ip queried ip_geolocations per unique IP from the batch-log enricher and the visitor manager. Eliminate both from the request path: - Hold an in-memory ArcSwap<Vec<IpNetwork>> snapshot of block rules, refreshed every 30s by a background task and after each write. is_blocked now does a lock-free CIDR-containment scan with no DB query (IpNetwork::contains mirrors the prior $ip <<= ip_address). Unparsable client IPs fail open as before. - Add a moka cache (IP -> IpAddressInfo, 100k cap, 6h TTL) in IpAddressService so repeat IPs resolve from memory; only cache misses touch the DB. Benefits the batch writer and the visitor manager alike. Per-request proxy DB queries drop from ~2 to ~0. Adds unit tests for the in-memory block matching. * docs(changelog): note proxy per-request DB query elimination (#174) * fix(otel): use sort_by_key to satisfy clippy -D warnings CI runs clippy across the whole workspace with -D warnings. A pre-existing unnecessary_sort_by lint in temps-otel (merged from main via #173) failed the advisory clippy job on this branch. Replace the manual comparator closure with sort_by_key. No behaviour change. * fix(ai-api-tools): use sort_by_key to satisfy clippy -D warnings Another pre-existing unnecessary_sort_by lint (from main) that the whole-workspace clippy -D warnings CI job flags. Replace the reverse comparator closure with sort_by_key + Reverse. No behaviour change.
dviejokfs
added a commit
that referenced
this pull request
Jul 26, 2026
…with (#442) Closes 11 of the 14 open Dependabot alerts; the remaining 3 are upstream-blocked and re-verified unreachable (documented in Cargo.toml). Fixed: - next 16.2.6 -> 16.2.11 in examples/nextjs/basic (alerts #175-#183, 9 alerts: SSRF in Server Actions on custom servers, SSRF via rewrite destination hostname, middleware/proxy bypass with Turbopack + single locale, DoS in Server Actions, cache confusion on invalid-UTF-8 and on request bodies, unbounded Server Action payload on Edge, image optimization SVG DoS, and unauthenticated Server Function endpoint disclosure). Verified: tsc --noEmit clean and `next build` succeeds. - setuptools 82.0.0 -> 83.0.0 in sdks/python/uv.lock (alert #174, MANIFEST.in sdist exclusion bypass via NFC/NFD collision on APFS/HFS+). - serde_with 3.20.0 -> 3.21.0, pulling serde_with_macros 3.21.0 (alert #173, KeyValueMap serialize panic on empty sequence/map entries). Reached via cloudflare 0.14 -> temps-dns. Not fixable, re-verified unreachable (triage refreshed in Cargo.toml): - protobuf 2.28.0 (#4): pingora-core 0.8.1 declares prometheus = "0.13" with default features, and `protobuf` is a DEFAULT feature of prometheus 0.13 -- cargo features are additive, so it cannot be disabled without vendoring a pingora fork. No ProtobufEncoder / TextEncoder / protobuf:: / PrometheusServer reference exists under crates/, so the 2.x parser is never fed input. - opentelemetry_sdk 0.30.0 (#151): pinned via the getsentry/relay git rev. The bug is in the W3C BaggagePropagator; no case-insensitive match for `baggage` exists under crates/. - remove_dir_all 0.5.3 (#1): nixpacks 1.41.0 (still crates.io latest) uses deprecated tempdir, which hard-requires remove_dir_all ^0.5. The TOCTOU branch only runs when out_dir is None, and nixpacks_preset.rs:508 remains the only out_dir site, passing Some(..). - serde_with 2.3.3 also remains via nixpacks and has no fix (2.3.3 is the last 2.x; the fix landed only in 3.21.0). KeyValueMap does exist in 2.3.3, but nixpacks uses serde_with with default-features = false, features = ["macros"], solely for skip_serializing_none. Cargo.lock also re-unifies two entries that any re-resolve surfaces, both within their declared ranges and not regressions: pingora-lru moves to hashbrown 0.14.5 (its req is "0", and ahash/allocator-api2 are 0.14.5 defaults), and iana-time-zone moves to windows-core 0.57.0 (req is ">=0.56, <=0.62"; a cfg(target_os = "windows")-only dep, collapsing a duplicate copy). Verified: cargo check --lib --workspace clean (0 errors); cargo audit vulnerability count unchanged at the 3 known upstream-blocked entries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Brings the default TimescaleDB OTEL-metrics backend to full-fidelity parity with the ClickHouse backend from #158. It now persists and queries the complete decoded metric record, with efficient day-one indexing.
ClickHouse is opt-in (
TEMPS_CLICKHOUSE_*); TimescaleDB is what every default self-host runs — so this closes the fidelity gap for the common path (histograms, exponential histograms, summaries, exemplars, temporality).Changes
batch_insert_metrics): store all 31 columns —start_time,temporality,is_monotonic,flags,description, exponential-histogram fields,summary_quantiles, andexemplars(JSONB) — as bound params.query_metrics): temporality-aware histogram reconstruction (delta = element-wise bucket-count sum viaWITH ORDINALITY; cumulative = latest snapshot per series viarn=1), NULL-safegroup_byjoin (IS NOT DISTINCT FROM), and Prometheus-style histogram quantile interpolation (p50/p95/p99) instead of a mean fallback.m20260629_000001_otel_metrics_full_fidelity: 14 nullable columns + composite btree(project_id, metric_name, service_name, timestamp DESC)+ GIN(attributes jsonb_path_ops). Idempotent, reversible, verified safe on compressed hypertables.otel_metrics_1min/1hrcontinuous aggregates: the CAGGs lag materialization and would silently drop recent buckets from charts. Raw + the composite index + chunk pruning is correct and fast.Security
The SQL builder is injection-safe: all user values are bound
$Nparams; the only interpolated tokens are a clamped quantile float and an allowlisted bucket-interval integer (reviewed by thesecurity-auditoragent — verdict: injection-safe).Tests
histogram_quantile(interpolation, +Inf overflow bucket, empty/zero).clippyclean.E2E
Verified live end-to-end against the Observability Starter example app: real OTLP metrics → ingested into TimescaleDB (full-fidelity write confirmed) → rendered in the Observe metrics explorer, with histogram p50/p95/p99 bucket interpolation confirmed in the UI.