openvibers/openvibe.network Commits
Status: the daily developer path's last run (passed or failed, each step) on /status and in /api/v1/status, from its result file — WS-N task 1
Changes
@@@ -24,6 +24,16 @@ const seo = require('openvibe-shared/seo');const exposure = require('../registry/exposure');const SLO_FILE = path.join(__dirname, '..', '..', 'docs', 'slo.json');++// The daily developer path against production (OpenVibe.Host openvibe-devpath.timer, WS-N task 1): step++// names and outcomes only, written by OpenVibe.Examples' developer-path --result.++const DEVPATH_FILE = process.env.DEVPATH_RESULT_FILE || '/var/lib/openvibe-devpath/last.json';++function loadDevPath(file = DEVPATH_FILE) {++ try {++ const d = JSON.parse(fs.readFileSync(file, 'utf8'));++ if (!d || !Array.isArray(d.steps)) return null;++ return { ok: !!d.ok, finished_at: String(d.finished_at || ''), steps: d.steps.slice(0, 20).map((st) => ({ name: String(st.name).slice(0, 40), ok: !!st.ok, skipped: !!st.skipped })) };++ } catch { return null; }++}const esc = seo.esc;const STATES = ['up', 'degraded', 'down', 'not-running', 'unknown'];const LABEL = { up: 'Up', degraded: 'Degraded', down: 'Down', 'not-running': 'Not running', unknown: 'Unknown' };@@@ -107,7 +117,7 @@ function checksList(ready) {return items.length ? `<ul class="chk">${items.join('')}</ul>` : '';}--function renderPage(list, slo, generatedAt) {++function renderPage(list, slo, generatedAt, devPath = null) {const counts = summary(list);const tr = list.map((r) => `<tr id="svc-${esc(r.id)}"><td><b>${esc(r.name)}</b><small>${esc(r.id)} · manifest: ${esc(r.manifest_status)}</small></td>@@@ -149,6 +159,10 @@ ${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links${tr}</tbody></table>++<section aria-labelledby="h-devpath"><h2 id="h-devpath">Developer path</h2>++${devPath ? `<p class="lede">Once a day a test developer account goes from sign-in to a sandbox project, a Media upload, an Events publish and pull, a credential rotation and revocation, and cleanup, through the public API and the SDK. Last run <time datetime="${esc(devPath.finished_at)}">${esc(devPath.finished_at)}</time>: <b>${devPath.ok ? 'passed' : 'failed'}</b> (${devPath.steps.filter((st) => st.ok).length}/${devPath.steps.length} steps).</p>++<ul class="st-sum">${devPath.steps.map((st) => `<li class="st-${st.ok ? 'up' : st.skipped ? 'unknown' : 'down'}">${esc(st.name)}: ${st.ok ? 'ok' : st.skipped ? 'skipped' : 'failed'}</li>`).join('')}</ul>` : '<p class="lede">The daily developer path has not reported here yet.</p>'}++</section><section aria-labelledby="h-slo"><h2 id="h-slo">SLO categories (proposals)</h2><p class="lede">${esc(slo.note)} ${esc(slo.binding)} JSON: <a href="/api/v1/status/slo"><code>/api/v1/status/slo</code></a>.</p><dl>@@@ -175,6 +189,7 @@ function createStatusRoutes({ ecosystem, now = () => new Date() }) {summary: summary(list),exposure_states: exposure.STATES,exposure_summary: exposureSummary(list),++ developer_path: loadDevPath(),services: list,});});@@@ -184,9 +199,9 @@ function createStatusRoutes({ ecosystem, now = () => new Date() }) {r.get('/status', (_req, res) => {const slo = { ...loadSlo(), pollSeconds: ecosystem.pollMs / 1000 };res.set('Content-Type', 'text/html; charset=utf-8').set('Cache-Control', 'no-cache, max-age=0').set('X-Robots-Tag', 'noindex, nofollow');-- res.send(renderPage(rows(ecosystem), slo, now().toISOString()));++ res.send(renderPage(rows(ecosystem), slo, now().toISOString(), loadDevPath()));});return r;}--module.exports = { createStatusRoutes, rows, STATES };++module.exports = { loadDevPath, createStatusRoutes, rows, STATES };@@@ -123,6 +123,19 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22if (!c.measured_by.length) assert.ok(/not instrumented/.test(c.measurement_status), `${c.id} says it is not measured`);}++ // The daily developer path (WS-N task 1): read from its result file, names and outcomes only.++ {++ const fsx = require('fs'); const osx = require('os'); const px = require('path');++ const { loadDevPath } = require('../server/status/routes');++ const f = px.join(fsx.mkdtempSync(px.join(osx.tmpdir(), 'ov-devpath-')), 'last.json');++ assert.strictEqual(loadDevPath(f), null, 'no file: nothing reported');++ fsx.writeFileSync(f, JSON.stringify({ ok: false, finished_at: '2026-09-25T22:54:43.000Z', steps: [{ name: 'account', ok: true }, { name: 'media', ok: false }, { name: 'events', ok: false, skipped: true }], secret: 'x' }));++ const d = loadDevPath(f);++ assert.deepStrictEqual(d, { ok: false, finished_at: '2026-09-25T22:54:43.000Z', steps: [{ name: 'account', ok: true, skipped: false }, { name: 'media', ok: false, skipped: false }, { name: 'events', ok: false, skipped: true }] }, 'only names and outcomes pass through');++ fsx.writeFileSync(f, 'not json');++ assert.strictEqual(loadDevPath(f), null);++ }++for (const s of [network, media, community, tools, events]) s.srv.close();srv.close();console.log('status: all checks passed');
@@ -24,6 +24,16 @@ const seo = require('openvibe-shared/seo');
@@ -24,6 +24,16 @@ const seo = require('openvibe-shared/seo');
const exposure = require('../registry/exposure');
const exposure = require('../registry/exposure');
const SLO_FILE = path.join(__dirname, '..', '..', 'docs', 'slo.json');
const SLO_FILE = path.join(__dirname, '..', '..', 'docs', 'slo.json');
+// The daily developer path against production (OpenVibe.Host openvibe-devpath.timer, WS-N task 1): step
+// names and outcomes only, written by OpenVibe.Examples' developer-path --result.
+const DEVPATH_FILE = process.env.DEVPATH_RESULT_FILE || '/var/lib/openvibe-devpath/last.json';
+function loadDevPath(file = DEVPATH_FILE) {
+ try {
+ const d = JSON.parse(fs.readFileSync(file, 'utf8'));
+ if (!d || !Array.isArray(d.steps)) return null;
+ return { ok: !!d.ok, finished_at: String(d.finished_at || ''), steps: d.steps.slice(0, 20).map((st) => ({ name: String(st.name).slice(0, 40), ok: !!st.ok, skipped: !!st.skipped })) };
+ } catch { return null; }
+}
const esc = seo.esc;
const esc = seo.esc;
const STATES = ['up', 'degraded', 'down', 'not-running', 'unknown'];
const STATES = ['up', 'degraded', 'down', 'not-running', 'unknown'];
const LABEL = { up: 'Up', degraded: 'Degraded', down: 'Down', 'not-running': 'Not running', unknown: 'Unknown' };
const LABEL = { up: 'Up', degraded: 'Degraded', down: 'Down', 'not-running': 'Not running', unknown: 'Unknown' };
@@ -107,7 +117,7 @@ function checksList(ready) {
@@ -107,7 +117,7 @@ function checksList(ready) {
return items.length ? `<ul class="chk">${items.join('')}</ul>` : '';
return items.length ? `<ul class="chk">${items.join('')}</ul>` : '';
}
}
-function renderPage(list, slo, generatedAt) {
+function renderPage(list, slo, generatedAt, devPath = null) {
const counts = summary(list);
const counts = summary(list);
const tr = list.map((r) => `<tr id="svc-${esc(r.id)}">
const tr = list.map((r) => `<tr id="svc-${esc(r.id)}">
<td><b>${esc(r.name)}</b><small>${esc(r.id)} · manifest: ${esc(r.manifest_status)}</small></td>
<td><b>${esc(r.name)}</b><small>${esc(r.id)} · manifest: ${esc(r.manifest_status)}</small></td>
@@ -149,6 +159,10 @@ ${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links
@@ -149,6 +159,10 @@ ${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links
${tr}
${tr}
</tbody>
</tbody>
</table>
</table>
+<section aria-labelledby="h-devpath"><h2 id="h-devpath">Developer path</h2>
+${devPath ? `<p class="lede">Once a day a test developer account goes from sign-in to a sandbox project, a Media upload, an Events publish and pull, a credential rotation and revocation, and cleanup, through the public API and the SDK. Last run <time datetime="${esc(devPath.finished_at)}">${esc(devPath.finished_at)}</time>: <b>${devPath.ok ? 'passed' : 'failed'}</b> (${devPath.steps.filter((st) => st.ok).length}/${devPath.steps.length} steps).</p>
+<ul class="st-sum">${devPath.steps.map((st) => `<li class="st-${st.ok ? 'up' : st.skipped ? 'unknown' : 'down'}">${esc(st.name)}: ${st.ok ? 'ok' : st.skipped ? 'skipped' : 'failed'}</li>`).join('')}</ul>` : '<p class="lede">The daily developer path has not reported here yet.</p>'}
+</section>
<section aria-labelledby="h-slo"><h2 id="h-slo">SLO categories (proposals)</h2>
<section aria-labelledby="h-slo"><h2 id="h-slo">SLO categories (proposals)</h2>
<p class="lede">${esc(slo.note)} ${esc(slo.binding)} JSON: <a href="/api/v1/status/slo"><code>/api/v1/status/slo</code></a>.</p>
<p class="lede">${esc(slo.note)} ${esc(slo.binding)} JSON: <a href="/api/v1/status/slo"><code>/api/v1/status/slo</code></a>.</p>
<dl>
<dl>
@@ -175,6 +189,7 @@ function createStatusRoutes({ ecosystem, now = () => new Date() }) {
@@ -175,6 +189,7 @@ function createStatusRoutes({ ecosystem, now = () => new Date() }) {
summary: summary(list),
summary: summary(list),
exposure_states: exposure.STATES,
exposure_states: exposure.STATES,
exposure_summary: exposureSummary(list),
exposure_summary: exposureSummary(list),
+ developer_path: loadDevPath(),
services: list,
services: list,
});
});
});
});
@@ -184,9 +199,9 @@ function createStatusRoutes({ ecosystem, now = () => new Date() }) {
@@ -184,9 +199,9 @@ function createStatusRoutes({ ecosystem, now = () => new Date() }) {
r.get('/status', (_req, res) => {
r.get('/status', (_req, res) => {
const slo = { ...loadSlo(), pollSeconds: ecosystem.pollMs / 1000 };
const slo = { ...loadSlo(), pollSeconds: ecosystem.pollMs / 1000 };
res.set('Content-Type', 'text/html; charset=utf-8').set('Cache-Control', 'no-cache, max-age=0').set('X-Robots-Tag', 'noindex, nofollow');
res.set('Content-Type', 'text/html; charset=utf-8').set('Cache-Control', 'no-cache, max-age=0').set('X-Robots-Tag', 'noindex, nofollow');
- res.send(renderPage(rows(ecosystem), slo, now().toISOString()));
+ res.send(renderPage(rows(ecosystem), slo, now().toISOString(), loadDevPath()));
});
});
return r;
return r;
}
}
-module.exports = { createStatusRoutes, rows, STATES };
+module.exports = { loadDevPath, createStatusRoutes, rows, STATES };
@@ -123,6 +123,19 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22
@@ -123,6 +123,19 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22
if (!c.measured_by.length) assert.ok(/not instrumented/.test(c.measurement_status), `${c.id} says it is not measured`);
if (!c.measured_by.length) assert.ok(/not instrumented/.test(c.measurement_status), `${c.id} says it is not measured`);
}
}
+ // The daily developer path (WS-N task 1): read from its result file, names and outcomes only.
+ {
+ const fsx = require('fs'); const osx = require('os'); const px = require('path');
+ const { loadDevPath } = require('../server/status/routes');
+ const f = px.join(fsx.mkdtempSync(px.join(osx.tmpdir(), 'ov-devpath-')), 'last.json');
+ assert.strictEqual(loadDevPath(f), null, 'no file: nothing reported');
+ fsx.writeFileSync(f, JSON.stringify({ ok: false, finished_at: '2026-09-25T22:54:43.000Z', steps: [{ name: 'account', ok: true }, { name: 'media', ok: false }, { name: 'events', ok: false, skipped: true }], secret: 'x' }));
+ const d = loadDevPath(f);
+ assert.deepStrictEqual(d, { ok: false, finished_at: '2026-09-25T22:54:43.000Z', steps: [{ name: 'account', ok: true, skipped: false }, { name: 'media', ok: false, skipped: false }, { name: 'events', ok: false, skipped: true }] }, 'only names and outcomes pass through');
+ fsx.writeFileSync(f, 'not json');
+ assert.strictEqual(loadDevPath(f), null);
+ }
+
for (const s of [network, media, community, tools, events]) s.srv.close();
for (const s of [network, media, community, tools, events]) s.srv.close();
srv.close();
srv.close();
console.log('status: all checks passed');
console.log('status: all checks passed');