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.
Changes
@@@ -86,7 +86,7 @@ groups:for: 1mlabels: { 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: OpenVibeBrowserCheckMissedexpr: time() - openvibe_browser_check_last_run_timestamp_seconds > 26 * 3600for: 30m@@@ -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,openreEnvironment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binEnvironment=OVHOST_INVENTORY=/etc/openvibe/host.jsonNice=10@@@ -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@@@ -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 (neverroot): 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@@@ -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}`),@@@ -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 });
@@ -86,7 +86,7 @@ groups:
@@ -86,7 +86,7 @@ groups:
for: 1m
for: 1m
labels: { severity: page }
labels: { severity: page }
annotations:
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
- alert: OpenVibeBrowserCheckMissed
expr: time() - openvibe_browser_check_last_run_timestamp_seconds > 26 * 3600
expr: time() - openvibe_browser_check_last_run_timestamp_seconds > 26 * 3600
for: 30m
for: 30m
@@ -9,7 +9,7 @@ Type=oneshot
@@ -9,7 +9,7 @@ Type=oneshot
# Root reads the state and writes the textfile metrics; Chrome runs as the unprivileged ovcheck account
# 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).
# (runuser), never as root. --sites names the watched services (inventory origin or manifest publicOrigin).
User=root
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=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=OVHOST_INVENTORY=/etc/openvibe/host.json
Environment=OVHOST_INVENTORY=/etc/openvibe/host.json
Nice=10
Nice=10
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
[Unit]
[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]
[Timer]
OnBootSec=5min
OnBootSec=5min
@@ -33,7 +33,8 @@ Prometheus (127.0.0.1:9090) ──GET /api/v1/alerts──▶ ovhost alerts re
@@ -33,7 +33,8 @@ Prometheus (127.0.0.1:9090) ──GET /api/v1/alerts──▶ ovhost alerts re
## The release UX check
## 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
`/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
[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.
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
@@ -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
- **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`.
`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
## Commands
@@ -12,7 +12,8 @@
@@ -12,7 +12,8 @@
* <stateDir>/browser-watch/<site>.json, and every site's latest outcome is written to the textfile collector
* <stateDir>/browser-watch/<site>.json, and every site's latest outcome is written to the textfile collector
* (openvibe_browsercheck.prom):
* (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_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
* 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) {
@@ -71,9 +72,13 @@ async function writeMetrics(exec, inv) {
}
}
rows.sort((a, b) => a.site.localeCompare(b.site));
rows.sort((a, b) => a.site.localeCompare(b.site));
const lines = [
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',
'# 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.',
'# HELP openvibe_browser_check_failing_checks Checks with failures in the last browser check of the site.',
'# TYPE openvibe_browser_check_failing_checks gauge',
'# TYPE openvibe_browser_check_failing_checks gauge',
...rows.map((s) => `openvibe_browser_check_failing_checks{service="${esc(s.site)}"} ${(s.failing || []).length}`),
...rows.map((s) => `openvibe_browser_check_failing_checks{service="${esc(s.site)}"} ${(s.failing || []).length}`),
@@ -33,7 +33,8 @@ runTests([
@@ -33,7 +33,8 @@ runTests([
let r = await watch(host.ctx(), { check: host.check, now });
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(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.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);
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 });
r = await watch(host.ctx(), { check: host.check, now: now + 60000 });
assert.strictEqual(r.sites[0].action, 'skipped');
assert.strictEqual(r.sites[0].action, 'skipped');
@@ -52,7 +53,7 @@ runTests([
@@ -52,7 +53,7 @@ runTests([
let r = await watch(host.ctx(), { check: host.check });
let r = await watch(host.ctx(), { check: host.check });
assert.strictEqual(host.runs.length, 2);
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.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);
assert.match(host.files.get(PROM).content, /^openvibe_browser_check_failing_checks\{service="live"\} 2$/m);
host = site();
host = site();
host.answers.push({ code: 1, reports: fail }, { code: 0, reports: pass });
host.answers.push({ code: 1, reports: fail }, { code: 0, reports: pass });