igneum/docs/community/discord-hooks.md
igneum-labs 583eeea437 site: light-theme app captures, the X9 withdrawal correction (ledger X36), the Discord vanity link
Light theme shows light screenshots on the miner and app pages (the
0.3.16 renders from the RTX 5090 Windows rig's live state, the 390 px
pair included). The X9 never shipped: pre-orders 26 December 2025,
withdrawn mid-May 2026 before any unit shipped, no benchmark; every
public sentence that had it shipping now says so, with k about 0.33
labelled as the claimed, unmeasured core; ledger X36, notes on X34, X35,
M34 and C2. Every public Discord link is https://discord.gg/igneum.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 08:01:03 +00:00

7.1 KiB

Discord webhooks: the bot's posts to #numbers, #announcements and #incidents

6 October 2026. Three channel webhooks named "Igneum" (the Bot role in the server kit, docs/community/reddit/discord.md) carry the project's numbers to the Discord server. One script, tools/community/discord-hooks.mjs (Node 22, standard library only), shapes every post as a single embed: ember accent (#F2541B), the site icon as thumbnail, plain words, at most eight fields, UK time in the footer with UTC in brackets, never a mention, never a reply. Dry run is the default.

The posts

Post Channel When (UK) Content
Network pulse #numbers 03:00, 09:00, 15:00, 21:00 chain block and blocks/s over the 6 h window, hash rate and active vote keys ("a card runs several"), difficulty and its 6 h change, last lock (index, share of weight, age, voters), shards paid in the window and IGN paid to provers in the last 5 min, median proof lag and provers, reward and ramp, miner version share when /api/live exposes engines, Devnet 2 in one line (boxes, height, last gate PASS or FAIL)
Daily digest #numbers 09:00 a small monospace table, yesterday against now, with the 24 h deltas
Weekly numbers #numbers Monday 09:00 the reddit kit's template (docs/community/reddit/posts/02-weekly-numbers-template.md) as six fields with the last-week column, a link to the ledger
Release #announcements on a publish, by the shipper version, three download links with full sha256, node commit, consensus digest, up to five "what changed" lines read from the release plan
Incident open / resolve #incidents by hand, or by the watcher UTC time, what happened, who is affected per tier, what is being done; then cause, the rule or fix, duration

Every number comes from the public API (/api/stats, /api/live?window=300, /api/supply; docs/api/public-stats.md). The Devnet 2 line reads the fleet file and uses its numbers and the gate word only; the seed address and the state text never pass.

The watcher

watch (and every tick) opens one incident per condition and resolves it when the condition has been clear for 2 minutes. One open incident per condition at a time; the state file carries since / clear_since / open.

Condition Opens when Text
finality_paused finality.active false, or the locked index unchanged, for over 5 min finality paused; miners and provers paid as usual; nothing final until weight returns
proof_lag proving.median_proof_lag_s over 900 s proving behind; blocks final as usual, proofs late
observer_silent stale with age_s over 180 s, or the API unreachable for over 3 min the live numbers are stale; the chain is unaffected

The watcher's texts are fixed sentences in the script, each a stated rule, and are the only incidents nobody typed.

Commands

node tools/community/discord-hooks.mjs pulse | digest | weekly [--live] [--force]
node tools/community/discord-hooks.mjs release 0.3.14 --windows <url> --windows-sha <hex> --mac <url> --mac-sha <hex> --hive <url> --hive-sha <hex> \
     --node-commit 4c6b129d --digest "b18ed271 (thirteen fields, unchanged)" --plan docs/plans/release-0.3.14.md --section "1. Why" [--changed "line"]... [--live]
node tools/community/discord-hooks.mjs incident open --what "..." --affected "..." --doing "..." [--at 2026-10-06T15:44:00Z] [--id inc-...] [--live]
node tools/community/discord-hooks.mjs incident resolve --id inc-... --cause "..." --fix "..." [--at ...] [--live]
node tools/community/discord-hooks.mjs watch [--live]        one watcher pass
node tools/community/discord-hooks.mjs tick  [--live]        one scheduler pass (what the London clock says is due, then watch)
node tools/community/discord-hooks.mjs preview               rebuild tools/community/out/preview.html
node tools/community/discord-hooks.mjs check                 which webhooks are configured (names, never values)
node --test tools/community/discord-hooks.test.mjs

Dry run writes tools/community/out/<key>.json (git-ignored) and renders out/preview.html, a Discord-like dark page of every payload in the folder. The release subcommand takes the "what changed" lines from --changed flags or from the named section of the release plan: bullet lines as they are, table rows by their first cell, cut at the first semicolon, a long parenthetical dropped, 160 characters at most; a line that trips the guard is dropped with a note on stderr.

Guard rails

Rail How
Forbidden strings before every post: the founder's name and logins, hosting providers, internal host names, machine ids, internal paths, IP addresses, a standalone 32-hex token (a sha256 passes), any dl.igneum.network path outside /public/, a webhook URL, any mention; the post is refused with the pattern named
Limits title 256, description 4,096, field value 1,024, eight fields, 6,000 in total, content 2,000; refused, never cut silently
Idempotency a post id file (posts in the state) keyed pulse:<date>:<hour>, digest:<date>, weekly:<date>, release:<version>, `incident::open
429 exponential backoff from 1 s, doubling to 60 s, retry_after honoured, six tries, then exit 1
State written only after a successful live post; a dry run keeps its own state in out/state.json
Mentions allowed_mentions: {parse: []} on every payload and a guard that refuses @everyone, @here and user mentions in the text

Credentials and deployment

~/.config/igneum/discord, mode 600, KEY=VALUE lines: DISCORD_WEBHOOK_NUMBERS, DISCORD_WEBHOOK_ANNOUNCEMENTS, DISCORD_WEBHOOK_INCIDENTS. Never in the repository, never printed; check prints which keys are set.

The scheduler runs on igneum-build-1 because the Mac sleeps: infra/build-server/discord-hooks/{igneum-discord-hooks.service, igneum-discord-hooks.timer, install.sh}, a tick every minute. install.sh copies the script to /srv/discord-hooks/bin, the credentials file to /srv/discord-hooks/env (mode 600, owner build, over ssh stdin) and enables the timer. The repository rule says the box never holds a secret; a webhook URL is one, so the script refuses unless IGNEUM_SECRET_ON_BOX_OK=1 is set, which records the coordinator's ruling. A leaked webhook is deleted in Discord and the file replaced.

Tested, 6 October 2026

Dry run against the live API at 18:51 UTC rendered all six posts (pulse 641 characters, digest 477, weekly 1,699, release 1,501, incident open 716 and resolve 659). The guard refused the fleet file's seed address and a line with an internal path from the release plan. Finality was paused at render time (finality.active false, last lock 6842 at about 18:42Z, the newest checkpoints at 55% of total weight): the watcher's first condition, seen in dry run. Not yet run: a live post (no credentials file yet), the install on the box.

The server invite

The standing invite is https://discord.gg/igneum (created 6 October 2026, main; the vanity names discord.gg/igneum and discord.gg/igneumnetwork were free on 3 October and are not yet claimed). The home page's join line and any public text use this URL; nobody asks for it again.