Kevin Crutt commited on
Commit ·
47cbc00
1
Parent(s): e352bcb
liris: add route health baseline
Browse files- docs/LIRIS-ROUTE-HEALTH-BASELINE-2026-06-13.hbp +7 -0
- docs/LIRIS-ROUTE-HEALTH-BASELINE-2026-06-13.hbp.sha256 +1 -0
- tests/route-health-baseline.unit.test.mjs +91 -0
- tests/route-health-baseline.unit.test.mjs.sha256 +1 -0
- tests/supervisor-collision-router.system.test.mjs +3 -0
- tests/supervisor-collision-router.system.test.mjs.sha256 +1 -1
- tools/behcs/route-health-baseline.mjs +198 -0
- tools/behcs/route-health-baseline.mjs.sha256 +1 -0
docs/LIRIS-ROUTE-HEALTH-BASELINE-2026-06-13.hbp
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
LIRISRHBHDR|host=liris|date=2026-06-13|topic=route-health-baseline-core-fabric-ports|responds_to=operator-continue+acer-next-queue-route-health-baseline+ask-fabric-council-q-1781391748125-66153y|mode=read-only-probe-only|tag=PROVEN-LIVE|json=0
|
| 2 |
+
LIRISRHBWHAT|tool=tools/behcs/route-health-baseline.mjs|test=tests/route-health-baseline.unit.test.mjs|targets=127.0.0.1:4944-health+127.0.0.1:4947-health+192.168.1.50:4949-health|emits=RHBHDR+RHBROUTE+RHBSUM+RHBGATE|process_launch=0|tag=PROVEN|json=0
|
| 3 |
+
LIRISRHBASKFABRIC|council_request=council-q-1781391748125-66153y|fabric_health=4944-UP-service-super-asolaria-os-dashboard-liris-mirror|fabric_self_status=bases-127.0.0.1-4944+192.168.1.50-4949|fabric_route_map=acer-route-map-visible-through-HBP-any-with-pool+omnispindle+omniflywheel+behcs1024|tag=OBSERVED|json=0
|
| 4 |
+
LIRISRHBLIVEPROBE|ts=2026-06-14T01:21:53.519Z|liris_mirror_4944=UP-http200-latency51ms-body_sha16-EF05AA02D91DDD2C|behcs_bus_4947=ROUTE_BOUNDARY-http404-latency23ms-body_sha16-7EE09A56CB814E42|acer_fabric_4949=UP-http200-latency6ms-body_sha16-3E78F83A1F072752|summary=up2-boundary1-down0-timeout0-unprobed0-answered3-all_core_answered1-all_core_up0|tag=PROVEN-LIVE|json=0
|
| 5 |
+
LIRISRHBBOUNDARY|meaning_4947=port-answers-but-/health-is-not-the-bus-health-contract-on-this-vantage|not_an_outage=1|no_restart=1|no_spawn=1|no_write=1|no_mint=1|repair_requires_operator-gated-supervisor-step-not-this-tool|tag=BOUNDARY|json=0
|
| 6 |
+
LIRISRHBVERIFY|node_check=OK|self_test=7-of-7-PASS|unit_test=9-of-9-PASS|live_probe=3-of-3-answered|hbp_rows_only=1|injection_safe=1|body_sha16_preserved=1|tag=PROVEN|json=0
|
| 7 |
+
LIRISRHBFT|state=ROUTE-HEALTH-BASELINE-BUILT|next=ask-fabric-after-push-for-specific-4947-health-contract-or-next-not-yet-built-item|cosign=not-touched|json=0
|
docs/LIRIS-ROUTE-HEALTH-BASELINE-2026-06-13.hbp.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
9333E05A3D70AE866682266FB2151476F50FE7A0BCE9A409677219F828BB57F9 LIRIS-ROUTE-HEALTH-BASELINE-2026-06-13.hbp
|
tests/route-health-baseline.unit.test.mjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import assert from 'node:assert/strict';
|
| 2 |
+
import { readFileSync } from 'node:fs';
|
| 3 |
+
import { dirname, join } from 'node:path';
|
| 4 |
+
import { fileURLToPath } from 'node:url';
|
| 5 |
+
import test from 'node:test';
|
| 6 |
+
|
| 7 |
+
import {
|
| 8 |
+
ROUTE_TARGETS,
|
| 9 |
+
emitRows,
|
| 10 |
+
normalizeProbe,
|
| 11 |
+
probeTarget,
|
| 12 |
+
routeHealthBaseline,
|
| 13 |
+
selfTest,
|
| 14 |
+
} from '../tools/behcs/route-health-baseline.mjs';
|
| 15 |
+
|
| 16 |
+
const repo = dirname(dirname(fileURLToPath(import.meta.url)));
|
| 17 |
+
|
| 18 |
+
test('self-test passes', () => {
|
| 19 |
+
assert.equal(selfTest().ok, true);
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
+
test('the three core route targets are fixed and ordered', () => {
|
| 23 |
+
assert.deepEqual(ROUTE_TARGETS.map((t) => `${t.id}:${new URL(t.base).port}`), [
|
| 24 |
+
'liris_mirror_4944:4944',
|
| 25 |
+
'behcs_bus_4947:4947',
|
| 26 |
+
'acer_fabric_4949:4949',
|
| 27 |
+
]);
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
test('probe states classify UP, route boundary, degraded, down, timeout, and unprobed', () => {
|
| 31 |
+
assert.equal(normalizeProbe({ id: 'liris_mirror_4944', ok: true, http_status: 200 }).state, 'UP');
|
| 32 |
+
assert.equal(normalizeProbe({ id: 'liris_mirror_4944', http_status: 404 }).state, 'ROUTE_BOUNDARY');
|
| 33 |
+
assert.equal(normalizeProbe({ id: 'liris_mirror_4944', http_status: 503 }).state, 'HTTP_DEGRADED');
|
| 34 |
+
assert.equal(normalizeProbe({ id: 'liris_mirror_4944', error: 'ECONNREFUSED' }).state, 'DOWN');
|
| 35 |
+
assert.equal(normalizeProbe({ id: 'liris_mirror_4944', error: 'timeout' }).state, 'TIMEOUT');
|
| 36 |
+
assert.equal(normalizeProbe({ id: 'liris_mirror_4944' }).state, 'UNPROBED');
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
test('baseline fills missing targets and summarizes answered/up counts', () => {
|
| 40 |
+
const b = routeHealthBaseline([
|
| 41 |
+
{ id: 'liris_mirror_4944', ok: true, http_status: 200 },
|
| 42 |
+
{ id: 'acer_fabric_4949', http_status: 404 },
|
| 43 |
+
], { ts: '2026-06-13T23:00:00.000Z' });
|
| 44 |
+
assert.equal(b.results.length, 3);
|
| 45 |
+
assert.equal(b.summary.up, 1);
|
| 46 |
+
assert.equal(b.summary.route_boundary, 1);
|
| 47 |
+
assert.equal(b.summary.unprobed, 1);
|
| 48 |
+
assert.equal(b.summary.answered, 2);
|
| 49 |
+
assert.equal(b.summary.all_core_answered, false);
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
test('emitted rows are HBP-only, single-line, and read-only gated', () => {
|
| 53 |
+
const rows = emitRows(routeHealthBaseline([
|
| 54 |
+
{ id: 'liris_mirror_4944', ok: true, http_status: 200, error: 'x|json=0\nFAKE|x=1' },
|
| 55 |
+
]));
|
| 56 |
+
assert.ok(rows.every((r) => r.endsWith('|json=0') && !/[\r\n]/.test(r) && !r.includes('{"')));
|
| 57 |
+
assert.ok(rows.some((r) => r.startsWith('RHBGATE|') && r.includes('no_spawn=1') && r.includes('no_restart=1')));
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
test('body_sha16 is preserved when normalized baselines are emitted', () => {
|
| 61 |
+
const b = routeHealthBaseline([{ id: 'liris_mirror_4944', ok: true, http_status: 200, body: 'route-health-proof-body' }]);
|
| 62 |
+
const hash = b.results[0].body_sha16;
|
| 63 |
+
assert.notEqual(hash, 'E3B0C44298FC1C14');
|
| 64 |
+
assert.ok(emitRows(b).some((r) => r.includes(`body_sha16=${hash}`)));
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
test('probeTarget supports injected read-only fetchers', async () => {
|
| 68 |
+
const target = ROUTE_TARGETS[0];
|
| 69 |
+
const result = await probeTarget(target, {
|
| 70 |
+
timeoutMs: 100,
|
| 71 |
+
fetcher: async (url, opts) => {
|
| 72 |
+
assert.equal(url, 'http://127.0.0.1:4944/health');
|
| 73 |
+
assert.equal(opts.method, 'GET');
|
| 74 |
+
return { ok: true, status: 200, text: async () => '{"ok":true}' };
|
| 75 |
+
},
|
| 76 |
+
});
|
| 77 |
+
assert.equal(result.state, 'UP');
|
| 78 |
+
assert.equal(result.source, 'live-fetch');
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
test('probeTarget turns fetch failures into DOWN/TIMEOUT observations, not throws', async () => {
|
| 82 |
+
const down = await probeTarget(ROUTE_TARGETS[0], { fetcher: async () => { throw new Error('ECONNREFUSED'); } });
|
| 83 |
+
assert.equal(down.state, 'DOWN');
|
| 84 |
+
const timeout = await probeTarget(ROUTE_TARGETS[0], { fetcher: async () => { const e = new Error('abort'); e.name = 'AbortError'; throw e; } });
|
| 85 |
+
assert.equal(timeout.state, 'TIMEOUT');
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
test('tool has no spawn/exec/write/mint/restart capability', () => {
|
| 89 |
+
const src = readFileSync(join(repo, 'tools/behcs/route-health-baseline.mjs'), 'utf8');
|
| 90 |
+
assert.equal(/child_process|spawnSync|\.spawn\(|execSync|writeFileSync|appendFileSync|mintPid|Start-Process|Stop-Process/.test(src), false);
|
| 91 |
+
});
|
tests/route-health-baseline.unit.test.mjs.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
A54EF188069EB4D937113DB2DDFD3E39D8E75CD258D8AE560872066485917EFB route-health-baseline.unit.test.mjs
|
tests/supervisor-collision-router.system.test.mjs
CHANGED
|
@@ -90,6 +90,7 @@ const sealedFiles = [
|
|
| 90 |
'docs/LIRIS-SLICE-ENGINE-LAW-2026-06-13.hbp',
|
| 91 |
'docs/LIRIS-SELFHEAL-200STEP-INTEGRATION-2026-06-13.hbp',
|
| 92 |
'docs/ACER-SLICE-ENGINE-LIVE-CRANK-RECEIPT-2026-06-13.hbp',
|
|
|
|
| 93 |
'docs/acer-ecosystem-fullbody-receipt-20260612.hbp',
|
| 94 |
'docs/CLAIMSLEDGER-2026-06-12.hbp',
|
| 95 |
'docs/CLAIMSGATE-PARITY-BASELINE-2026-06-12.hbp',
|
|
@@ -208,6 +209,7 @@ const sealedFiles = [
|
|
| 208 |
'tools/behcs/mlc-line-watcher.mjs',
|
| 209 |
'tools/behcs/omni-engine-loop.mjs',
|
| 210 |
'tools/behcs/heal-envelope-emitter.mjs',
|
|
|
|
| 211 |
'tools/behcs/logical-extreme-stress.mjs',
|
| 212 |
'tools/behcs/logical-stress-cube-feed.mjs',
|
| 213 |
'tools/behcs/brown-hilbert-expansion-stress.mjs',
|
|
@@ -234,6 +236,7 @@ const sealedFiles = [
|
|
| 234 |
'tests/mlc-line-watcher.unit.test.mjs',
|
| 235 |
'tests/omni-engine-loop.unit.test.mjs',
|
| 236 |
'tests/heal-envelope-emitter.unit.test.mjs',
|
|
|
|
| 237 |
'tests/logical-extreme-stress.unit.test.mjs',
|
| 238 |
'tests/logical-stress-cube-feed.unit.test.mjs',
|
| 239 |
'tests/brown-hilbert-expansion-stress.unit.test.mjs',
|
|
|
|
| 90 |
'docs/LIRIS-SLICE-ENGINE-LAW-2026-06-13.hbp',
|
| 91 |
'docs/LIRIS-SELFHEAL-200STEP-INTEGRATION-2026-06-13.hbp',
|
| 92 |
'docs/ACER-SLICE-ENGINE-LIVE-CRANK-RECEIPT-2026-06-13.hbp',
|
| 93 |
+
'docs/LIRIS-ROUTE-HEALTH-BASELINE-2026-06-13.hbp',
|
| 94 |
'docs/acer-ecosystem-fullbody-receipt-20260612.hbp',
|
| 95 |
'docs/CLAIMSLEDGER-2026-06-12.hbp',
|
| 96 |
'docs/CLAIMSGATE-PARITY-BASELINE-2026-06-12.hbp',
|
|
|
|
| 209 |
'tools/behcs/mlc-line-watcher.mjs',
|
| 210 |
'tools/behcs/omni-engine-loop.mjs',
|
| 211 |
'tools/behcs/heal-envelope-emitter.mjs',
|
| 212 |
+
'tools/behcs/route-health-baseline.mjs',
|
| 213 |
'tools/behcs/logical-extreme-stress.mjs',
|
| 214 |
'tools/behcs/logical-stress-cube-feed.mjs',
|
| 215 |
'tools/behcs/brown-hilbert-expansion-stress.mjs',
|
|
|
|
| 236 |
'tests/mlc-line-watcher.unit.test.mjs',
|
| 237 |
'tests/omni-engine-loop.unit.test.mjs',
|
| 238 |
'tests/heal-envelope-emitter.unit.test.mjs',
|
| 239 |
+
'tests/route-health-baseline.unit.test.mjs',
|
| 240 |
'tests/logical-extreme-stress.unit.test.mjs',
|
| 241 |
'tests/logical-stress-cube-feed.unit.test.mjs',
|
| 242 |
'tests/brown-hilbert-expansion-stress.unit.test.mjs',
|
tests/supervisor-collision-router.system.test.mjs.sha256
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
|
|
|
|
| 1 |
+
88B8683F149B8BAC6E4C291AD94A27479EA388DC27F3423C3A3A90DBC156F044 supervisor-collision-router.system.test.mjs
|
tools/behcs/route-health-baseline.mjs
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
// route-health-baseline.mjs - read-only health baseline for the core fabric routes.
|
| 3 |
+
//
|
| 4 |
+
// CONTEXT (operator continue cycle 2026-06-13): after SLICE-ENGINE-LAW and the
|
| 5 |
+
// heal-envelope-emitter, the next queued item is a route-health baseline for the
|
| 6 |
+
// core moving lanes: Liris mirror :4944, BEHCS bus :4947, Acer fabric :4949.
|
| 7 |
+
//
|
| 8 |
+
// SAFETY: probe-only, HBP rows only, no spawn/exec/write/mint/restart. A DOWN or
|
| 9 |
+
// BOUNDARY result is an observation, not an action. Engines remain operator-gated.
|
| 10 |
+
|
| 11 |
+
import { createHash } from 'node:crypto';
|
| 12 |
+
import { resolve } from 'node:path';
|
| 13 |
+
import { fileURLToPath } from 'node:url';
|
| 14 |
+
|
| 15 |
+
export const TOOL_ID = 'route-health-baseline.v1';
|
| 16 |
+
export const ROUTE_TARGETS = Object.freeze([
|
| 17 |
+
Object.freeze({ id: 'liris_mirror_4944', role: 'liris-mirror-dashboard', base: 'http://127.0.0.1:4944', path: '/health' }),
|
| 18 |
+
Object.freeze({ id: 'behcs_bus_4947', role: 'behcs-bus', base: 'http://127.0.0.1:4947', path: '/health' }),
|
| 19 |
+
Object.freeze({ id: 'acer_fabric_4949', role: 'acer-fabric-dashboard', base: 'http://192.168.1.50:4949', path: '/health' }),
|
| 20 |
+
]);
|
| 21 |
+
|
| 22 |
+
const STATES = Object.freeze(['UP', 'ROUTE_BOUNDARY', 'HTTP_DEGRADED', 'DOWN', 'TIMEOUT', 'UNPROBED']);
|
| 23 |
+
const safe = (s) => { try { return String(s == null ? '' : s).replace(/[|\r\n]/g, '_'); } catch { return '_'; } };
|
| 24 |
+
const sha16 = (s) => createHash('sha256').update(String(s ?? ''), 'utf8').digest('hex').slice(0, 16).toUpperCase();
|
| 25 |
+
const isObj = (x) => x !== null && typeof x === 'object';
|
| 26 |
+
|
| 27 |
+
function targetById(id) {
|
| 28 |
+
return ROUTE_TARGETS.find((t) => t.id === id);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
function portOf(base) {
|
| 32 |
+
try {
|
| 33 |
+
const u = new URL(base);
|
| 34 |
+
return Number(u.port || (u.protocol === 'https:' ? 443 : 80));
|
| 35 |
+
} catch {
|
| 36 |
+
return 0;
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
function stateFor(sample) {
|
| 41 |
+
const status = Number(sample.http_status || 0);
|
| 42 |
+
const error = safe(sample.error).toLowerCase();
|
| 43 |
+
if (sample.ok === true && status >= 200 && status < 300) return 'UP';
|
| 44 |
+
if ([400, 401, 403, 404].includes(status)) return 'ROUTE_BOUNDARY';
|
| 45 |
+
if (status > 0) return 'HTTP_DEGRADED';
|
| 46 |
+
if (error.includes('timeout') || error.includes('abort')) return 'TIMEOUT';
|
| 47 |
+
if (error) return 'DOWN';
|
| 48 |
+
return 'UNPROBED';
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
export function normalizeProbe(sample = {}) {
|
| 52 |
+
const raw = isObj(sample) ? sample : {};
|
| 53 |
+
const target = targetById(raw.id) || {};
|
| 54 |
+
const base = raw.base || target.base || '';
|
| 55 |
+
const path = raw.path || target.path || '/health';
|
| 56 |
+
const body = raw.body == null ? '' : String(raw.body).slice(0, 512);
|
| 57 |
+
const priorHash = /^[0-9A-Fa-f]{16}$/.test(String(raw.body_sha16 || '')) ? String(raw.body_sha16).toUpperCase() : '';
|
| 58 |
+
const out = {
|
| 59 |
+
id: safe(raw.id || target.id || 'unknown'),
|
| 60 |
+
role: safe(raw.role || target.role || 'unknown'),
|
| 61 |
+
base: safe(base),
|
| 62 |
+
port: Number(raw.port || portOf(base)),
|
| 63 |
+
path: safe(path),
|
| 64 |
+
ok: raw.ok === true,
|
| 65 |
+
http_status: Number(raw.http_status || 0),
|
| 66 |
+
latency_ms: Number.isFinite(raw.latency_ms) ? Math.max(0, Math.round(raw.latency_ms)) : 0,
|
| 67 |
+
source: safe(raw.source || 'sample'),
|
| 68 |
+
error: safe(raw.error || ''),
|
| 69 |
+
body_sha16: priorHash || sha16(body),
|
| 70 |
+
};
|
| 71 |
+
out.state = STATES.includes(raw.state) ? raw.state : stateFor(out);
|
| 72 |
+
out.answered = ['UP', 'ROUTE_BOUNDARY', 'HTTP_DEGRADED'].includes(out.state);
|
| 73 |
+
return out;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
export function routeHealthBaseline(samples = [], opts = {}) {
|
| 77 |
+
const byId = new Map((Array.isArray(samples) ? samples : []).map((s) => [s?.id, s]));
|
| 78 |
+
const results = ROUTE_TARGETS.map((target) => normalizeProbe(byId.get(target.id) || { ...target, source: 'unprobed' }));
|
| 79 |
+
const counts = Object.fromEntries(STATES.map((s) => [s, results.filter((r) => r.state === s).length]));
|
| 80 |
+
const answered = results.filter((r) => r.answered).length;
|
| 81 |
+
return {
|
| 82 |
+
tool: TOOL_ID,
|
| 83 |
+
ts: opts.ts || new Date().toISOString(),
|
| 84 |
+
process_launch: 0,
|
| 85 |
+
read_only: true,
|
| 86 |
+
results,
|
| 87 |
+
summary: {
|
| 88 |
+
target_count: ROUTE_TARGETS.length,
|
| 89 |
+
up: counts.UP,
|
| 90 |
+
route_boundary: counts.ROUTE_BOUNDARY,
|
| 91 |
+
http_degraded: counts.HTTP_DEGRADED,
|
| 92 |
+
down: counts.DOWN,
|
| 93 |
+
timeout: counts.TIMEOUT,
|
| 94 |
+
unprobed: counts.UNPROBED,
|
| 95 |
+
answered,
|
| 96 |
+
all_core_up: counts.UP === ROUTE_TARGETS.length,
|
| 97 |
+
all_core_answered: answered === ROUTE_TARGETS.length,
|
| 98 |
+
},
|
| 99 |
+
};
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
export function emitRows(input) {
|
| 103 |
+
try {
|
| 104 |
+
const baseline = isObj(input) && Array.isArray(input.results) ? input : routeHealthBaseline([]);
|
| 105 |
+
const s = baseline.summary || {};
|
| 106 |
+
const rows = [
|
| 107 |
+
`RHBHDR|tool=${TOOL_ID}|ts=${safe(baseline.ts)}|target_count=${safe(s.target_count)}|read_only=1|process_launch=0|json=0`,
|
| 108 |
+
];
|
| 109 |
+
for (const r of baseline.results.map(normalizeProbe)) {
|
| 110 |
+
rows.push(`RHBROUTE|id=${safe(r.id)}|role=${safe(r.role)}|base=${safe(r.base)}|port=${safe(r.port)}|path=${safe(r.path)}|state=${safe(r.state)}|http_status=${safe(r.http_status)}|latency_ms=${safe(r.latency_ms)}|source=${safe(r.source)}|error=${safe(r.error)}|body_sha16=${safe(r.body_sha16)}|json=0`);
|
| 111 |
+
}
|
| 112 |
+
rows.push(`RHBSUM|up=${safe(s.up)}|route_boundary=${safe(s.route_boundary)}|http_degraded=${safe(s.http_degraded)}|down=${safe(s.down)}|timeout=${safe(s.timeout)}|unprobed=${safe(s.unprobed)}|answered=${safe(s.answered)}|all_core_up=${s.all_core_up ? 1 : 0}|all_core_answered=${s.all_core_answered ? 1 : 0}|json=0`);
|
| 113 |
+
rows.push('RHBGATE|read_only=1|probe_only=1|no_spawn=1|no_write=1|no_mint=1|no_restart=1|operator_gate_for_repair=1|tag=PROVEN-READONLY-BASELINE|json=0');
|
| 114 |
+
return rows;
|
| 115 |
+
} catch {
|
| 116 |
+
return [
|
| 117 |
+
`RHBHDR|tool=${TOOL_ID}|ts=INVALID|target_count=${ROUTE_TARGETS.length}|read_only=1|process_launch=0|json=0`,
|
| 118 |
+
'RHBERROR|error=emit-threw-on-hostile-input|json=0',
|
| 119 |
+
'RHBGATE|read_only=1|probe_only=1|no_spawn=1|no_write=1|no_mint=1|no_restart=1|operator_gate_for_repair=1|tag=INVALID-HELD|json=0',
|
| 120 |
+
];
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
export async function probeTarget(target, opts = {}) {
|
| 125 |
+
const started = Date.now();
|
| 126 |
+
const fetcher = opts.fetcher || globalThis.fetch;
|
| 127 |
+
const timeoutMs = Number.isFinite(opts.timeoutMs) ? Math.max(1, opts.timeoutMs) : 1500;
|
| 128 |
+
const base = target?.base || '';
|
| 129 |
+
const path = target?.path || '/health';
|
| 130 |
+
if (typeof fetcher !== 'function') {
|
| 131 |
+
return normalizeProbe({ ...target, source: 'live-fetch', error: 'fetch-unavailable', latency_ms: Date.now() - started });
|
| 132 |
+
}
|
| 133 |
+
const controller = new AbortController();
|
| 134 |
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
| 135 |
+
try {
|
| 136 |
+
const url = new URL(path, base).toString();
|
| 137 |
+
const response = await fetcher(url, { method: 'GET', signal: controller.signal, headers: { accept: 'application/json,text/plain,*/*' } });
|
| 138 |
+
const body = typeof response.text === 'function' ? await response.text() : '';
|
| 139 |
+
return normalizeProbe({
|
| 140 |
+
...target,
|
| 141 |
+
source: 'live-fetch',
|
| 142 |
+
ok: response.ok === true,
|
| 143 |
+
http_status: response.status,
|
| 144 |
+
latency_ms: Date.now() - started,
|
| 145 |
+
body,
|
| 146 |
+
});
|
| 147 |
+
} catch (err) {
|
| 148 |
+
const name = err && err.name === 'AbortError' ? 'timeout' : (err?.code || err?.message || 'fetch-error');
|
| 149 |
+
return normalizeProbe({ ...target, source: 'live-fetch', error: name, latency_ms: Date.now() - started });
|
| 150 |
+
} finally {
|
| 151 |
+
clearTimeout(timer);
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
export async function probeRoutes(opts = {}) {
|
| 156 |
+
const results = [];
|
| 157 |
+
for (const target of ROUTE_TARGETS) {
|
| 158 |
+
results.push(await probeTarget(target, opts));
|
| 159 |
+
}
|
| 160 |
+
return routeHealthBaseline(results, { ts: opts.ts });
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
export function selfTest() {
|
| 164 |
+
const samples = [
|
| 165 |
+
{ id: 'liris_mirror_4944', ok: true, http_status: 200, latency_ms: 12, body: '{"ok":true}' },
|
| 166 |
+
{ id: 'behcs_bus_4947', error: 'ECONNREFUSED', latency_ms: 4 },
|
| 167 |
+
{ id: 'acer_fabric_4949', http_status: 404, latency_ms: 30, body: '{"routes":["/health"]}' },
|
| 168 |
+
];
|
| 169 |
+
const b = routeHealthBaseline(samples, { ts: '2026-06-13T23:00:00.000Z' });
|
| 170 |
+
const rows = emitRows(b);
|
| 171 |
+
const checks = [
|
| 172 |
+
['three-targets', b.results.length === 3],
|
| 173 |
+
['state-classification', b.results[0].state === 'UP' && b.results[1].state === 'DOWN' && b.results[2].state === 'ROUTE_BOUNDARY'],
|
| 174 |
+
['summary-counts', b.summary.up === 1 && b.summary.down === 1 && b.summary.route_boundary === 1 && b.summary.answered === 2],
|
| 175 |
+
['hbp-only', rows.every((r) => r.endsWith('|json=0') && !/[\r\n]/.test(r) && !r.includes('{"'))],
|
| 176 |
+
['gate-readonly', rows.some((r) => r.startsWith('RHBGATE|') && r.includes('no_restart=1'))],
|
| 177 |
+
['injection-safe', emitRows(routeHealthBaseline([{ id: 'liris_mirror_4944', error: 'x|json=0\nFAKE|x=1' }])).every((r) => !/[\r\n]/.test(r))],
|
| 178 |
+
['body-hash-preserved', rows.some((r) => r.includes(`body_sha16=${b.results[0].body_sha16}`)) && b.results[0].body_sha16 !== sha16('')],
|
| 179 |
+
].map(([name, ok]) => ({ name, ok: Boolean(ok) }));
|
| 180 |
+
return { ok: checks.every((c) => c.ok), checks };
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1]);
|
| 184 |
+
if (isMain) {
|
| 185 |
+
if (process.argv.includes('--self-test')) {
|
| 186 |
+
const r = selfTest();
|
| 187 |
+
for (const c of r.checks) console.log(`${c.ok ? 'PASS' : 'FAIL'} ${c.name}`);
|
| 188 |
+
process.exit(r.ok ? 0 : 1);
|
| 189 |
+
}
|
| 190 |
+
const baseline = process.argv.includes('--probe')
|
| 191 |
+
? await probeRoutes()
|
| 192 |
+
: routeHealthBaseline([
|
| 193 |
+
{ id: 'liris_mirror_4944', ok: true, http_status: 200, latency_ms: 0, source: 'fabric-health-read', body: 'ok=true service=super-asolaria-os-dashboard-liris-mirror port=4944' },
|
| 194 |
+
{ id: 'behcs_bus_4947', source: 'not-probed-in-demo' },
|
| 195 |
+
{ id: 'acer_fabric_4949', http_status: 404, source: 'fabric-route-map-read', body: 'route-map-visible health-path-boundary' },
|
| 196 |
+
], { ts: '2026-06-13T23:00:00.000Z' });
|
| 197 |
+
for (const row of emitRows(baseline)) console.log(row);
|
| 198 |
+
}
|
tools/behcs/route-health-baseline.mjs.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
9BA42D52CBED8603AFD7552AF7742BE23B93882E6B950BCBD97D52FC45399A2D route-health-baseline.mjs
|