## Problem
RUM-to-traces correlation stopped working after the browser SDK 0.4.x
line. The SDK's identifiers became plain `BigInt`, so the unchanged
`toString(16)` serialization silently started dropping leading zeros.
Trace ids are UUIDv7-shaped (48-bit ms timestamp in the top bits), so
the leading hex nibble is always zero today — effectively every RUM
event stored a 31-char `_oo_trace_id`/`_o2_trace_id` while the traces
stream stores the padded 32-char `trace_id` (the `traceparent` header
was padded correctly, so backend traces have the full id). Exact-match
correlation could never hit, in either direction.
Verified against live data: for sampled traced requests, `'0' +
stored_id` found the backend trace every time; the stored id never did.
## Fix (frontend compatibility layer)
Works for the already-ingested short ids **and** for correct ids from
the fixed SDK:
- `utils/rum/fields.ts`: `normalizeTraceId` (pads to canonical 32-char
hex), `traceIdLookupVariants` (padded + legacy zero-stripped forms),
`rumFieldEqualsAnySql` (matches both namespace spellings x both id
forms), and a shared `RUM_CORRELATION_TRACES_STREAM` constant replacing
four scattered "default" literals.
- `useRumSpanBuilder`: trace→RUM lookup queries both id forms; canonical
id stamped on generated RUM spans.
- `useTraceCorrelation`: backend-span query now targets the real
`default` traces stream (was a nonexistent `_traces` stream) as
`page_type: "traces"` (was `"logs"`), with the padded id.
- `EventDetailDrawerContent`: trace navigation pads the id before
building the route.
- `PlayerTracesTab`: the session Traces tab canonicalizes ids before the
traces-stream metadata join (rows were silently dropped).
- `ErrorViewer`: error correlation card receives the padded id;
implausible (non-hex) ids hide the card instead of showing a misleading
empty state.
- `package.json`: browser SDK bumped to 0.4.2-beta.3, which serializes
trace/span ids as fixed-width canonical hex at the source (resolved
`browser-rum-core` verified at 0.4.2-beta.3).
## Testing
- New unit coverage: `fields.spec.ts` (16 tests) plus regression tests
in each touched spec (legacy 31-char id fixtures asserting padded
32-char behavior); full RUM/traces suites green (53 files, 1971 passed).
- Live-verified against real data: a trace from the broken window
renders the full RUM hierarchy (Session→View→Action→Resource + backend
spans) where the old code showed backend spans only; pre-regression
sessions still correlate (no behavior change for correct ids).
- `npm run lint` and `npm run type-check` clean.
## Problem
Every frontend build is red on `npm ci` in `web/`:
```
npm error `npm ci` can only install packages when your package.json and
package-lock.json ... are in sync.
npm error Missing: @noble/hashes@2.3.0 from lock file
```
## Root cause (not a feature change)
- `web/package-lock.json` was last regenerated with **npm 11**, whose
resolver **omits** the explicit `@noble/hashes@2.3.0` entry that
`jsdom`'s transitive `@exodus/bytes@1.15.1` **peer-requires** (`^1.8.0
|| ^2.0.0`).
- The **Playwright build pins `node-version: 22` → npm 10**
(`.github/workflows/playwright.yml`). npm 10's `npm ci` recomputes the
ideal tree, needs that entry, finds it missing, and hard-fails.
- Reproduced locally: `npm@10 ci` **fails**, `npm@11 ci` **passes**, on
the exact committed lock. So it's a **toolchain mismatch**, independent
of any PR — it breaks `main` and every open PR building the frontend.
## Fix
Regenerated the lock with `npm@10 install --package-lock-only` → adds
the nested `node_modules/jsdom/node_modules/@noble/hashes@2.3.0` entry.
**+15 lines, lockfile only**, `lockfileVersion` unchanged (3).
Verified `npm ci` now passes under **both npm 10 and npm 11**.
## Note
The durable fix is aligning the Playwright build to **Node 24 / npm 11**
(matching the AI-review workflow and the Node 20/22 runner deprecation).
This PR is the minimal unblock; the Node bump can follow separately.
## What
Deletes `web/scripts/design-debt-baseline.json` — **not by relaxing the
rules, by fixing the debt**. Every design-token category is now
zero-tolerance, SCSS is gone from the app, and 144 hand-written shadow
values collapse into one scale.
## The guard was under-reporting (three separate holes)
1. **Comment contamination.** `check-design-consistency.mjs` fed comment
text into its exemption checks, so a `keep()` comment that merely
*named* `:deep(` / `::after` / `@keyframes` silenced the rule after it —
and a `;` or `{` inside a comment corrupted the offsets, so stripping
comments afterwards couldn't help. Comments are now blanked
(offset-preserving) before the scan. **Uncovered 9 occurrences in 6
files.** Real debt was 110, not 101.
2. **`arbShadow` only scanned the template.** `box-shadow:` in a
`<style>` block and `boxShadow:` in a JS/`:style` object were invisible
— 40 literals sat there while the guard reported zero. It now scans all
three syntaxes across `.vue` and `.ts`.
3. **`rawVarInTemplate` missed the fallback form.**
`border-(--color-x,rgba(…))` and `bg-[var(--color-x,#fff)]` slipped
through. The token is always defined, so the fallback can never fire —
it only hid the read.
## stylelint had never run on 14 files
`postcss-scss` isn't installed, so `postcss-html` **silently skipped**
every `<style lang="scss">` block. The CI step named *"Lint styles
(stylelint — bans legacy --o2-* tokens)"* was a no-op for those files —
including `color-no-hex` and the `.body--dark` selector ban. All 14 are
plain CSS now and `vue/block-lang` errors on `scss`/`sass`/`less`, so it
cannot silently recur.
## What moved
| | before | after |
|---|---|---|
| `rawVarInComponent` (in `<style>`) | 110 | **0** |
| template `x-[var(--color-*)]`, utility exists | 225 | **0** |
| template reads, no utility | 27 | **0** |
| `style` / `:style` colour literals | 29 | **0** |
| `color-mix(token N%, transparent)` | 160 | **0** |
| hand-written shadows | 233 (144 distinct) | **0** |
| SFCs using SCSS | 14 | **0** |
Guard categories: 22 → 24. `rawVarInTemplate` and `arbShadow` are new;
`arbPx` was deleted because main's `local/no-hardcoded-px` ESLint rule
(#13541) now owns px across every file type.
## Shadows are a system now
Stripped of colour, the 144 values were the same handful of geometries
rewritten in px vs rem, with and without an explicit `0` spread.
```
--shadow-xs / sm / md / lg elevation
--shadow-sticky-left / -right directional (sticky columns)
--shadow-rail inset status rail
--shadow-glow-sm / -md / -lg coloured, 3 intensities
--shadow-scroll-top / -bottom / -both scroll affordance
--shadow-ring-hairline 1px ring where a utility can't reach
--shadow-ai-focus AI composer focus glow (flips in dark)
```
Colour is a **separate axis**. Tailwind rewrites a `--shadow-*` token as
`<geometry> var(--tw-shadow-color, <default>)` — verified by compiling a
probe, and it holds for multi-layer tokens — so a template writes
`shadow-md shadow-ai-accent/35`. A plain stylesheet can't add a utility,
so the role and glow tokens read `--glow-color`; that keeps the pipeline
canvas colour-coding nodes by type from CSS and lets the status rail be
painted per row from JS.
Also reclassified: `0 0 0 2px <colour>` is a **ring**, not a shadow, and
`0 1px 0 0 <colour>` is a **border**.
## color-mix, analysed by intent
All 331 occurrences were classified rather than treated as one problem:
- **160** mixed a token with `transparent` — only an alpha change, so
it's Tailwind's `/N` modifier written the long way (verified: `/N`
compiles to `color-mix(in oklab, <token> N%, transparent)`, and mixing
with `transparent` is premultiplied, so the interpolation space doesn't
matter)
- **81** blended two colours — a derived colour, so it's a token now
(~50 minted)
- **75** were shadow/ring geometry, handled by the scale
- **8 gradients + 1 `calc()`-driven percentage** genuinely have no
utility form and remain; the guard exempts gradient/mask/filter
contexts, matching what the style-block counter already exempts
## Merge with main
Main was running the same migration in parallel (**#13541 "px to rem"**
removed all SCSS, added `no-hardcoded-px`, and built its own shadow
scale), so the merge collided in 68 files / 155 hunks. Resolved by
taking main's source files and re-applying this branch on top, with the
guards driving convergence (429 → 0) rather than hand-resolving hunks.
On shadows, **main's structure wins** — geometry in `semantic.css`
`@theme`, colours as `--shadow-*-c*` sub-tokens, wrapped in its px
disable; its `--shadow-sm/md/lg`, `--shadow-scroll-*` and
`--shadow-glow` keep their definitions. This branch adds what main
didn't have: `xs`, the directional/inset roles, and the
elevation-matched coloured glows.
## New tokens
No new *colours* — each names a value a component already hardcoded,
because a registered palette utility (`text-orange-500`) is banned by
`rawPalette`, so "just register the ramp" was never available.
`--color-splitter-hover`, `--color-status-flaky` (+`-text`),
`--color-chat-bubble-user-border`, `--color-thread-avatar-bg`,
`--color-thread-accent-strong`, `--color-menu-badge-ring`,
`--color-progress-track-fill`, `--color-ingest-accent-*` (5), ~50
derived blend tokens, plus `--color-border-strong` /
`--color-border-subtle` / `--color-warning-300` registered for utility
use.
## Visual deltas to review
Everything else is value-preserving. These are not:
- **4 hover accents** move from a fixed `primary-600` to `accent` — 600
→ 400 **in dark only** (2 chevrons, an empty-state icon fill, one link)
- **sticky table header** in `TraceDetailsSidebar` was a fixed light
grey that never flipped; now `border-default`, so it themes
- **shadow canonicalization** snaps near-identical geometries together
and moves ~40 hardcoded `rgba(0,0,0,x)` onto the scale's token colour
Highest-surface single file: `AlertSettingsHelpDrawer.vue` — a whole
style block deleted with ~44 class names moved into template utilities.
## Verification
`lint:design` · `lint:tokens` · `lint:token-purity` · `lint:styles` ·
`format:check` · `lint:ci` · `vue-tsc` — all clean.
**94 spec files covering every file this branch touches: 4395 passing, 0
failing.**
Not run: the full 1236-file unit suite, Playwright/E2E, and any browser
visual check.
---------
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: Bhargav <BJP232004@GMAIL.COM>
## Summary
- update vulnerable web transitive dependencies (`axios`,
`brace-expansion`, `fast-uri`, `http-proxy-middleware`, `js-yaml`,
`uuid`, and `websocket-driver`)
- remove the unused `node-polyfill-webpack-plugin`, eliminating the
unpatched `elliptic` dependency chain
- update Playwright to 1.55.1 and `node-forge` to 1.4.0 in the UI test
lockfile
- adapt `BaseImport` to Axios 1.18's stricter response-header type
This is expected to resolve 39 of the 41 currently open Dependabot
alerts.
## Remaining alerts
- `rmcp` remains on the project's custom `rmcp-openapi` fork. Updating
it safely requires a coordinated change to the enterprise
`Cargo.toml.openobserve`, which currently replaces the OSS manifest
during enterprise builds and pins the old API. The compatible
intermediate upstream releases also lack the fork's required
`allOf`/`anyOf` support.
- `thrift` 0.17.0 remains through `parquet` 58.4.0. The patched Thrift
line (0.23+) is outside Parquet 58's version constraint. Parquet 59
removes the Thrift dependency, but adopting it requires a coordinated
Arrow/DataFusion 59 upgrade.
## Validation
- `npm ci` and `npm audit --package-lock-only` in `web/` (0
vulnerabilities)
- `npm ci` and `npm audit --package-lock-only` in `tests/ui-testing/` (0
vulnerabilities)
- `npm run type-check:app`
- `npm run lint:ci -- --quiet`
- `npm run lint:design:strict`
- `NODE_OPTIONS=--max-old-space-size=8192 npm run build`
- `npm run test:unit -- --run` (41,917 passed, 404 skipped)
- `npx vitest run src/components/common/BaseImport.spec.ts --environment
jsdom` (41 passed)
- Playwright test discovery under 1.55.1 (2,076 tests in 265 files)
- `cargo fmt --all -- --check`
- `cargo test -p openobserve-mcp` (94 passed, 1 ignored)
- `cargo audit` (0 vulnerabilities; 2 allowed unmaintained warnings)
Design at autocomplete/autocomplete.md
Rewrites the query editor's autocomplete across every surface that has
one, and
follows the field-value path far enough to fix the bugs it was hiding.
53
commits, in six phases; each phase was written test-first, reviewed
adversarially, and checked in a browser before the next one started.
## What was wrong
The completion providers were wired inconsistently. Six surfaces bound a
resolver that was never defined, so their value completion could not
resolve at
all — SLO, dashboards and anomaly detection among them. Traces never
bound
`:suggestions`. Alerts never called `updateStreamKeywords`. PromQL ran a
second
editor runtime purely for its grammar, and asked
`/prometheus/api/v1/series`
— the heaviest endpoint available — for data the stream schema already
has.
## Phases
**1 — icons, staleness, quoting.** Correct `CompletionItemKind` glyphs,
stream
keywords refreshed when the stream changes, `spath` quoting.
**2 — the function catalog.** Custom functions get one tab stop per
argument,
org VRL functions stop appearing twice, column types read from
`dataType` as
well as `type`.
**3 — signature help, hover, async completion.** Providers return real
`CompletionList`s with `incomplete: true`, so a list that arrives late
is
re-queried rather than pinned.
**4 — numeric ranking.** Numeric columns sort first inside a numeric
aggregate.
**5 — values for streams nobody has searched.** The value cache only
ever held
what a search had already returned, so a stream you had not queried
offered
nothing. Values are now fetched on demand, bounded (10s timeout, 60s
cooldown,
one in-flight request per key), and shared between the editor and the
builder.
PromQL joins the same path: labels from the stream schema, values from
the
shared cache. `/series` now has zero production callers.
**6 — one way to fetch a value.** Item 22 only, in this PR.
## The dashboard filter 400
Clicking the ✕ beside "Select Field" in a filter's dropdown sets the
column to
`{}`, and the watcher on `condition.column` then asks for that column's
values.
`loadFilterItem` built `fields: [row.field]` out of `undefined`,
JSON.stringify
turned that into `null`, and the server answered
```
400 {"code":400,"message":"SerdeJsonError# invalid type: null, expected a string"}
```
`_values_stream` answers 400, not an empty result, for each way a piece
of the
payload can go missing — and every one is reachable: an unresolved
stream alias
(`.find(…)?.stream`), a stream not yet chosen (`""`), a range not yet
set. The
range fails differently: `meta.dateTime` starts as `{start_time: ""}`
and
`""?.toISOString()` does not short-circuit, so it throws and both
callers
surface "Something went wrong!" for a lookup the user never asked to
fail.
Guarded at the two callers, where the payload is built and where the
throw
happens. Not at `fetchFieldValues` — a guard there is too late to stop
the
throw and would duplicate validation the callers need anyway. The
trigger is
left alone: re-picking a field must still load its values.
## Notable removals
CodeMirror is gone — it was still bundled for the PromQL grammar alone.
Bundle 26.3 MB → 25.43 MB; the PromQL chunks 442.7 KB → 20.6 KB. The
PromQL
term catalog went from 7 entries to 113, generated from Prometheus's own
tables
and vendored with its MIT notice.
## Verification
- 76 files, +10.5k/−1.4k, ~9,100 tests passing across the touched areas.
- `vue-tsc` clean, `npm run build` exit 0.
- Every phase checked in a running browser against a live instance,
including
the 400 above: the ✕ now produces no request and no toast, adding a
filter and
re-picking a field each still fetch once, and the value list still
populates.
## Known, deliberately out of scope
- A filter still fetches twice (once on add, once on open) — that is
item 23,
the cache read, not this PR.
- `useMetricsExplorer.loadFilterItem` has the same unguarded
`toISOString()`
pattern on a different surface.
- `services/metrics.ts` and `services/search.ts` still carry dead
`get_promql_series` wrappers now that nothing calls `/series`.
Design at: alerts_2.md
Per-group and per-series alerting, SLO measurement, and the UI for both.
## Alerts
**Multi-alerts (Feature 3).** A grouped alert can opt in to evaluating
and paging **per group** rather than collapsing to one verdict. Opt-in
only (`multi_alert`), so no existing alert changes behaviour: the flag
cannot be present in JSON written before it existed. Per-group state,
transitions, silence fingerprints, disappearance/reaping and a
cardinality cap, with the count-gate rules that make "most severe group"
and the legacy collapsed level provably the same verdict.
**Per-series alerting for PromQL.** The same feature for the metrics
family, where a group is one returned **series** keyed by its full label
set — that is a series' identity in Prometheus, and the expression's own
`by (…)` clause is already where the label set is chosen, so a second
picker could only disagree with it. One additive nullable column; NULL
means off, so stored alerts are untouched.
**Alert detail page**, replacing the row-click side panel: stat strip,
evaluation chart with threshold marklines, per-group table and history.
Simple alerts get a real history too — evaluations (one record per run)
by default, level changes alongside.
## SLOs
Count / time-slice / alert SLIs, error-budget and burn-rate arithmetic,
rolling windows, per-group slices with an exact rollup, coverage gating,
backfill, and SLO-based alerts.
Two capabilities the model could not express, both added tests-first:
- **Metrics-native counting** (`CountSource::PromQl`) — counter metrics
have no rows for a predicate to classify, so "good" exists only as
arithmetic between series, and correct counter arithmetic is
`increase()`.
- **Freshness semantics** (`absent_is_bad`) — for a pipeline SLO,
silence IS the failure, which the default gap rule can never report. A
*failed* query still freezes rather than pages; only a query that PROVED
emptiness counts as bad.
**SLO form**: live good/bad preview so a wrong predicate is caught
before saving rather than days later, real query editors with field
autocomplete, folders shared with alerts, and a move endpoint.
## Notes for review
- The SLO and composite features are **deferred (TODO)**:
`ZO_SLO_ENABLED` stays default **false** and the SLOs menu entry is
hidden until they ship; the SLO backend and UI remain in the tree behind
the flag, and composites stay pure logic + tests.
- One migration (schema 61): a nullable column for the PromQL opt-in. No
backfill.
- Two fixes outside the feature that it surfaced: query-editor
completions were duplicated per mounted editor (Monaco registers
providers per *language*), and the alert detail chart had no PromQL
branch, so it charted row counts against the series-count gate.
- Lands together with the matching o2-enterprise PR — `QueryType::Slo`
is new here and the enterprise dedup matches are exhaustive over it.
- `ratelimit::test_add_batch_missing_rule_id` fails intermittently on
`main` as well; unrelated to this branch.
---------
Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
Co-authored-by: hengfeiyang <hengfei.yang@gmail.com>
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
Applies the **Calm Signal** colour language and summary stat strips
across the monitoring + AI-evaluation pages.
- Colourful summary stat strips (`OStatStrip`) with selectable filter
tiles + left row rails / exception washes on **Alerts, Incidents, Eval
Jobs, Scorers, Score Configs**.
- Tile tones match their `OTag` chip colours; critical-first (left)
ordering; re-click a tile to clear the filter.
- AI Observability: consistent section headers, detail drawer, and
per-session signals.
- Migrate `ScorerTypeDialog` to the standard `ODialog`; token-compliance
+ vertical separators on the eval form pages.
- Table perf: lazy tooltips, delegated row shortcuts, virtualization.
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.11 to
3.4.12.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cure53/DOMPurify/releases">dompurify's
releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.4.12</h2>
<ul>
<li>Fixed an issue where a hook would not get called for custom
elements, thanks <a
href="https://github.com/Rikuxx0"><code>@Rikuxx0</code></a></li>
<li>Hardened the handling of hooks removing elements, <a
href="https://github.com/mkrause-bee360"><code>@mkrause-bee360</code></a></li>
<li>Added support for a few new SVG attributes, thanks <a
href="https://github.com/cbn-falias"><code>@cbn-falias</code></a> &
<a
href="https://github.com/Develop-KIM"><code>@Develop-KIM</code></a></li>
<li>Hardened the handling of declarative partial updates</li>
<li>Updated the documentation is several spots, README, wiki, etc.</li>
<li>Bumped several dependencies where possible</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cure53/DOMPurify/commit/a9ca1e537422319a557a9a2aa61f003b23b4a197"><code>a9ca1e5</code></a>
release: 3.4.12 (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1537">#1537</a>)</li>
<li>See full diff in <a
href="https://github.com/cure53/DOMPurify/compare/3.4.11...3.4.12">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/openobserve/openobserve/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>