Commit Graph
6450 Commits
Author SHA1 Message Date
Subhra264 77a4fc5f32 fix: backfill should work if only remote destination is present (#13152)
## What

Adds backfill support for pipelines whose only destination is a **remote
destination**.

## Why

Backfilling a remote-only pipeline failed at runtime:
`get_destination_stream_from_pipeline` only recognized local `Stream`
nodes, so a pipeline with only a `RemoteStream` destination returned "No
destination streams found". That error path **deleted the scheduler
trigger** while keeping the `backfill_jobs` config row, which then
surfaced as:

- `GET .../backfill/{job_id}` returning **404** (`DbError# key
<org>/backfill/<job_id> does not exist`)
- the backfill row **disappearing from the UI** on the next refresh

Remote destinations were never actually unsupported by execution —
`ExecutablePipeline::process_batch` (already called by the backfill
handler) routes remote records to the remote WAL. The pipeline was just
being rejected by an over-strict pre-execution gate before it could run.

## Changes

- `get_destination_stream_from_pipeline` now returns an empty list (not
an error) for a remote-only pipeline, and only errors when there is no
destination leaf of either kind. Remote records continue to be handled
inside `process_batch`.
- Added a defensive guard so `delete_before_backfill` is skipped (with a
clear log) when a pipeline has no local destination streams — the
edit-after-create case. Creation already rejects
`delete_before_backfill` for remote pipelines with a user-facing
message.
- Added unit tests for remote-only (→ `Ok(empty)`) and mixed
local+remote (→ locals only) pipelines.

## Testing

- `cargo test -p openobserve get_destination_stream` → 8 passed.
- ⚠️ Enterprise build not compiled locally (private `o2_enterprise`
crate unavailable). The remote-WAL write path should be smoke-tested on
an enterprise build with a real remote-only pipeline backfill before
merge.

## Out of scope

The pre-existing trigger-orphaning inconsistency (get 404s while list
silently skips when a trigger is missing) is not addressed here.
2026-07-13 12:23:21 +00:00
ktx-mihirandAbhay Padamani e483e26b9d fix: pipeline module form migration with zod (#13077)
- form migration with zod schema for pipeline module

---------

Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-13 12:03:24 +00:00
ktx-mihirandAbhay Padamani 44866b482e fix: billing and others form migration with zod (#12905)
- form migration with zod schema for billing , marketplaces and other
module

---------

Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-13 11:38:38 +00:00
ktx-vaidehi 67aec54d27 fix: ui header inconsistency (#13150) 2026-07-13 11:33:39 +00:00
ktx-vaidehi 75fec14891 fix: prevent dialog focus loss from leaking page shortcuts (#13157)
OSelect's trigger lacked role="combobox", so a focused select wasn't
recognized as an input by the shortcut guard — pressing a single-letter
key (e.g. "s" on logs) fired the page-level shortcut instead of typing
into the select. ODialog's auto-focus also skipped selects entirely and
never re-focused after a body swap (e.g. Create/Update toggle), leaving
focus on a button where keystrokes leaked past the modal.

- OSelect: mark the trigger role="combobox" and add type-to-search so a
focused, closed trigger opens the dropdown and seeds the filter.
- ODialog: auto-focus falls back to a combobox trigger when there's no
text field, re-focuses the first field after body content swaps, and now
swallows any bare printable keydown that doesn't land in an input-like
element so page shortcuts can never fire while the dialog is open,
regardless of what has focus.
2026-07-13 11:29:26 +00:00
Subhra264 13c0c77703 feat: sso user blocklist (#13119)
Design at: security/domain-management
2026-07-13 08:01:02 +00:00
Ashish Kolhe 4cdad21f50 feat(org): org deletion with grace period, resurrection & cleanup (#13092)
design at: cloud/org%20deletion/org-deletion-design.md

## Summary

Self-service cloud org deletion with a **recoverable grace period**,
followed by complete, retryable, auditable hard cleanup.

**Lifecycle:** `active → pending_deletion → deleting → gone`

- **Delete** soft-deletes into `pending_deletion` — data intact, org
hidden from the switcher + all API access blocked, `deleted_at`
recorded. With `grace=0` it deletes immediately (legacy path).
- A **promotion scheduler** flips expired pending orgs to `deleting`
after the grace window; the background **cleanup worker** then tears
down all data across object store, file_list, DB, OpenFGA, and billing —
per-step tracked and retried (`MAX_ATTEMPTS`).
- **Resurrect** (`_meta`/root only) flips `pending_deletion` back to
`active` — no data was ever touched.

## Key changes

- New `org_cleanup_tasks` ledger table +
`organizations.status`/`deleted_at` columns (2 migrations);
`DB_SCHEMA_VERSION` 47→48.
- `OrgStatus::PendingDeletion` cache + coordinator events + watch;
`is_blocked` middleware hides & blocks pending/deleting orgs.
- Grace window from enterprise `O2_ORG_DELETION_GRACE_PERIOD_DAYS`
(default 30), read from OSS behind `#[cfg]`.
- Cleanup worker + promotion scheduler run on **compactor** nodes;
dist-lock + atomic CAS make execution exactly-once cluster-wide. Task
rows purged immediately on hard-delete completion and on resurrect (no
retention sweep).
- Audit-trail entry to `_meta` on every status transition
(fire-and-forget).
- Resurrect endpoint (dual-impl, cloud-gated) + list surfaces
`deleted_at`/grace days.
- UI: pending-deletion badge + countdown + resurrect action,
deletion-progress dialog, Danger Zone restyle with type-to-confirm.

## RBAC

In-handler role checks (no OFGA middleware): **delete** = root OR org
Admin; **resurrect** = root only via `_meta`.

## Companion PR

Enterprise side: `openobserve/o2-enterprise` #2128 (config + ofga +
cloud billing).

## Testing

Unit suites pass; full flow live-verified end-to-end (soft-delete →
resurrect with data intact, grace=0 immediate delete, audit entries,
RBAC 403s).

> Requires ≥1 compactor node running for deletions to progress (always
true for single-binary / `ZO_NODE_ROLE=all`).
2026-07-13 07:54:16 +00:00
ktx-mihir efb12a59d8 fix: form migration of ai toolsets module with zod (#12945)
- form migration with zod schema ai toolsets module
2026-07-13 07:33:02 +00:00
Bhargav ff5ddaf6f2 fix: tooltip fix on logs histogram (#13146) 2026-07-13 05:52:58 +00:00
Bhargav 3dfdaa4cc6 fix: disappearing fields on collapse (#13145) 2026-07-13 05:17:26 +00:00
Hengfei YangandClaude Fable 5 99c571b561 perf: parallelize PromQL aggregation grouping and large-group accumulation (#13136)
## Summary

Follow-up to #13109. Two aggregation-side bottlenecks showed up at high
cardinality (3.17M series), both single-threaded loops that scale with
series count:

1. **Parallel group signature computation** — `eval_aggregate` computed
each series' grouping signature (clone projected labels + hash)
sequentially: 1.17s of a ~3.8s `sum by(le, path)` query. Now fanned out
with rayon (~95ms); `group_series_by_labels` (used by `topk`/`bottomk`)
gets the same treatment.
2. **Parallel chunked accumulation for huge groups** — an aggregation
without a modifier (`sum(rate(m[5m]))`) puts every series into a single
group, so the "parallel per-group" aggregation ran on one thread: ~250ms
for 66M samples at a 15-min window, and 2-3s at a 1-hour window. Large
groups (≥64k series) are now split into 32k-series chunks aggregated in
parallel and merged via a new `Accumulate::merge` (implemented by all 9
accumulators; equivalence covered by a unit test).

Plus a refactor commit: `eval_aggregate` had reimplemented
`group_series_by_labels` inline just to keep per-series projected labels
it only needed once per group — both now share `projected_labels()`, and
a 3.17M-entry `(hash, Labels)` side vector (and its drop) disappears.

## Benchmarks

Same setup as #13109: single node (M-series Mac, 32GB), 1.34B rows /
3.17M series histogram bucket stream, result cache disabled, warm = mean
of runs 2-4.

**15-minute window:**

| Query | main (#13109) | this PR | speedup |
|---|---|---|---|
| `histogram_quantile(0.9, sum by(le,path)(rate(m[5m])))` | 3.81s |
3.02s | **1.26x** |
| `count by(path) (rate(m[5m]))` | 3.54s | 2.86s | **1.24x** |
| `sum by(region) (rate(m[5m]))` | 3.40s | 2.83s | **1.20x** |
| `max(rate(m[5m]))` | 2.60s | 2.34s | 1.11x |
| `sum(rate(m[5m]))` | 2.69s | 2.46s | 1.09x |
| `topk(5, rate(m[5m]))` | 10.37s | 10.39s | parity |
| selective filter (132k series) | 0.46s | 0.45s | parity |

**1-hour window (1.2B samples):** `sum(rate(m[5m]))` 12.1s → 8.6s
(**1.4x** — the single-threaded accumulation scales with samples, so the
fix matters more at longer ranges), `histogram_quantile` 13.0s → 11.8s,
`sum by(region)` 14.0s → 13.3s. Peak RSS unchanged.

Output verified against main: `max` bitwise identical; `sum`/`avg`
differ by ≤1.7e-11 relative (float summation order over 3.17M values;
run-to-run noise on main alone is ~1e-14).

## Tried and rejected (kept out of this PR)

Each was implemented, benchmarked A/B on the same dataset, and reverted
— recorded here so they aren't retried blindly:

- **Pre-reserving the partition-map merge**: zero effect — the merge is
hidden behind the slowest scan partition.
- **O(1) rate windows via a counter-reset prefix**: only -2~4%
end-to-end; the per-window reset rescan is cheap branch-predictable
compares, and the per-series prefix allocation ate the savings.
- **DataFusion `array_agg` for the sample load**: +17-55% slower — DF's
vectorized grouping is fast, but converting its Arrow list output into
engine structures is a second full materialization.
- **Sharded concurrent series map instead of `RepartitionExec`**:
+10-14% slower — the repartition copy is vectorized and buys each
consumer a purely local, cache-friendly map build.

Conclusion from the last two: the sample load (~60% of a 1-hour query)
is at a local optimum for the current row-at-a-time `Vec<Sample>` model;
further gains there need a columnar (SoA) sample layout, which is out of
scope for this PR.

## Notes

- Query-side only; no storage or config changes, no new env vars.
- `Accumulate` gains `merge`/`into_any`; all merges are
order-insensitive combines (add / min / max / union / concat).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 08:33:05 +00:00
Huaijin 147649e548 refactor: reduce some warm up for index search (#13135) 2026-07-11 01:44:24 +00:00
Subhra264andHengfei Yang 22177453eb fix: enrichment table query should ignore max query range (#13131)
Enrichment table queries (top k values and search) should ignore max
query range because enrichment tables, even stored as a stream in
backend, are not exactly logically a stream. It contains the data used
for enrichment like a csv file.

Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-07-10 13:19:11 +00:00
dependabot[bot] 1aabaf23cd chore(deps-dev): bump ws from 7.5.10 to 7.5.11 in /web (#13134)
Bumps [ws](https://github.com/websockets/ws) from 7.5.10 to 7.5.11.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/websockets/ws/releases">ws's
releases</a>.</em></p>
<blockquote>
<h2>7.5.11</h2>
<h1>Bug fixes</h1>
<ul>
<li>Backported 2b2abd45 to the 7.x release line (e14c4586).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/websockets/ws/commit/fd36cd864fcdf62a08273a99e19a7d975401fee8"><code>fd36cd8</code></a>
[dist] 7.5.11</li>
<li><a
href="https://github.com/websockets/ws/commit/e14c45861deca0cef60dec0f9109b694abebdf52"><code>e14c458</code></a>
[security] Limit retained message parts</li>
<li>See full diff in <a
href="https://github.com/websockets/ws/compare/7.5.10...7.5.11">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ws&package-manager=npm_and_yarn&previous-version=7.5.10&new-version=7.5.11)](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>
2026-07-10 11:22:02 +00:00
Huaijin afa3569020 perf: optimize simple select to only read limit records (#13116)
## Summary

This PR improves the Tantivy `SimpleSelect` path for queries using
`ORDER BY _timestamp ASC|DESC LIMIT n`.

Previously, each index file could return up to `limit` rows, causing
extra Parquet reads. The new path collects `(_timestamp, doc_id)`
candidates, safely stops when remaining file groups cannot produce
better results, and merges the global Top-N before building row
selections. On the fully indexed path, only the global `LIMIT`
candidates reach Parquet.

## Performance
https://github.com/openobserve/o2-ck-benchmark use this repo to generate
100M recores.

```sql
SELECT
  *
FROM
  k8s_logs
WHERE
  kubernetes_pod_name = 'api-gateway-9a2c1e6f4-8923f' 
ORDER BY _timestamp DESC 
LIMIT 100
```

**main branch: 550ms
this branch: 250ms**

## Changes

- Add `SimpleSelectPruner` for safe early stopping, global Top-N
merging, and file/row pruning.
- Add timestamp-aware candidate results and cache entries.
- Fall back to full row-ID collection and DataFusion filtering when
Tantivy skips any condition.
- Conservatively retain unindexed, overlapping, or timestamp-tied files
unless they are provably dominated.
- Avoid caching results for files only partially covered by the query
time range.
- Separate SimpleSelect, SimpleCount, and generic row-count aggregation,
with minor query and scan-repartition cleanup.

## Correctness

- Supports both ascending and descending timestamp order.
- Uses strict timestamp bounds so ties are not pruned incorrectly.
- Keeps uncertain files when fewer than `limit` exact candidates are
available.
- No public API, configuration, or wire-format changes.

## Testing

- `cargo test --lib service::search::tantivy::` — 84 passed.
2026-07-10 10:26:08 +00:00
12f8e134ff test(autoe2e): generated E2E tests for #11813 (logs-visualize-persistence) (#13010)
🤖 Auto-generated E2E tests for #11813 — **healed to passing** against a
live OpenObserve before this PR was opened.

- Branch `autoe2e/pr-11813` (stacked on `main`), so the tests run
against the feature code and pass before it merges.
- Spec:
`tests/ui-testing/playwright-tests/Logs/logsVisualizePersistence.spec.js`

The repo Playwright CI re-validates here. Review is for **quality +
coverage** — the tests already pass.

_Generated by the E2E Council pipeline._


**Test cases added / changed:**

| Test case | Type | What it verifies |
|---|---|---|
| `should persist live mode / auto-run toggle across navigation` |
extend | live-mode toggle state-guard added before navigation to catch
silent no-op when auto_query_enabled is false |

---------

Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-07-10 10:12:34 +00:00
Simran KumariandHengfei Yang 9accadd02f fix: Update awsIntegrations.ts (#12980)
update dashboard link for AWS RDS

---------

Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-07-10 10:08:13 +00:00
a9b67f7b8b chore: update translations from en.json (#13099)
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).

🤖 Generated with automated translation workflow

Co-authored-by: oasisk <35160958+oasisk@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-07-10 09:00:05 +00:00
Ashish Kolhe ddf1935e5c chore: enable-usage-reporting CTA + default reporting OFF (#13128)
## What

Adds an **"Enable usage reporting" call-to-action** to the Cloud Billing
→ Usage tab for organizations that have self-usage reporting turned off,
a **"waiting for usage data" illustrated state** for orgs that have it
on but haven't reported any usage yet, and flips the per-org default for
the setting to **OFF** so the CTA is the entry point.

Previously, when `usage_stream_enabled` was off the Usage tab showed
only the billing-cycle cards and nothing where the daily chart would be.
And when it was on but the org's `usage` stream didn't exist yet, the
panel showed a raw `Search stream not found: usage` error. Now:

- **Reporting OFF** → an illustrated empty state (`OEmptyState`,
`wave-bars`) invites the user to enable reporting. Clicking **Enable**
opens a `ConfirmDialog` explaining the per-org, reversible effect before
the merge-and-POST enable flow runs.
- **Reporting ON, no data yet** → the raw "stream not found" error is
suppressed (on this panel only) and replaced with an illustrated
**"Waiting for usage data"** state. When usage lands, the search
succeeds, the error clears, and the daily chart shows through
automatically.
- **Reporting ON, has data** → unchanged: the daily line chart
(Ingestion + Search + Pipelines) renders.

## Behavior change (please review)

`default_usage_stream_enabled()` now returns **`false` in every build**
(previously cloud builds defaulted to ON when
`ZO_USAGE_REPORT_TO_OWN_ORG` was set). `ZO_USAGE_REPORT_TO_OWN_ORG`
remains the deploy-wide master switch but no longer flips the per-org
default. This is the intentional pairing with the CTA — an "enable"
prompt only makes sense when reporting is off by default. Explicit
stored values (opt-in or opt-out) still win in every build.

## How the waiting state is scoped

The "stream not found" suppression is set via `error_config` **only on
the usage panel's schema** (`usageDailyPanelSchema.ts`) —
`custom_error_handeling: true` with no `custom_error_message` silences
PanelSchemaRenderer's own error blocks. `usage.vue` catches the panel's
`@error`, detects the stream-not-found case specifically (unrelated
query errors still surface), and overlays the graphic. No other chart is
affected.

## Testing

- `web`: 417/417 billing specs pass, incl. new coverage for the enable
CTA, merged-settings POST, the schema silencing the renderer, and the
waiting overlay showing only for stream-not-found (not other errors).
- `cargo check -p openobserve` (non-release): clean.

## Note

These commits were cherry-picked onto a fresh branch from `main` — the
original branch (`chore/usage-ui-cloud-changes`) had already merged its
base work via #13112 and carried divergent, pre-squash history that
would have produced a conflicting diff.
2026-07-10 14:46:36 +05:30
23d8e19826 fix(clipboard): prevent false-success toast when execCommand copy silently no-ops inside a focus-trapped drawer (#13076)
## Summary

Fixes #13062.

On deployments served over plain HTTP (`isSecureContext = false`) the
Clipboard API is disabled, so `writeClipboard` falls back to
`document.execCommand("copy")`. The hidden textarea was appended to
`document.body` — outside reka-ui's `FocusScope` subtree — so the
drawer's focus trap immediately reclaimed focus, cleared the textarea
selection, and `execCommand` ran on an empty selection. Chrome returns
`true` for an empty-selection copy, so no error was thrown and the
"Content Copied Successfully!" toast appeared even though nothing was
copied.

## Changes (`web/src/utils/clipboard.ts`)

**1. Append inside the dialog, not `document.body`**

```ts
const activeEl = document.activeElement as HTMLElement | null;
const container: HTMLElement =
  activeEl?.closest<HTMLElement>('[role="dialog"]') ?? document.body;
container.appendChild(textarea);
```

The temporary textarea is now inserted inside the nearest
`[role="dialog"]` ancestor (the drawer's `DialogContent`). The
`FocusScope` trap does not fire for focus changes that stay within its
own subtree, so the selection survives until `execCommand` runs.

**2. Gate the copy on focus still being held**

```ts
const focused = document.activeElement === textarea;
const copied = focused && document.execCommand("copy");
if (!copied) throw new Error("execCommand copy failed");
```

If focus was reclaimed (edge case) the copy is a known no-op; we throw
so `copyToClipboard` shows an error toast instead of a false-success.

**3. Fall through from `navigator.clipboard` on permission error**

The `navigator.clipboard.writeText()` call is now wrapped in its own
`try/catch` so a `NotAllowedError` (permission denied) falls through to
the `execCommand` path rather than propagating as an unhandled
rejection.

## Test plan

- [ ] HTTP deployment (non-secure context): open Source Details drawer →
click "Copy to clipboard" → paste confirms content is copied; no
false-success toast on failure
- [ ] HTTPS / localhost: `navigator.clipboard` path still used;
behaviour unchanged
- [ ] Deny clipboard permission in browser settings on HTTPS: falls
through to `execCommand` path gracefully

Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-07-10 08:54:57 +00:00
Hengfei Yang 7795a7bc53 perf: speed up PromQL queries by eliminating redundant series label work (#13109) 2026-07-10 16:23:05 +08:00
ktx-vaidehiandsai nikhil kethe 467dd0ed55 fix: backfill number input save (#13124)
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
2026-07-10 06:44:31 +00:00
Ashish Kolhe 380c24e628 fix(dashboards): keep Home pin in sync on move/delete (o2-enterprise#2139, #2140) (#13121)
## Summary

Fixes the "pin dashboard to Home" feature going stale when the pinned
dashboard is **moved** or **deleted**.

- **o2-enterprise#2139** (P1) — pin a dashboard, delete it, click the
pin → **404**.
- **o2-enterprise#2140** (P2) — pin a dashboard in Folder B, move it to
Folder A, click the pin → navigates to the **stale folder**.

### Root cause
The pin is stored as an org setting `home_dashboard` holding a static
snapshot `{ dashboardId, folderId, label }`, written once at pin-time
and never reconciled with the dashboard's live state. Both issues are
instances of the same class: a stored reference that drifts out of sync
on move/delete/rename.

## Approach — defense in depth

**L1 — authoritative server-side reconciliation (root fix).**
`move_dashboard` / `delete_dashboard` (OSS
`src/service/dashboards/mod.rs`) now keep the `home_dashboard` org
setting in sync at the mutation source:
- **move** → update the stored `folderId` to the destination folder.
- **delete** → clear the setting.

This is authoritative across every client and API caller (incl. **bulk
delete**, which loops `delete_dashboard`). Reconciliation is
**best-effort** — it runs *after* the primary op succeeds and
logs-and-swallows any error, so it can never fail the move/delete. No
`folder_id` is added to the GET response (no folder-membership info
leak).

**L2 — read-side re-sync (cross-system convergence).** The FE re-reads
the now-authoritative setting when the pinned tab is opened and when the
Dashboards page mounts, so a snapshot that went stale on **another
system** self-corrects before navigation.

**L3 — graceful degradation.** A deleted pin clears + toasts instead of
routing to a 404 (both the Home pinned tab and the Dashboards shortcut
button). Deliberate unpin (× button) stays silent.

## Deferred (follow-ups, out of scope)
- **Super-cluster cross-region reconciliation** — the replication queue
consumer (`src/super_cluster_queue/dashboards.rs`) applies moves/deletes
via table ops directly (bypassing the service layer where L1 lives), and
`system_settings` is not super-cluster-replicated. In a multi-region
deployment the non-originating region's pin isn't reconciled on **move**
(delete still converges via L3). Follow-up: add the same reconcile calls
to the queue consumer.
- **403-on-move (RBAC)** — moving a pin into a folder the current user
can't access. L3 handles 404 only for now; whether to clear the org-wide
setting vs. hide per-user is a separate design.

## Testing
- **Backend:** 6 new unit tests for the reconcile decision logic
(match/no-match, folder-changed, malformed value). `cargo test -p
openobserve home_pin` ✅
- **Frontend:** extended `HomeView.spec.ts` — re-sync on tab activation,
error toast on unavailable, silent deliberate unpin. `HomeView` +
`PinnedDashboardTab` specs: **40 passing** ✅
- **rcheck:** `cargo fmt --all --check` + `cargo clippy -- -D warnings`
clean ✅
- The `openHomeDashboard` shortcut guard is a small guard mirroring the
already-tested `PinnedDashboardTab` unavailable path; it's covered by
manual repro rather than a brittle full-mount of the 1400-line
`Dashboards.vue`.

### Manual reproduction
- **#2140:** pin in B → move to A → pin opens in **A** (correct
URL/breadcrumb); setting shows `folderId: "A"`.
- **#2139:** pin → delete → click pin/shortcut → no 404, toast,
pin/tab/shortcut gone; setting removed.
- **Cross-system:** move on client B while client A sits on Home/AI tab
→ A opens the pinned tab → resolves to the new folder.

## Layer compliance
All changes are OSS and unconditional (no enterprise gating, no
migrations, no new HTTP handlers) — `home_dashboard` is a generic system
setting readable by both builds. Reconciliation lives in the service
layer and reuses `src/service/db/system_settings.rs`.
2026-07-10 06:12:52 +05:30
Ashish Kolhe c2ac5fb89c chore: usage ui cloud changes (#13112)
## What

Adds a **daily usage view** to Cloud Billing → Usage for orgs with usage
reporting enabled, layered on top of the existing billing-cycle cards.
Cloud-only.

### Frontend (cloud billing components)
- **Merged into the existing Usage tab** (no separate tab): the billable
usage cards are always shown; when self-usage reporting is on, a
dashboards **date-range picker** (in the toolbar next to the GB/MB
toggle) drives **both** the cards and a daily chart.
- **Daily line chart** — a single query via `PanelSchemaRenderer` over
the org's own `usage` stream, broken down by event into **Ingestion /
Search / Pipeline / Remote Pipeline** lines (connect-nulls on).
- **Six billable usage tiles** on one row with **automatic unit
scaling** (GB→TB→PB / MB→GB→…) so large numbers stay readable; AI
Credits shown as a count.
- Cards re-fetch over the calendar range (derived `<N>days`, hardened
against bad values).

### Backend (all cloud-gated)
- `usage_stream_enabled` **defaults to `ZO_USAGE_REPORT_TO_OWN_ORG`** in
cloud builds, so orgs report to their own `usage` stream by default; a
per-org opt-out persists an explicit `false`. OSS / self-hosted default
stays `false`.
- **Reserved self-reporting streams** (`usage`, `stats`, `triggers`,
`errors`, `data_retention_usage`) are blocked from user **create /
ingest / delete** — cloud-only; the internal self-reporting writer
(`IngestionRequest::Usage`) is exempt.
- `get_org_usage`: a **missing `usage` / `data_retention_usage` stream
is tolerated** (treated as zero) instead of failing the whole request
with a 400.

## Notes
- No enterprise-repo or DB-migration changes; backend logic stays in OSS
behind `#[cfg(feature = "cloud")]`, per the OSS/Enterprise layer map.
- `Cargo.toml` / `Cargo.lock` are intentionally **not** included (local
`downloadO2.sh` dev-setup).

## Testing
- `cargo check` (OSS) and `cargo check --features cloud` — pass; `cargo
fmt` clean.
- Frontend: **411 billing unit tests** pass; `vue-tsc` typecheck clean;
production build succeeds.
2026-07-09 14:51:01 +00:00
ktx-vaidehi 5ec3e960c7 fix: ui improvement (#13106) 2026-07-09 11:00:22 +00:00
Abhay Padamani d04df5c72d fix: multi sql variable improve ux (#13114)
1. Variable dropdown closes automatically after selecting "Select All"
in multi-select mode.
2. not able to unselect all.
3. for multi select dropdown, requires to click 2 times to close
dropdown list.
2026-07-09 10:29:14 +00:00
Huaijin 42c7aec0c8 chore: make region leader metrics better (#13113)
- close https://github.com/openobserve/o2-enterprise/issues/2100
2026-07-09 08:44:30 +00:00
Abhay Padamani 4eda24e4af build: filter console logs by feature group in dev and strip log/debug/info from production builds (#13105)
- #13102

## Logging: dev filtering + production strip

### Setup

1. Add to `web/.env` (git-ignored):
   ```ini
VITE_DEBUG_GROUPS=dashboards,auth:* # comma-separated, * wildcard
supported
   ```
2. Run `npm run dev` (editing `.env` auto-restarts Vite — just reload
the page).
3. Log with the feature group as the first argument:
   ```ts
   console.debug('dashboards', 'panel refreshed', panelId);
   ```
4. In DevTools, enable the **Verbose** level to see `console.debug`.

### Default behavior (`VITE_DEBUG_GROUPS` unset)

- **Dev:** all `console.log/debug/info` are hidden — logging is opt-in
per feature group.
- **Production build:** `console.log/debug/info` are always stripped
from the bundle, regardless of env.
- `console.error` / `console.warn` are never filtered or stripped.

Design At: designs/ui/feature-group-console-logging.md
2026-07-09 07:54:18 +00:00
232642bef2 fix: form migration with zod for management module (#12877)
- form migration with zod schema for management module

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-09 06:59:45 +00:00
Omkar Kesarkhane be8e0adda3 fix: Removed native scrollIntoView from SpanBlock onMounted as it was blocking scroll (#12837)
## What changed

- **Removed** the native `scrollIntoView()` call from `SpanBlock.vue`'s
`onMounted` hook, which was fighting the user's manual scroll in a
virtualized list.
- **Added** `cancelScroll()` to `TraceTree.vue` — clears pending
timeouts and aborts the TanStack virtualizer retry loop by resetting
`currentScrollToIndex`.
- **Wired cancellations** in `TraceDetails.vue` — calls
`traceTreeRef.value?.cancelScroll?.()` when the sidebar closes, trace
details reset, or a new trace is loaded.
- **Added `onBeforeUnmount`** cleanup in `TraceTree.vue` so a queued
scroll can't fire against a torn-down virtualizer.
- **Added regression tests** in `SpanBlock.spec.ts`,
`TraceTree.spec.ts`, and `TraceDetails.spec.ts` covering all
scroll-cancellation paths.

## Why

When `TraceDetails.vue` is opened with a `span_id` query parameter,
`SpanBlock.vue` called `element.scrollIntoView()` on mount. Because rows
are virtualized via `@tanstack/vue-virtual`, a `SpanBlock` remounts
every time it scrolls into the viewport — so each remount snapped the
viewport back to the pre-selected span, fighting the user's manual
scroll. The issue only manifested when a `span_id` was pre-selected at
page load.
2026-07-09 05:33:35 +00:00
Omkar KesarkhaneandClaude Sonnet 5 9a8aa0a11d fix: Traces run query button UI fix (#13103)
## What changed

- **`web/src/plugins/traces/SearchBar.vue`**: Fixed the Run Query button
by changing `variant="ghost"` to `variant="primary"`, restoring its
primary action styling (matches Logs SearchBar behavior).
- **`web/src/components/TenstackTable.vue`**: Fixed skeleton loader
background color to match the logs version. Added
`bg-(--o2-log-table-row-bg)` and `border-(--o2-log-table-row-border)`
tokens to skeleton rows. Replaced inline shimmer gradients with a
reusable `.o2-skel-pill` CSS class that uses OTable skeleton token
variables (`--color-skeleton-base`, `--color-skeleton-highlight`).

## Why

The Traces Run Query button had lost its primary styling and appeared
visually broken — it was rendering as a ghost button instead of the
prominent primary action button. The generic `TenstackTable.vue`
skeleton loaders had a different background color than the logs-specific
`TenstackTable.vue`, causing visual inconsistency.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 05:19:08 +00:00
udev 085c1881a0 fix: eval pipelines should not block ingestion (#13079) 2026-07-08 14:04:11 +00:00
ktx-vaidehi 3ebb8158f9 fix: table listing missing refresh and column toggle (#13065) 2026-07-08 12:53:23 +00:00
ktx-mihirandAbhay Padamani 205d77f007 fix: dashboard form migration with zod (#12906)
- form migration with zod schema for dashboard module

---------

Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-08 11:32:35 +00:00
Alexander peczon 78a6f1605f fix: allow Teams workflow webhook URLs (#12872) 2026-07-08 19:39:14 +08:00
ktx-akshay 39c1d8ae83 test: reduce Logs-Builder-Basic e2e runtime by removing dead waits and duplicate setup (#13080)
## Reduce `Logs-Builder-Basic` e2e shard runtime — ~10 min → ~5 min

### 🎯 Result (measured on CI)

| Metric | Before | After | Saved |
|--------|--------|-------|-------|
| **`Logs-Builder-Basic` shard** | **10m 05s** | **5m 12s** | **~4m 53s
(≈48% faster)** |

The shard runtime is cut roughly in half. Most of the old time was fixed
sleeps, waits that always ran their full timeout, and duplicated
per-test setup — not actual test logic. **No test cases, steps, or
assertions were removed.**

### What we changed

| # | Area | Before | After | Saving |
|---|------|--------|-------|--------|
| 1 | `initQueryBuilderTest` setup | Logs page loaded twice (helper
`goto` + `selectStream` navigates again) | Navigate once | 1 page load ×
~41 tests |
| 2 | `waitForBuildTabLoaded` | Always waited up to 10s for editor to
fill, even when it's meant to be empty | Skip when SQL mode is off |
~10s dead wait on many tests |
| 3 | `enableSqlModeIfNeeded` | 2s watcher wait + 500ms sleep, even
where the watcher never fires | Gated to Build tab; waits on real signal
| ~2.5s × ~35 tests |
| 4 | `disableSqlModeIfNeeded` | Fixed 500–600ms sleeps | Deterministic
wait on editor update | Blind sleeps removed |
| 5 | `applyQueryButton` | Clicked Run blindly | Waits for button ready
first | Fewer early-click retries |
| 6 | `pagination.spec` | Fresh stream ingest + index-wait per test (9×)
| One ingest per worker, reused | 8 redundant ingests + waits |
| 7 | `pagination.spec` | Dead `waitForTimeout(2000)` per streaming test
| Removed | ~2s × 5 tests |
| 8 | `unflattened.spec` | Search run twice with 3s + 1s sleeps | Single
run | ~5s × ~8 calls |
| 9 | `unflattened` row scan | Fixed 3s sleep per row | Wait for content
to render | 15-row scan: ~97s → seconds |
| 10 | `unflattened.spec` | Blind sleep after ingest | Poll until stream
queryable | No over-waiting |

### Bonus

Items 2–5 live in the shared `logsPage` page object, so
`Logs-Builder-Advanced`, `Logs-Core`, and `Logs-Features` shards get
faster for free too.

### Notes

| Item | Detail |
|------|--------|
| `playwright.yml` | Unchanged — shard layout stays as-is |
| Assertions | Preserved everywhere (e.g. pagination still checks the
same record counts) |
| Validation | All 52 tests still discovered by `playwright --list`;
shard confirmed green on CI at 5m 12s |
2026-07-08 09:47:08 +00:00
Huaijin 3c0b5196b0 perf: cache fast field header in the footer_cache (#13074)
## Summary

Fast fields are used by search optimizations such as timestamp
filtering, histograms, multi-histograms, and top-N queries. Previously,
`build_footer_cache` only warmed indexed field metadata, so fast field
access could still trigger additional reads later. This change primes
fast field handles during footer cache construction so those reads can
be served from the cached directory.

## Changes

- Warm fast field handles while building the puffin footer cache.
- Refactor `warm_up_terms` to operate on a `SegmentReader`.
- Update the Tantivy search warm-up call site for the new
`warm_up_terms` signature.
- Add a unit test to verify footer cache generation includes fast field
cache entries.

## Testing

- Added `test_build_footer_cache_with_fast_field`.
- Ran formatting and clippy fixes.
2026-07-08 08:51:29 +00:00
Bhargav 223a748b9f fix: translation workflow improved (#13091) 2026-07-08 08:30:55 +00:00
Hengfei Yang 0b6558d062 fix: skip missing OTLP histogram optional stats (#13082)
## Summary

- Skip OTLP histogram `_sum`, `_min`, and `_max` records when the
corresponding optional stat is absent.
- Preserve existing `_count` and bucket records.
- Add coverage for histograms whose optional stats are missing.

## Root Cause

OTLP `HistogramDataPoint` defines `sum`, `min`, and `max` as optional
values. The previous ingestion path converted missing optional stats
into JSON `null` and still emitted derived metric records such as
`<metric>_min` and `<metric>_max`. The flattening layer drops `null`
fields, so those derived streams could be created without a `value`
field.

Fixes #13019.

## Validation

- `cargo test -p openobserve test_process_hist_data_point --quiet`
- `cargo test -p openobserve
otlp::tests::metric_name_processing_tests::test_histogram_metric_name_suffixes
--quiet`
- `git diff --check`
2026-07-08 08:26:20 +00:00
Ashish Kolheandktx-abhay c07e4a6879 feat(dashboards): org-wide home dashboard (#13078)
## What

Lets an org pin a single dashboard as a tab on the **Home** page for the
whole organization. The chosen dashboard is stored server-side (per org)
so it persists across devices/browsers, and every member of the org sees
the same home tab.

## How

- **New `useHomeDashboard` composable** — single-value, server-backed
store using the existing `settings/v2` KV API (`setting_key:
"home_dashboard"`, org scope). Optimistic writes with revert-on-failure
(403 → permission toast), a falsy-org guard, and label auto-sync that
persists.
- **Loads in the existing `getOrganizationSettings()` hook** (boot + org
switch), so it is naturally **org-scoped** — switching orgs shows that
org's home dashboard, with no cross-org leak.
- **HomeView** renders the single home tab (read-only
`PinnedDashboardTab` embedding `RenderDashboardCharts` with its own time
picker); the tab × removes it.
- **Set / remove** from the dashboard list ⋮ overflow menu and the
ViewDashboard header button, with an at-a-glance indicator on the home
dashboard.

## Scope

- **Frontend only — no backend changes.** Reuses the existing
`system_settings` table + `settings/v2` handler + its RBAC gate.
- Single value (max 1); pinning a new dashboard replaces the previous
one.
- Permissions are enforced by the settings API (a 403 reverts the
optimistic change + toasts) — no client-side RBAC logic.

## Testing

- **74 unit tests**: composable (optimistic/revert/403/falsy-org
guard/label persist), HomeView (single tab render, ×
pointerdown-activation regression), PinnedDashboardTab (time/panel
contracts, µs time shape, tab-id provide).
- **Verified live against a local server**: set → server persists →
**fresh browser context with no localStorage** renders the tab → replace
swaps the single value → remove clears it. **Org isolation confirmed**
(`default` vs `demo` resolve independent values).
- 0 type errors in touched files.

Single squashed commit; 10 files, +1154/-11.

---------

Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
2026-07-08 08:26:01 +00:00
github-actions[bot]andgithub-merge-queue[bot] 56d467a29f chore: update translations from en.json (#13084)
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).

🤖 Generated with automated translation workflow

Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
2026-07-08 08:24:16 +00:00
Subhra264 0c14da4522 fix: self_reporting_timeout_errors and self_reporting_dropped_triggers metrics label mismatch (#13088)
The two metrics were using an empty string as the label name even though
no label names were registered in the metrics. Hence, whenever this
metric is emitted, there is a runtime panic. This pr fixes that.
2026-07-08 07:11:09 +00:00
Ashish Kolhe 37a92fa87b chore: service graph fixes (#13070) 2026-07-08 05:49:13 +00:00
Bhargav 5c288c3444 fix: logs table FF. UI fix (#13067) 2026-07-08 04:22:15 +00:00
github-actions[bot]andgithub-merge-queue[bot] 4cdcdcc0b2 chore: update translations from en.json (#13075)
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).

🤖 Generated with automated translation workflow

Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
2026-07-08 01:44:06 +00:00
2c087d6953 fix: empty states (#13058)
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
2026-07-07 15:38:39 +00:00
Abhay Padamani 717351254f fix: dashboard issues (#13050)
- #13004 
- support styling for HTML panel
<img width="1812" height="651" alt="image"
src="https://github.com/user-attachments/assets/bfa3d3ff-c54d-4deb-823b-c2be9ba6e49b"
/>
<img width="1820" height="720" alt="image"
src="https://github.com/user-attachments/assets/6d31a719-a0a0-4a8b-b7c8-28277a310687"
/>

- duplicate values on y axis.
<img width="464" height="193" alt="image"
src="https://github.com/user-attachments/assets/78e15896-2da8-47b4-9ab2-78287fe6db0a"
/>
<img width="523" height="166" alt="image"
src="https://github.com/user-attachments/assets/8d5d42a7-1a31-4b07-a19c-a8be2b33eda0"
/>

- axis value overlap
<img width="453" height="170" alt="image"
src="https://github.com/user-attachments/assets/e05133d2-adf2-4038-9e09-0b451feb71d1"
/>
<img width="443" height="160" alt="image"
src="https://github.com/user-attachments/assets/f594f3ee-6efc-4753-a7b0-15cd37349744"
/>



- metric chart type, font size auto adjust.
<img width="1022" height="181" alt="image"
src="https://github.com/user-attachments/assets/95bafd35-857d-444a-92cd-7f44b88714b1"
/>
<img width="1116" height="161" alt="image"
src="https://github.com/user-attachments/assets/424cd551-5dc1-4e1c-8c4d-c0d5d7338669"
/>


- promql chart gridline should be dashed.
<img width="1358" height="539" alt="image"
src="https://github.com/user-attachments/assets/4e64cca3-c153-49f0-9e79-86562f9200a7"
/>
<img width="1376" height="537" alt="image"
src="https://github.com/user-attachments/assets/8b5cc6e9-6d66-40ff-a09b-6d8d48a6e230"
/>


- horizontal chart spacing issue.
<img width="1826" height="337" alt="image"
src="https://github.com/user-attachments/assets/f3d7e6ef-a8ab-490c-afc8-ec7c7529fac0"
/>
<img width="1814" height="286" alt="image"
src="https://github.com/user-attachments/assets/faf03345-24e9-4a57-941e-b218ba2982a3"
/>


- legend and x axis value overlap and top spacing.
<img width="613" height="280" alt="image"
src="https://github.com/user-attachments/assets/0c5b89c1-2b6e-4c56-91dd-92c93d81d0d7"
/>
<img width="607" height="253" alt="image"
src="https://github.com/user-attachments/assets/414fb5d8-8e3e-4c31-b41b-8df2224f34eb"
/>

- promql chart typed chart tooltip overflow issue.
<img width="607" height="244" alt="image"
src="https://github.com/user-attachments/assets/5385cb82-9790-4b33-bbfd-c320cbdd51d9"
/>
<img width="606" height="381" alt="image"
src="https://github.com/user-attachments/assets/1f7b6007-fc3a-495a-8ae8-d4cb0178d259"
/>

- add query, left/right arrow issue on add/remove query.
<img width="211" height="95" alt="image"
src="https://github.com/user-attachments/assets/89d5ef0d-03d4-4fdf-822d-0d4a43c63152"
/>

- on x axis, timeseries chart value overlap
<img width="362" height="254" alt="image"
src="https://github.com/user-attachments/assets/127010d6-8a5c-441c-8fe4-9b9ee571e4c2"
/>
<img width="343" height="243" alt="image"
src="https://github.com/user-attachments/assets/369ead68-fd6b-4e16-a4d7-d93535fab7b5"
/>

- pie chart overlap issue
<img width="613" height="268" alt="image"
src="https://github.com/user-attachments/assets/1b06a56e-a863-4a63-9587-128ed2cfd0e8"
/>
<img width="608" height="270" alt="image"
src="https://github.com/user-attachments/assets/218f02ae-90ed-459f-99cb-103975bbe596"
/>

- plain legend spacing issue
<img width="910" height="269" alt="image"
src="https://github.com/user-attachments/assets/efd27d44-097f-4637-98ac-2e645b0f4dc7"
/>
<img width="916" height="267" alt="image"
src="https://github.com/user-attachments/assets/a5ecdc99-28cc-4552-a247-97a9ef05d6d1"
/>

- tooltip highlights all values
<img width="600" height="221" alt="image"
src="https://github.com/user-attachments/assets/8ab57fc1-27f7-410c-a279-56ff4cf2295b"
/>
<img width="610" height="305" alt="image"
src="https://github.com/user-attachments/assets/951334ea-9fb2-4249-880b-d564ba1c19c7"
/>
2026-07-07 15:03:13 +00:00
Subhra264 9b628f1bc4 fix: follow rfc 5321/5322 for email validation regex (#13027)
Fixes #12961
2026-07-07 13:05:12 +00:00
Bhargav 7a8ecab307 fix: player issue fix after 7.4.0 upgrade (#13069) 2026-07-07 12:15:27 +00:00
Subhra264 6945b82f7a perf: improve scheduled job puller (#12938)
## Scheduler: per-module job pullers with dedicated worker pools (A3 +
A4)

### Why
Scheduled jobs are dispatched late at scale. A single shared puller uses
one FIFO, one cadence, and one worker pool across every module, so a
backlog or slow handler in one module (e.g. an alert burst or a bulk
backfill) starves all others. This is Part A of the scheduler
scalability design.

### What
Give **each `TriggerModule` its own pull loop, cadence, LIMIT budget,
channel, and worker pool**, behind a flag. Off by default — zero
behavior change until explicitly enabled.

- **`ZO_SCHEDULER_PER_MODULE_PULLERS`** (default `false`) — master
switch.
- **off** → single shared puller (legacy, byte-identical SQL + same
global advisory lock).
- **on** → one lane per module (Alert, Report, DerivedStream, Backfill,
AnomalyDetection, QueryRecommendations).
- **Module-filtered pull** — `pull(...)` gains `module:
Option<TriggerModule>`; when `Some(m)` it filters `WHERE module = m` and
takes a **per-module advisory lock** (`scheduler_pull_lock:{module}`,
strategy C3) so modules no longer serialize against each other. `None`
keeps the exact legacy query + global lock.
- **Per-module budgets/cadence** — `ZO_SCHEDULER_<MODULE>_CONCURRENCY` /
`_INTERVAL` env vars, `0` = inherit the global alert default. Backfill
defaults to the smallest budget (1) so bulk jobs can't crowd out
latency-sensitive modules.

### Changes
| Area | Files |
|---|---|
| Flag + per-module config | `src/config/src/config.rs` |
| `module` param on `pull` (trait + wrappers) |
`src/infra/src/scheduler/{mod,postgres,sqlite}.rs`,
`src/service/db/scheduler.rs` |
| Per-module lanes (puller + pool + channel) |
`src/service/alerts/scheduler/worker.rs` |

### Notes
- No new SQL statement variants beyond two (module-filtered / legacy);
bind ordinals preserved, `is_realtime/is_silenced` exclusion unchanged.
- The keep-alive task and channel-full back-pressure retry are preserved
verbatim.
- Deferred: per-module Prometheus metrics (A6) and `FOR UPDATE SKIP
LOCKED` benchmark (A2/C2).

### Rollout
Flip the flag **cluster-wide**. During a mixed on/off window the
per-module and global locks don't mutually exclude, but the atomic
`UPDATE ... WHERE status=Waiting ... RETURNING` still claims each row
once (no double-dispatch) — only transient loss of cross-node FIFO. Size
the Postgres/sqlx pool for the extra pullers before enabling at scale.

### Testing
- 11 unit tests in `worker.rs`: legacy single-lane path,
one-lane-per-module, concurrency floor, and full
`resolve_module_configs` coverage (inherit/override/backfill floor).
- `cargo fmt --all -- --check` clean · `cargo clippy -- -D warnings`
clean.
2026-07-07 12:02:13 +00:00