eleven / messenger Public

server: status host labels — notable names doing things (#698)

* server: status host labels become notable-name-action pairs

box-1 -> a stable whimsical label ("hypatia-sketching") hashed from the
machine id: notable names real and fictional x actions that deliberately
avoid the dashboard's status vocabulary (no sleeping/resting — a box is
not an instance). No index; derivation is deterministic per box and the
hash direction means the label leaks nothing. Dry-run pins a fixture
machine id so committed fixtures are stable across dev machines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: finish the label rename in status.md

---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 66c92c59a020817c843d059057904d6dfc35d270 parent e37be0e
6 files changed, +48 −13
  • deploy/status-publish.sh +41 −6
  • docs/status.md +3 −3
  • test/status-contract.test.mjs +1 −1
  • test/status-fixtures/fleet-at-capacity.json +1 −1
  • test/status-fixtures/fleet-getting-full.json +1 −1
  • test/status-fixtures/fleet.json +1 −1
diff --git a/deploy/status-publish.sh b/deploy/status-publish.sh
index 73c2a4d..c2e0b6d 100755
--- a/deploy/status-publish.sh
+++ b/deploy/status-publish.sh
@@ -23,8 +23,9 @@
# (plenty / getting-full / at-capacity; comfortable / busy / critical).
# The caps, ratios, free GB, and percentages are read internally to pick
# the word and MUST NEVER reach the JSON — no remaining-slot counts, no GB,
-# no percentages, no cap values. Host labels are anonymous ordinals
-# ("box-1"), never peer URLs, IPs, or hostnames.
+# no percentages, no cap values. Host labels are whimsical name-action
+# pairs hashed from the machine id ("ada-stargazing") — stable per box,
+# never peer URLs, IPs, hostnames, or the machine id itself.
#
# books.json — AGGREGATED INCOME ONLY. Active-subscription count (by tier —
# tier names are the public price book), MRR total in EUR, gross collected
@@ -181,6 +182,34 @@ load_word() { case "$1" in 0) echo comfortable ;; 1) echo busy ;; *) echo critic
# ---- fleet.json --------------------------------------------------------------
+# box_label derives a stable, whimsical, anonymous name for this box —
+# "ada-stargazing" — from a hash of the machine id. Notable names, real and
+# fictional (history, literature, myth, science, pop culture), paired with an
+# action. The hash direction only (id -> label) is published, so the label
+# leaks nothing; the action list deliberately avoids the dashboard's own
+# status vocabulary (no sleeping/waking/resting — a box is not an instance).
+box_label() {
+ local names=(ada alan grace marie nikola leonardo cleopatra homer sappho
+ darwin einstein curie turing hopper tesla gandalf frodo sherlock watson
+ ahab ishmael gatsby scout atticus hermione matilda mowgli merlin arthur
+ morgana beowulf odysseus penelope achilles athena apollo freya thor loki
+ amelia boudicca cyrano quixote sancho ophelia viola rosalind puck oberon
+ titania darcy pip scrooge nemo phileas zorro heidi galileo kepler newton
+ archimedes hypatia confucius rumi frida vincent wolfgang ludwig clara)
+ local actions=(stargazing juggling whistling painting sailing gardening
+ baking skating humming doodling fishing knitting strolling yodeling
+ beekeeping fencing waltzing sketching brewing climbing cartwheeling
+ birdwatching pondering inventing exploring composing conducting
+ spelunking kayaking orienteering puppeteering tinkering)
+ local idfile=${STATUS_MACHINE_ID_FILE:-/etc/machine-id}
+ local seed
+ seed=$( { cat "$idfile" 2>/dev/null || hostname; } | sha256sum | cut -c1-16)
+ # two independent hex slices -> indices; bash arithmetic handles hex via 16#
+ local n=$(( 16#${seed:0:8} % ${#names[@]} ))
+ local a=$(( 16#${seed:8:8} % ${#actions[@]} ))
+ printf '%s-%s' "${names[$n]}" "${actions[$a]}"
+}
+
build_fleet() {
local out=$1 now total systemd_n hib awake_json asleep_json versions
now=$(now_iso)
@@ -283,12 +312,14 @@ build_fleet() {
# Mesh membership isn't enabled on this box, and the status-publisher's S3
# key is scoped to the status bucket, so it couldn't list mesh heartbeats
# anyway — multi-box joins here when the mesh goes live (it will need a read
- # grant on the mesh prefix). Labels are ALWAYS anonymous ordinals ("box-1",
- # "box-2", …), never peer URLs, IPs, or hostnames. Per-box awake/asleep are
- # the fleet-wide counts (one box = the whole fleet), already public above.
+ # grant on the mesh prefix). Labels are whimsical name-action pairs derived
+ # from a HASH of the machine id ("ada-stargazing") — stable per box, and
+ # never peer URLs, IPs, hostnames, or the machine id itself. Per-box
+ # awake/asleep are the fleet-wide counts (one box = the whole fleet),
+ # already public above.
local boxes
boxes=$(sqj "SELECT json_array(json_object(
- 'label', 'box-1',
+ 'label', '$(box_label)',
'awake', json('$awake_json'),
'asleep', json('$asleep_json'),
'load', json('$load_json')))")
@@ -575,6 +606,10 @@ u_str LISTEN 0 4096 /run/lchat/charlie.example.sock 12003 * 0
u_str LISTEN 0 4096 /run/lchat/delta.example.sock 12004 * 0
EOF
printf '%s\n' lchat-home lchat-push lchat-cloud lchat-litestream > "$FIXDIR/active-units.txt" # connect down on purpose
+ # Fixed machine-id fixture so the dry-run label is deterministic everywhere
+ # (a dev Mac has no /etc/machine-id; hostname fallback would vary per machine).
+ echo dry-run-fixture-machine-id > "$FIXDIR/machine-id"
+ export STATUS_MACHINE_ID_FILE="$FIXDIR/machine-id"
echo 42 > "$FIXDIR/replication-lag.txt"
# Capacity fixtures (control caps + disk + meminfo): one set per public band
diff --git a/docs/status.md b/docs/status.md
index 1064530..0bfcc08 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -32,7 +32,7 @@ aggregation boundary lives in the generator and holds by construction:
a box's `load` is `comfortable` / `busy` / `critical`. **Never** remaining
slot counts, free GB, percentages, or cap values — the caps and ratios are
read internally to pick the word and die in the generator. Host labels are
- anonymous ordinals (`box-1`), never peer URLs, IPs, or hostnames.
+ whimsical name-action pairs hashed from the machine id (`ada-stargazing`) — stable per box; never peer URLs, IPs, hostnames, or the machine id itself.
- **Books: aggregated income only** (decision #19 in #550: income publishes
aggregated, never per-payer; invariant 12: no PII in our stores). Never
customer ids, emails, subscription ids, per-payer lines, or timestamps of
@@ -50,7 +50,7 @@ aggregation boundary lives in the generator and holds by construction:
{
"generated_at": "2026-07-11T13:00:00Z",
"instances": { "total": 6, "systemd": 2, "hibernating": 4, "awake": 2, "asleep": 2 },
- "hosts": { "count": 1, "boxes": [ { "label": "box-1", "awake": 2, "asleep": 2, "load": "comfortable" } ] },
+ "hosts": { "count": 1, "boxes": [ { "label": "ada-stargazing", "awake": 2, "asleep": 2, "load": "comfortable" } ] },
"capacity": { "level": "plenty" },
"versions": [ { "version": "v100", "count": 3 } ],
"services": { "home": true, "push": true, "cloud": true, "connect": true },
@@ -78,7 +78,7 @@ at-capacity outright), and memory utilization (`/proc/meminfo`). The caps are
read from `/data/control.env`. A box's `load` word bands memory utilization
the same way (`comfortable` / `busy` / `critical`, null when unknown). Only
the words publish — never the ratios, GB, or caps behind them. `hosts` v1 is
-the local box alone (`box-1`); multi-box joins when the mesh goes live.
+the local box alone; multi-box joins when the mesh goes live.
`books.json` (or `{"available": false, "reason": "…"}` when billing isn't
configured / Stripe is unreachable):
diff --git a/test/status-contract.test.mjs b/test/status-contract.test.mjs
index ba0d166..011d376 100644
--- a/test/status-contract.test.mjs
+++ b/test/status-contract.test.mjs
@@ -116,7 +116,7 @@ await test("hosts + capacity carry only the allowed abstract schema", () => {
for (const b of f.hosts.boxes) {
assert.deepEqual(Object.keys(b).sort(), ["asleep", "awake", "label", "load"], name + ": box keys");
// Labels are anonymous ordinals — never a hostname, IP, or URL.
- assert.match(b.label, /^box-\d+$/, name + ": box label must be an ordinal");
+ assert.match(b.label, /^[a-z]+-[a-z]+$/, name + ": box label must be a name-action pair");
assert.ok([null, "comfortable", "busy", "critical"].includes(b.load),
name + ": box load is a band word or null, got " + b.load);
}
diff --git a/test/status-fixtures/fleet-at-capacity.json b/test/status-fixtures/fleet-at-capacity.json
index acf75f3..adf06c0 100644
--- a/test/status-fixtures/fleet-at-capacity.json
+++ b/test/status-fixtures/fleet-at-capacity.json
@@ -1 +1 @@
-{"generated_at":"2026-07-11T14:22:57Z","instances":{"total":6,"systemd":2,"hibernating":4,"awake":2,"asleep":2},"hosts":{"count":1,"boxes":[{"label":"box-1","awake":2,"asleep":2,"load":"critical"}]},"capacity":{"level":"at-capacity"},"versions":[{"version":"v100","count":3},{"version":"v101","count":2},{"version":"unknown","count":1}],"services":{"home":true,"push":true,"cloud":true,"connect":false},"replication":{"healthy":true,"lag_seconds":42}}
+{"generated_at":"2026-07-11T14:49:29Z","instances":{"total":6,"systemd":2,"hibernating":4,"awake":2,"asleep":2},"hosts":{"count":1,"boxes":[{"label":"hypatia-sketching","awake":2,"asleep":2,"load":"critical"}]},"capacity":{"level":"at-capacity"},"versions":[{"version":"v100","count":3},{"version":"v101","count":2},{"version":"unknown","count":1}],"services":{"home":true,"push":true,"cloud":true,"connect":false},"replication":{"healthy":true,"lag_seconds":42}}
diff --git a/test/status-fixtures/fleet-getting-full.json b/test/status-fixtures/fleet-getting-full.json
index 0e0487b..b1089f3 100644
--- a/test/status-fixtures/fleet-getting-full.json
+++ b/test/status-fixtures/fleet-getting-full.json
@@ -1 +1 @@
-{"generated_at":"2026-07-11T14:22:57Z","instances":{"total":6,"systemd":2,"hibernating":4,"awake":2,"asleep":2},"hosts":{"count":1,"boxes":[{"label":"box-1","awake":2,"asleep":2,"load":"comfortable"}]},"capacity":{"level":"getting-full"},"versions":[{"version":"v100","count":3},{"version":"v101","count":2},{"version":"unknown","count":1}],"services":{"home":true,"push":true,"cloud":true,"connect":false},"replication":{"healthy":true,"lag_seconds":42}}
+{"generated_at":"2026-07-11T14:49:29Z","instances":{"total":6,"systemd":2,"hibernating":4,"awake":2,"asleep":2},"hosts":{"count":1,"boxes":[{"label":"hypatia-sketching","awake":2,"asleep":2,"load":"comfortable"}]},"capacity":{"level":"getting-full"},"versions":[{"version":"v100","count":3},{"version":"v101","count":2},{"version":"unknown","count":1}],"services":{"home":true,"push":true,"cloud":true,"connect":false},"replication":{"healthy":true,"lag_seconds":42}}
diff --git a/test/status-fixtures/fleet.json b/test/status-fixtures/fleet.json
index 30a4301..6dede19 100644
--- a/test/status-fixtures/fleet.json
+++ b/test/status-fixtures/fleet.json
@@ -1 +1 @@
-{"generated_at":"2026-07-11T14:22:57Z","instances":{"total":6,"systemd":2,"hibernating":4,"awake":2,"asleep":2},"hosts":{"count":1,"boxes":[{"label":"box-1","awake":2,"asleep":2,"load":"comfortable"}]},"capacity":{"level":"plenty"},"versions":[{"version":"v100","count":3},{"version":"v101","count":2},{"version":"unknown","count":1}],"services":{"home":true,"push":true,"cloud":true,"connect":false},"replication":{"healthy":true,"lag_seconds":42}}
+{"generated_at":"2026-07-11T14:49:28Z","instances":{"total":6,"systemd":2,"hibernating":4,"awake":2,"asleep":2},"hosts":{"count":1,"boxes":[{"label":"hypatia-sketching","awake":2,"asleep":2,"load":"comfortable"}]},"capacity":{"level":"plenty"},"versions":[{"version":"v100","count":3},{"version":"v101","count":2},{"version":"unknown","count":1}],"services":{"home":true,"push":true,"cloud":true,"connect":false},"replication":{"healthy":true,"lag_seconds":42}}