openvibers/openvibe.host Commits

browser-watch: all 23 public sites, and one alert per site across releases. The release moves from openvibe_browser_check_ok's labels to openvibe_browser_check_release_info{service,release}, which the alert summary reads. Before this, each new release of a failing site resolved one alert and opened another. The first run over every site found two real regressions (Search's palette ignoring the network theme; Billing's /policy table scroller), both fixed and deployed.

OP OpenVibers committed 26 Sept 2026 · 3d5d8e792b · 6 files, +22 −10


Changes

ddiff --git a/deploy/prometheus/openvibe-rules.yml b/deploy/prometheus/openvibe-rules.yml
iindex 803941e..615e686 100644
---- a/deploy/prometheus/openvibe-rules.yml
++++ b/deploy/prometheus/openvibe-rules.yml
@@@ -86,7 +86,7 @@ groups:
for: 1m
labels: { severity: page }
annotations:
-- summary: "{{ $labels.service }}: the browser check of release {{ $labels.release }} failed ({{ with printf \"openvibe_browser_check_failing_checks{service='%s'}\" $labels.service | query }}{{ . | first | value }}{{ end }} failing check(s)); run: sudo ovhost browser-watch --sites {{ $labels.service }} --force"
++ summary: "{{ $labels.service }}: the browser check of release {{ with printf \"openvibe_browser_check_release_info{service='%s'}\" $labels.service | query }}{{ (. | first).Labels.release }}{{ end }} failed ({{ with printf \"openvibe_browser_check_failing_checks{service='%s'}\" $labels.service | query }}{{ . | first | value }}{{ end }} failing check(s)); run: sudo ovhost browser-watch --sites {{ $labels.service }} --force"
- alert: OpenVibeBrowserCheckMissed
expr: time() - openvibe_browser_check_last_run_timestamp_seconds > 26 * 3600
for: 30m
ddiff --git a/deploy/systemd/openvibe-browsercheck.service b/deploy/systemd/openvibe-browsercheck.service
iindex 1714c63..bf823ed 100644
---- a/deploy/systemd/openvibe-browsercheck.service
++++ b/deploy/systemd/openvibe-browsercheck.service
@@@ -9,7 +9,7 @@ Type=oneshot
# Root reads the state and writes the textfile metrics; Chrome runs as the unprivileged ovcheck account
# (runuser), never as root. --sites names the watched services (inventory origin or manifest publicOrigin).
User=root
--ExecStart=/usr/local/bin/ovhost browser-watch --sites live
++ExecStart=/usr/local/bin/ovhost browser-watch --sites live,network,tools,media,community,chat,games,blog,wiki,codes,host,ai,billing,search,sources,news,reviews,deals,coupons,trade,tips,vip,openre
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=OVHOST_INVENTORY=/etc/openvibe/host.json
Nice=10
ddiff --git a/deploy/systemd/openvibe-browsercheck.timer b/deploy/systemd/openvibe-browsercheck.timer
iindex 6b81cf5..4e4aae3 100644
---- a/deploy/systemd/openvibe-browsercheck.timer
++++ b/deploy/systemd/openvibe-browsercheck.timer
@@@ -1,5 +1,5 @@
[Unit]
--Description=Check each watched OpenVibe site in a browser after a release (every five minutes; WS-H task 11)
++Description=Check every public OpenVibe site in a browser after its releases and daily (every five minutes; WS-H task 11)
[Timer]
OnBootSec=5min
ddiff --git a/docs/operator-alerts.md b/docs/operator-alerts.md
iindex d8a0a6a..311523f 100644
---- a/docs/operator-alerts.md
++++ b/docs/operator-alerts.md
@@@ -33,7 +33,8 @@ Prometheus (127.0.0.1:9090) ──GET /api/v1/alerts──▶ ovhost alerts re
## The release UX check
--`ovhost browser-watch --sites live` (openvibe-browsercheck.timer, every 5 minutes) reads each watched site's
++`ovhost browser-watch --sites live,network,…` (openvibe-browsercheck.timer, every 5 minutes; the unit lists all 23
++public sites) reads each watched site's
`/release.json`. For a release it has not checked yet, or once a day, it runs
[scripts/browser-check.js](../scripts/browser-check.js) in Chrome as the unprivileged `ovcheck` account (never
root): routes × widths, console errors, overflow, duplicate scripts, no-JS content, axe, navigation growth.
@@@ -49,7 +50,12 @@ root): routes × widths, console errors, overflow, duplicate scripts, no-JS cont
- **Setup**: Chrome comes from Google's apt repository (`google-chrome-stable`, updated with the system), and
`ovcheck` is a system account whose home is `/var/lib/ovcheck`.
--To watch more sites, add them to `--sites` in the unit.
++The first run over all 23 sites (2026-09-26) took 10 minutes and found two real regressions:
++
++- Search's palette ignored the network theme, so the shipped widget failed color-contrast (fixed in Search `78fe5cd`);
++- Billing's `/policy` had a table scroller keyboard users could not reach (fixed in Billing `db8109c`).
++
++The release is its own series (`openvibe_browser_check_release_info`), so a failure keeps one alert across releases.
## Commands
ddiff --git a/lib/browser-watch.js b/lib/browser-watch.js
iindex e162e98..a78e478 100644
---- a/lib/browser-watch.js
++++ b/lib/browser-watch.js
@@@ -12,7 +12,8 @@
* <stateDir>/browser-watch/<site>.json, and every site's latest outcome is written to the textfile collector
* (openvibe_browsercheck.prom):
*
-- * openvibe_browser_check_ok{service,release} 1 passed, 0 failed
++ * openvibe_browser_check_ok{service} 1 passed, 0 failed
++ * openvibe_browser_check_release_info{service,release} 1: the release that check saw
* openvibe_browser_check_failing_checks{service} checks with failures (status, console, overflow, axe…)
* openvibe_browser_check_last_run_timestamp_seconds{service} when the last check ran to the end
*
@@@ -71,9 +72,13 @@ async function writeMetrics(exec, inv) {
}
rows.sort((a, b) => a.site.localeCompare(b.site));
const lines = [
-- '# HELP openvibe_browser_check_ok 1 when the last browser check of the site passed (label: the release it checked).',
++ // The release is its own series, so an alert keeps one identity across releases (no resolve-and-reopen).
++ '# HELP openvibe_browser_check_ok 1 when the last browser check of the site passed.',
'# TYPE openvibe_browser_check_ok gauge',
-- ...rows.map((s) => `openvibe_browser_check_ok{service="${esc(s.site)}",release="${esc(s.release || 'unknown')}"} ${s.ok ? 1 : 0}`),
++ ...rows.map((s) => `openvibe_browser_check_ok{service="${esc(s.site)}"} ${s.ok ? 1 : 0}`),
++ '# HELP openvibe_browser_check_release_info The release the last browser check of the site checked.',
++ '# TYPE openvibe_browser_check_release_info gauge',
++ ...rows.map((s) => `openvibe_browser_check_release_info{service="${esc(s.site)}",release="${esc(s.release || 'unknown')}"} 1`),
'# HELP openvibe_browser_check_failing_checks Checks with failures in the last browser check of the site.',
'# TYPE openvibe_browser_check_failing_checks gauge',
...rows.map((s) => `openvibe_browser_check_failing_checks{service="${esc(s.site)}"} ${(s.failing || []).length}`),
ddiff --git a/test/browser-watch.test.js b/test/browser-watch.test.js
iindex dde1ee8..e896409 100644
---- a/test/browser-watch.test.js
++++ b/test/browser-watch.test.js
@@@ -33,7 +33,8 @@ runTests([
let r = await watch(host.ctx(), { check: host.check, now });
assert.deepStrictEqual(r.sites.map((x) => [x.site, x.release, x.action, x.ok]), [['live', 'abc1234def56', 'checked', true]]);
assert.deepStrictEqual(host.runs.map((x) => [x.s, x.opts.asUser, x.opts.home, x.opts.args]), [['live', 'ovcheck', '/var/lib/ovcheck', ['--json']]]);
-- assert.match(host.files.get(PROM).content, /^openvibe_browser_check_ok\{service="live",release="abc1234def56"\} 1$/m);
++ assert.match(host.files.get(PROM).content, /^openvibe_browser_check_ok\{service="live"\} 1$/m);
++ assert.match(host.files.get(PROM).content, /^openvibe_browser_check_release_info\{service="live",release="abc1234def56"\} 1$/m);
assert.match(host.files.get(PROM).content, /^openvibe_browser_check_failing_checks\{service="live"\} 0$/m);
r = await watch(host.ctx(), { check: host.check, now: now + 60000 });
assert.strictEqual(r.sites[0].action, 'skipped');
@@@ -52,7 +53,7 @@ runTests([
let r = await watch(host.ctx(), { check: host.check });
assert.strictEqual(host.runs.length, 2);
assert.deepStrictEqual([r.sites[0].ok, r.sites[0].failing], [false, [{ check: 'console', fail: 2 }, { check: 'overflow', fail: 1 }]]);
-- assert.match(host.files.get(PROM).content, /^openvibe_browser_check_ok\{service="live",release="abc1234def56"\} 0$/m);
++ assert.match(host.files.get(PROM).content, /^openvibe_browser_check_ok\{service="live"\} 0$/m);
assert.match(host.files.get(PROM).content, /^openvibe_browser_check_failing_checks\{service="live"\} 2$/m);
host = site();
host.answers.push({ code: 1, reports: fail }, { code: 0, reports: pass });