openvibers/openvibe.sites Commits

Every placeholder publishes /release.json (the shared release watcher asked each page for it and got a 404): service, and a release id that is the hash of the page with dates neutralised; nginx serves it; the test checks it and each footer's shipped line

OP OpenVibers committed 24 Sept 2026 · bad3ca77fa · 36 files, +385 −1


Changes

ddiff --git a/build.js b/build.js
iindex c08f079..1121c65 100644
---- a/build.js
++++ b/build.js
@@@ -3,7 +3,7 @@
/**
* OpenVibe.Sites — static front pages for the network's domains that are not full apps yet.
*
-- * node build.js → dist/<domain>/{index.html,robots.txt,sitemap.xml,manifest.webmanifest}
++ * node build.js → dist/<domain>/{index.html,robots.txt,sitemap.xml,manifest.webmanifest,status.json,release.json}
* node build.js --check → exit 1 when dist/ is stale (CI / deploy guard)
*
* One template, one catalog (sites.json), one snapshot of facts (facts.json, refreshed by
@@@ -396,6 +396,11 @@ server {
add_header Access-Control-Allow-Origin "*";
${sec}
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ ${sec}
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -465,6 +470,20 @@ function liveStatus(site) {
`;
}
++/**
++ * /release.json: what the shared release watcher (openvibe-shared release-watch, loaded by the navbar
++ * on every page) asks each site for. A placeholder's release is the hash of its page with the dates
++ * neutralised, so it changes exactly when the page does.
++ */
++function releaseJson(site, html) {
++ const release = require('crypto').createHash('sha256').update(String(html).replace(/\d{4}-\d{2}-\d{2}/g, 'DATE')).digest('hex').slice(0, 12);
++ return JSON.stringify({
++ service: site.tld, release, released_at: `${today}T00:00:00.000Z`, booted_at: null, contracts_version: null,
++ packages: { 'openvibe-shared': require('openvibe-shared/package.json').version },
++ min_client_release: null, mixed_version_window_hours: 24, kind: 'placeholder',
++ }, null, 2) + '\n';
++}
++
/** /status.json: the same facts as the page, for scripts (CORS-open). */
function statusJson(site) {
const f = repoFacts(site);
@@@ -488,6 +507,7 @@ function build() {
const out = {};
for (const site of catalog.sites) {
out[`${site.domain}/index.html`] = page(site);
++ out[`${site.domain}/release.json`] = releaseJson(site, out[`${site.domain}/index.html`]);
out[`${site.domain}/404.html`] = notFound(site);
out[`${site.domain}/robots.txt`] = robots(site);
if (!site.kind) out[`${site.domain}/sitemap.xml`] = sitemap(site);
ddiff --git a/deploy/nginx/admin.openvibe.network.conf b/deploy/nginx/admin.openvibe.network.conf
iindex b1e78ad..ca45a15 100644
---- a/deploy/nginx/admin.openvibe.network.conf
++++ b/deploy/nginx/admin.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/ai.openvibe.network.conf b/deploy/nginx/ai.openvibe.network.conf
iindex 83c2c78..d980d54 100644
---- a/deploy/nginx/ai.openvibe.network.conf
++++ b/deploy/nginx/ai.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/api.openvibe.network.conf b/deploy/nginx/api.openvibe.network.conf
iindex 849b719..cd6acef 100644
---- a/deploy/nginx/api.openvibe.network.conf
++++ b/deploy/nginx/api.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/auth.openvibe.network.conf b/deploy/nginx/auth.openvibe.network.conf
iindex 8b2b38e..eabafc9 100644
---- a/deploy/nginx/auth.openvibe.network.conf
++++ b/deploy/nginx/auth.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openre.stream.conf b/deploy/nginx/openre.stream.conf
iindex 3b83906..b6861f3 100644
---- a/deploy/nginx/openre.stream.conf
++++ b/deploy/nginx/openre.stream.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.chat.conf b/deploy/nginx/openvibe.chat.conf
iindex 545ac7f..8c6a442 100644
---- a/deploy/nginx/openvibe.chat.conf
++++ b/deploy/nginx/openvibe.chat.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.coupons.conf b/deploy/nginx/openvibe.coupons.conf
iindex f2744da..8e4e259 100644
---- a/deploy/nginx/openvibe.coupons.conf
++++ b/deploy/nginx/openvibe.coupons.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.deals.conf b/deploy/nginx/openvibe.deals.conf
iindex 88ef98b..273ddeb 100644
---- a/deploy/nginx/openvibe.deals.conf
++++ b/deploy/nginx/openvibe.deals.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.host.conf b/deploy/nginx/openvibe.host.conf
iindex cebb754..141b55e 100644
---- a/deploy/nginx/openvibe.host.conf
++++ b/deploy/nginx/openvibe.host.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.news.conf b/deploy/nginx/openvibe.news.conf
iindex cf2ac74..46e0bd5 100644
---- a/deploy/nginx/openvibe.news.conf
++++ b/deploy/nginx/openvibe.news.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.reviews.conf b/deploy/nginx/openvibe.reviews.conf
iindex 12f2178..85a7cc9 100644
---- a/deploy/nginx/openvibe.reviews.conf
++++ b/deploy/nginx/openvibe.reviews.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.tips.conf b/deploy/nginx/openvibe.tips.conf
iindex 7ef7951..91594e9 100644
---- a/deploy/nginx/openvibe.tips.conf
++++ b/deploy/nginx/openvibe.tips.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.trade.conf b/deploy/nginx/openvibe.trade.conf
iindex dc5159d..ce52d75 100644
---- a/deploy/nginx/openvibe.trade.conf
++++ b/deploy/nginx/openvibe.trade.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/openvibe.vip.conf b/deploy/nginx/openvibe.vip.conf
iindex 55525b5..6151a1f 100644
---- a/deploy/nginx/openvibe.vip.conf
++++ b/deploy/nginx/openvibe.vip.conf
@@@ -73,6 +73,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/realtime.openvibe.network.conf b/deploy/nginx/realtime.openvibe.network.conf
iindex c4e265f..7ee2c9d 100644
---- a/deploy/nginx/realtime.openvibe.network.conf
++++ b/deploy/nginx/realtime.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/status.openvibe.network.conf b/deploy/nginx/status.openvibe.network.conf
iindex c55539e..fb07c49 100644
---- a/deploy/nginx/status.openvibe.network.conf
++++ b/deploy/nginx/status.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/deploy/nginx/themes.openvibe.network.conf b/deploy/nginx/themes.openvibe.network.conf
iindex 1a44d64..1eb5ac0 100644
---- a/deploy/nginx/themes.openvibe.network.conf
++++ b/deploy/nginx/themes.openvibe.network.conf
@@@ -63,6 +63,14 @@ server {
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
++ location = /release.json {
++ add_header Cache-Control "no-cache";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
ddiff --git a/dist/admin.openvibe.network/release.json b/dist/admin.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..9f8f920
---- /dev/null
++++ b/dist/admin.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "admin",
++ "release": "15932573ac66",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/ai.openvibe.network/release.json b/dist/ai.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..5db9b2e
---- /dev/null
++++ b/dist/ai.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "ai",
++ "release": "729f35e13916",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/api.openvibe.network/release.json b/dist/api.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..c84acc0
---- /dev/null
++++ b/dist/api.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "api",
++ "release": "0da0ea18ab49",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/auth.openvibe.network/release.json b/dist/auth.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..865d873
---- /dev/null
++++ b/dist/auth.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "auth",
++ "release": "88d4486c6aa8",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openre.stream/release.json b/dist/openre.stream/release.json
nnew file mode 100644
iindex 0000000..1b3bde0
---- /dev/null
++++ b/dist/openre.stream/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "stream",
++ "release": "7217c3153adb",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.chat/release.json b/dist/openvibe.chat/release.json
nnew file mode 100644
iindex 0000000..0a12bf4
---- /dev/null
++++ b/dist/openvibe.chat/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "chat",
++ "release": "d703c838dff3",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.coupons/release.json b/dist/openvibe.coupons/release.json
nnew file mode 100644
iindex 0000000..f25c612
---- /dev/null
++++ b/dist/openvibe.coupons/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "coupons",
++ "release": "7ffe4f0f5631",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.deals/release.json b/dist/openvibe.deals/release.json
nnew file mode 100644
iindex 0000000..a24204d
---- /dev/null
++++ b/dist/openvibe.deals/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "deals",
++ "release": "36af1bd4aa33",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.host/release.json b/dist/openvibe.host/release.json
nnew file mode 100644
iindex 0000000..cd10bd1
---- /dev/null
++++ b/dist/openvibe.host/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "host",
++ "release": "b6a20f0674b9",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.news/release.json b/dist/openvibe.news/release.json
nnew file mode 100644
iindex 0000000..fe8085a
---- /dev/null
++++ b/dist/openvibe.news/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "news",
++ "release": "019aa335b9ae",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.reviews/release.json b/dist/openvibe.reviews/release.json
nnew file mode 100644
iindex 0000000..07d1aa4
---- /dev/null
++++ b/dist/openvibe.reviews/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "reviews",
++ "release": "4093d27e2681",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.tips/release.json b/dist/openvibe.tips/release.json
nnew file mode 100644
iindex 0000000..011192f
---- /dev/null
++++ b/dist/openvibe.tips/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "tips",
++ "release": "3a6c853a9f2a",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.trade/release.json b/dist/openvibe.trade/release.json
nnew file mode 100644
iindex 0000000..831a45a
---- /dev/null
++++ b/dist/openvibe.trade/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "trade",
++ "release": "af685650dda9",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/openvibe.vip/release.json b/dist/openvibe.vip/release.json
nnew file mode 100644
iindex 0000000..be83cc2
---- /dev/null
++++ b/dist/openvibe.vip/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "vip",
++ "release": "05fbd1e56222",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/realtime.openvibe.network/release.json b/dist/realtime.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..906e1e3
---- /dev/null
++++ b/dist/realtime.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "realtime",
++ "release": "7df3c64d664f",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/status.openvibe.network/release.json b/dist/status.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..e270d9e
---- /dev/null
++++ b/dist/status.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "status",
++ "release": "6b4db8562da8",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/dist/themes.openvibe.network/release.json b/dist/themes.openvibe.network/release.json
nnew file mode 100644
iindex 0000000..5d95b03
---- /dev/null
++++ b/dist/themes.openvibe.network/release.json
@@@ -0,0 +1,13 @@
++{
++ "service": "themes",
++ "release": "80e1b0a48d84",
++ "released_at": "2026-09-24T00:00:00.000Z",
++ "booted_at": null,
++ "contracts_version": null,
++ "packages": {
++ "openvibe-shared": "1.11.1"
++ },
++ "min_client_release": null,
++ "mixed_version_window_hours": 24,
++ "kind": "placeholder"
++}
ddiff --git a/test/build.test.js b/test/build.test.js
iindex 5ea4743..740b257 100644
---- a/test/build.test.js
++++ b/test/build.test.js
@@@ -37,6 +37,13 @@ for (const site of catalog.sites) {
for (const f of ['robots.txt', 'manifest.webmanifest', 'status.json', ...(site.kind ? [] : ['sitemap.xml'])]) {
assert.ok(fs.existsSync(path.join(ROOT, 'dist', d, f)), `${d}: ${f}`);
}
++ // The shared release watcher asks every page for /release.json; a placeholder publishes one, and its
++ // footer carries its own service's "shipped" line (the OpenVibe Frame).
++ const rel = JSON.parse(read(`dist/${d}/release.json`));
++ assert.strictEqual(rel.service, site.tld, `${d}: release.json service`);
++ assert.match(rel.release, /^[0-9a-f]{12}$/, `${d}: release.json release`);
++ assert.match(read(`deploy/nginx/${d}.conf`), /location = \/release\.json \{/, `${d}: nginx serves /release.json`);
++ assert.ok(html.includes(`data-ov-shipped="latest" data-service="${site.tld}"`), `${d}: the footer's shipped line`);
if (site.kind) {
// A notice (a closed product, a pointer) is not indexed, has no sitemap and is not listed as opening.
assert.match(html, /<meta name="robots" content="noindex, follow">/, `${d}: notice is noindex`);