v0.9.4
Released
Fix
Cached Data-Availability, Activity Overview Multi-Engine Fix & Page Titles
Data-availability endpoint now reads cached flags on projects (6s+ → <50ms on ClickHouse), activity_overview panel works across all engines, 14 dashboard pages get proper titles, plus uuid 14 and fast-xml-parser 5.7.3 security bumps.
- GET /api/v1/projects/data-availability rewritten to read cached has_X_at flags — 6s+ → <50ms on production ClickHouse
- Boot-time one-shot backfill for the new project flags, throttled and gated by system_settings
- activity_overview panel logs/log_errors series now work on ClickHouse and MongoDB via reservoir.aggregate
- 14 dashboard pages now set proper <title> instead of falling back to generic 'LogTide'
- Security: uuid 14.0.0 (CVE-2026-41907) and fast-xml-parser 5.7.3 (CVE-2026-41650) bumps
Changed
GET /api/v1/projects/data-availabilityrewritten to read from cached flags onprojectsinstead of fanning outN*3existence queries to the reservoir per call. In production (ClickHouse, ~70M rows) the old path scannedcount(*)andLIMIT 1probes across logs/traces/metrics for every project in the org, taking 6s+ on non-trivial orgs. The new path is a singleSELECT id, has_logs_at, has_traces_at, has_metrics_at FROM projects WHERE organization_id = $1plus an in-memory staleness filter againstorganizations.retention_days, returning in <50ms regardless of ingest volume. The response shape is unchanged ({ logs: string[], traces: string[], metrics: string[] })- Ingest paths mark data-availability flags fire-and-forget: after a successful batch in logs / traces / metrics ingest,
projectsService.markHasData(projectId, kind)updatesprojects.has_X_at. A module-scoped in-memory debounce (5 min per(projectId, kind)) prevents UPDATE spam on hot projects: at most one UPDATE per 5 min per pod per project per kind. Failures are logged and swallowed so ingest is never impacted; the boot-time backfill is the safety net
Added
- Migration 042
project_data_availability: adds nullablehas_logs_at,has_traces_at,has_metrics_atTIMESTAMPTZcolumns toprojects. No backfill in SQL (handled at runtime, see below) - One-shot backfill at server boot:
runDataAvailabilityBackfill()runs fire-and-forget afterapp.listen, guarded by adata_availability.backfilledrow insystem_settingsso subsequent boots are a no-op. It selects every project with all three flags still NULL and, for each, runs threeLIMIT 1probes (logs / traces / metrics) via the reservoir in parallel. Throttled in batches of 10 with a 50ms pause to avoid hammering ClickHouse/Timescale/Mongo at boot. On a deployment with 1000 projects this takes ~6s on ClickHouse or MongoDB, up to ~30s on TimescaleDB when most projects are empty (chunk scan confirming zero rows). Force a re-backfill by deleting the safety row - Multi-engine staleness rule: a project is reported as “has data” only if
has_X_at >= now() - organizations.retention_days. Handles the “data aged out by retention” case without needing a background worker. The same logic works identically on TimescaleDB, ClickHouse and MongoDB deployments because the flag lives in Postgres and the reservoir only backs the ingest and backfill paths
Fixed
activity_overviewdashboard panel showed emptylogs/log_errorsseries on ClickHouse and MongoDB: the fetcher inpanel-data-service.tsgated both the logs and the spans queries behindreservoir.getEngineType() === 'timescale', so on non-Timescale engines those branches were skipped entirely and the panel rendered with all four log/span counters flat at zero (onlydetectionsandalerts, which read from Postgres operational tables, kept working). The logs path now branches: TimescaleDB still uses thelogs_hourly_stats/logs_daily_statscontinuous aggregate with the rawlogsfallback, while ClickHouse and MongoDB go throughreservoir.aggregate({ interval: '1h' | '1d' })(same dual pattern already used bybaseline-calculator). Bucket boundaries fromtoStartOfHour/$dateTruncalign with the panel’s UTCbuildBucketTimes, so the keys collide cleanly.logssumstotalper bucket,log_errorssumsbyLevel.error + byLevel.critical. Spans / span_errors stay TimescaleDB-only becauseIReservoirhas noaggregateSpans()primitive, matching the existing convention intrace_latencyandtrace_volume- Missing
<title>on 14 dashboard pages: every tab opened on/dashboard,/dashboard/admin/{organizations,projects,users,settings}(list and detail),/dashboard/monitoring(list and detail) and/dashboard/projects/[id]/{alerts,overview,performance,settings}showed the generic fallback “LogTide” fromapp.htmlbecause no<svelte:head><title>...</title></svelte:head>was set on the page. They now follow the existingTitle - LogTideconvention used by the rest of the app, with dynamic titles where the entity is already in scope:{$activeDashboard.name}for the custom dashboards root,{org.name}/{project.name}/{user.name ?? user.email}/{monitor.name}for the four[id]pages (each with a sensible fallback while the loader resolves). The two pure-redirect pages (/dashboard/projects/[id]and/dashboard/settings, whichgototheir default sub-route on mount) intentionally keep no title since the destination sets one immediately
Security
- Bump
uuidto 14.0.0 (GHSA-w5hq-g745-h8pq, CVE-2026-41907, MODERATE):v3,v5andv6accepted a caller-providedbuf+offsetbut did not validate bounds, so a small buffer or an out-of-range offset produced silent partial writes instead of throwingRangeErrorlikev1/v4/v7do.uuidis pulled in transitively bybullmqandldapts(production) andbson/ioredis(dev only); both production consumers resolve to their ESM entry points from our"type": "module"backend, so the major bump from^11.1.0is safe (uuid 12+ dropped CommonJS, but we never load the CJS path). Added"uuid": ">=14.0.0"to the root pnpm overrides; resolves to14.0.0. Engines requirement of Node 20+ is already satisfied byengines.node: ">=20.0.0" - Bump
fast-xml-parserto 5.7.3 (GHSA-gh4j-gqv2-49f6, CVE-2026-41650, MODERATE):XMLBuilderdid not escape the-->sequence in comment bodies or the]]>sequence in CDATA sections, so user-controlled data flowing into either could break out and inject sibling XML nodes (XSS in browser-rendered XML, SOAP injection, RSS/SVG payloads, etc.). The fix lands in 5.7.0; we use it transitively only via@aws-sdk/xml-builderpulled in by@types/nodemailer(dev), but bumping the override from>=5.5.7to>=5.7.0clears the lockfile advisory. Resolves to5.7.3