feat(metrics): render the operator health snapshot as a report (#19)
Quality gates / quality (pull_request) Canceled after 0s

The counters had an endpoint and no way to read them without jq. Adds a pure
formatter plus a CLI that prints the app, queue, and model boundaries.

The formatter is a function of the snapshot and the three frozen vocabularies,
so a caller-supplied failure label cannot reach the terminal: unknown classes
are simply not printed. Refusal paths are explicit — 404 means the route is off
because no token is configured, 403 means the token was rejected — and the token
is never echoed in the report or an error line.

RED: tests/model-metrics-report-format.test.js failed with ERR_MODULE_NOT_FOUND
before src/model-metrics-report.js existed; the refusal-path test failed with a
generic 'answered 404' before the 404 branch was written.

Tests: npm test 298/298, npm run test:metrics-report 8/8, syntax and diff gates
clean, real-Chromium ui acceptance green with the privacy disclosure visible.
This commit is contained in:
Timmy committed 2026-11-02 10:56:16 +00:00
1 parent 3e2253a052
commit e08b537866
6 files changed
+311 -3

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env node
// Operator report for the privacy-safe model counters (issue #19).
//
// It reads one snapshot from a running Timmy server and prints it. The token is a credential for the
// operator surface, so it is read from the environment or argv and never echoed — not in errors, not
// in the report, not in a stack trace.
import { formatOperatorReport } from '../src/model-metrics-report.js';
const USAGE = `Usage: node scripts/model_metrics_report.mjs [options]
--url <origin> base URL of the Timmy server (default http://127.0.0.1:4173)
--token <token> operator token (default: $TIMMY_METRICS_TOKEN)
--json print the raw snapshot instead of the text report
--timeout-ms <ms> request deadline (default 5000)
--help show this text
`;
function parseArgs(argv) {
const options = {
url: process.env.TIMMY_METRICS_URL || 'http://127.0.0.1:4173',
token: process.env.TIMMY_METRICS_TOKEN || '',
json: false,
timeoutMs: 5_000,
help: false,
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === '--help' || arg === '-h') {
options.help = true;
} else if (arg === '--json') {
options.json = true;
} else if (arg === '--url') {
options.url = argv[++index] ?? '';
} else if (arg === '--token') {
options.token = argv[++index] ?? '';
} else if (arg === '--timeout-ms') {
options.timeoutMs = Number(argv[++index]);
} else {
throw new Error(`unknown option: ${arg}`);
}
}
return options;
}
async function main(argv) {
const options = parseArgs(argv);
if (options.help) {
process.stdout.write(USAGE);
return 0;
}
if (!options.token) {
throw new Error('operator token required: pass --token or set TIMMY_METRICS_TOKEN');
}
const endpoint = new URL('api/metrics', options.url.endsWith('/') ? options.url : `${options.url}/`);
let response;
try {
response = await fetch(endpoint, {
headers: { authorization: `Bearer ${options.token}`, accept: 'application/json' },
signal: AbortSignal.timeout(Number.isFinite(options.timeoutMs) ? options.timeoutMs : 5_000),
});
} catch (error) {
process.stderr.write(`Could not read ${endpoint.origin}/api/metrics: ${error?.message || 'request failed'}\n`);
return 1;
}
if (!response.ok) {
if (response.status === 404) {
// The route is absent rather than guarded: that is a deployment fact, not a credential problem.
process.stderr.write('Metrics are disabled on this server: the route only exists when TIMMY_METRICS_TOKEN is set.\n');
return 1;
}
if (response.status === 403) {
process.stderr.write('The server rejected the operator token.\n');
return 1;
}
process.stderr.write(`The server answered ${response.status} for /api/metrics.\n`);
return 1;
}
const snapshot = await response.json();
process.stdout.write(options.json ? `${JSON.stringify(snapshot, null, 2)}\n` : formatOperatorReport(snapshot));
return 0;
}
main(process.argv.slice(2)).then(code => {
process.exitCode = code;
}).catch(error => {
process.stderr.write(`${error?.message || 'report failed'}\n`);
process.exitCode = 2;
});