OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay commits / 2c95fe2

feat(dashboard): show fleet diversity as advertised, not as configured
The operator dashboard rendered version, health, disk, RAM, restarts and watchdog per relay — nothing about who owns a box or what it fails with. So the concentration the operator/failureDomain split just made expressible was still invisible to the person looking at the fleet. The probe now reads /.well-known/hiverelay.json, not config.json. That matters for the same reason the dubai incident does: config is what someone wrote, the capability doc is what the relay actually advertises, and only the latter is what clients count when they build a quorum. /api/state gains a `diversity` block. Against the live fleet right now: relays 13 operators 1 operatorsUndeclared 12 failureDomains 13 regions 4 (AS, EU, ME, NA) note: "13 relays, 1 declared operator — spread across 13 failure domains, but not independent" Before the split the same fleet reported as 13 operators, because an undeclared operator fell back to the relay's own pubkey. Undeclared is now counted separately and never toward independence: unknown is not independent. Twelve relays read undeclared because they run v0.24.3, which predates the field — also honest, and it resolves as they take rc.7+. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2c95fe20c07e483d0afe130b8e9962a5f16bbb76
by bigdestiny2 <33146784+bigdestiny2@users.noreply.github.com> · 2026-07-28T19:25:49.000Z

Changed files (1) +55 -1

dashboard-admin-operator/server.cjs +55 -1
……@@ -134,11 +134,15 @@ function probeNode (relay) {
134134 'TS=$(systemctl show hiverelay -p TasksCurrent --value 2>/dev/null || echo \'?\')',
135135 'ENV=$(systemctl show hiverelay -p Environment --value 2>/dev/null | tr \'\' \';\' | tr \' \' \';\')',
136136 'CFG=$(cat ~/hiverelay/config.json 2>/dev/null || cat ~/.hiverelay/config.json 2>/dev/null || echo \'{}\')',
137 // Capability doc is what the relay ACTUALLY advertises. config.json is
138 // what someone wrote; the dubai incident is the standing reminder that
139 // those differ. Diversity must be read from the advertised document.
140 'CAP=$(curl -fsS --max-time 4 http://127.0.0.1:9100/.well-known/hiverelay.json 2>/dev/null || echo \'{}\')',
137141 'STATUS=$(curl -fsS --max-time 8 http://127.0.0.1:9100/status 2>/dev/null || echo \'{}\')',
138142 'HEALTH=$(curl -fsS --max-time 4 http://127.0.0.1:9100/health 2>/dev/null || echo \'\')',
139143 'WD=$(systemctl is-enabled hiverelay-health-watchdog.timer 2>/dev/null || echo missing)',
140144 'HO=0; echo "$HEALTH" | grep -q \'"ok"[[:space:]]*:[[:space:]]*true\' && HO=1',
141 'printf \'{"version":"%s","diskPct":%s,"diskGB":%s,"swapMB":%s,"ramUsedPct":%s,"loadAvg":%s,"uptime":"%s","restarts":%s,"memCurrentMB":%s,"tasks":%s,"env":"%s","watchdog":"%s","healthOk":%s,"config":%s,"status":%s}\\n\' "$V" "${D:-0}" "${DG:-0}" "${SW:-0}" "${RM:-0}" "${LA:-0}" "$UP" "${RT:-0}" "${MC:-0}" "${TS:-0}" "$ENV" "$WD" "$HO" "$CFG" "$STATUS"'
145 'printf \'{"version":"%s","diskPct":%s,"diskGB":%s,"swapMB":%s,"ramUsedPct":%s,"loadAvg":%s,"uptime":"%s","restarts":%s,"memCurrentMB":%s,"tasks":%s,"env":"%s","watchdog":"%s","healthOk":%s,"config":%s,"status":%s,"cap":%s}\\n\' "$V" "${D:-0}" "${DG:-0}" "${SW:-0}" "${RM:-0}" "${LA:-0}" "$UP" "${RT:-0}" "${MC:-0}" "${TS:-0}" "$ENV" "$WD" "$HO" "$CFG" "$STATUS" "$CAP"'
142146 ].join('\n')
143147
144148 // `ip` comes from the inventory, and POST /api/relay appends to that
……@@ -191,8 +195,17 @@ function probeNode (relay) {
191195
192196 const features = extractFeatures(config, env, st)
193197
198 // Advertised identity. `operator` is who owns it (the trust axis);
199 // `failureDomain` is what dies with it (the scheduling axis). They
200 // were one field until they were split, which is why a single-owner
201 // fleet used to report as many independent operators.
202 const cap = d.cap || {}
203
194204 resolve({
195205 ...relay,
206 operator: cap.operator || null,
207 failureDomain: cap.failureDomain || null,
208 advertisedRegion: cap.region || null,
196209 status,
197210 reachable: true,
198211 version: d.version || 'none',
……@@ -379,6 +392,7 @@ const server = http.createServer(async (req, res) => {
379392 return sendJson(res, 200, {
380393 nodes: fleetState,
381394 summary: computeSummary(),
395 diversity: computeDiversity(),
382396 lastProbe,
383397 channels,
384398 interval: INTERVAL_S,
……@@ -658,6 +672,46 @@ function serveStatic (res, filePath) {
658672 } catch { sendJson(res, 404, { error: 'not found' }) }
659673}
660674
675// Diversity, as advertised rather than as configured.
676//
677// The point of this block is to make concentration impossible to miss. A fleet
678// can be spread across many hosts and regions and still be ONE operator — and
679// a quorum drawn from it is only as independent as that number. Reporting
680// relays-as-operators is exactly the accounting error that let a single-owner
681// fleet satisfy a minOperators floor of five.
682function computeDiversity () {
683 const online = fleetState.filter(n => n.reachable)
684 const operators = new Set()
685 const domains = new Set()
686 const regions = new Set()
687 let undeclared = 0
688
689 for (const n of online) {
690 if (n.operator) operators.add(n.operator)
691 else undeclared++
692 // Fall back to the relay name for spreading only — never for operator.
693 domains.add(n.failureDomain || n.name)
694 regions.add(n.advertisedRegion || n.region || 'Unknown')
695 }
696
697 return {
698 relays: online.length,
699 operators: operators.size,
700 operatorNames: [...operators].sort(),
701 // Undeclared is UNKNOWN, not independent. Surfaced separately so it can
702 // never be quietly counted toward an independence claim.
703 operatorsUndeclared: undeclared,
704 failureDomains: domains.size,
705 regions: regions.size,
706 regionNames: [...regions].sort(),
707 // The honest headline: many boxes under one owner is not many operators.
708 singleOperator: operators.size <= 1 && online.length > 1,
709 concentrationNote: operators.size <= 1 && online.length > 1
710 ? `${online.length} relays, ${operators.size || 0} declared operator — spread across ${domains.size} failure domains, but not independent`
711 : null
712 }
713}
714
661715function computeSummary () {
662716 const nodes = fleetState
663717 const totalRamGB = nodes.reduce((s, n) => s + (n.ramGB || 0), 0)