status.sigil.ml is the public face of operational state for Sigil mainnet and the Evolve canary. It speaks for the chain during an incident.

Architecture

  • Static page: index.html plus inline CSS and JS, served from a Cloudflare R2 bucket.
  • State file: state.json in the same R2 bucket, polled by the page every 30 s.
  • Worker: worker.js, a Cloudflare Worker bound to the R2 bucket. Accepts Alertmanager webhooks and operator manual-incident POSTs.
The page intentionally does not run on the same Kubernetes cluster as sigil-rpc. When the chain or the platform cluster is down, the page must stay up — that is exactly when the audience needs it.

Components tracked

ComponentSource of truth
Mainnet RPC (rpc.sigil.ml)Cloudflare WAF event log + up{job="sigil-rpc-mainnet"}
Mainnet RPC direct (rpc-direct.sigil.ml)ingress-nginx access log + up{job="sigil-rpc-direct-mainnet"}
Mainnet explorerCloudflare R2 site + Pages health
Canary RPC (canary-rpc.sigil.ml)Cloudflare WAF + up{job="sigil-rpc-canary"}
Canary RPC directingress-nginx + up{job="sigil-rpc-direct-canary"}
Canary explorerCloudflare R2 + Pages
Wallet extension distributionChrome Web Store admin API
Canary faucetFaucet service health endpoint
Snapshot infrastructureSnapshot publisher health
Documentation (docs.sigil.ml)Cloudflare Pages health
When a source-of-truth signal degrades, Alertmanager fires an alert tagged with component=<name>, the Worker receives the webhook, and the relevant state.components[] entry flips to warn or bad.

Operator procedures

Post a manual incident

curl -X POST https://status.sigil.ml/incidents \
  -H "Authorization: Bearer $OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "severity": "bad",
    "summary": "Block production halted; investigating.",
    "message": "Validators val-1 and val-2 unreachable at 14:32 UTC; on-call paged."
  }'
severity is warn (yellow) or bad (red). Anything else falls through to warn.

Resolve an incident

curl -X POST https://status.sigil.ml/incidents \
  -H "Authorization: Bearer $OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "severity": "warn",
    "summary": "All systems operational",
    "message": "Block production resumed at 14:51 UTC. Root cause: <link to post-mortem>."
  }'
The Worker preserves the last 9 incidents in state.incidents[]. Older entries roll off; permanent records live in the post-mortem repo.

Rotate the operator token

wrangler secret put OPERATOR_TOKEN
# Distribute the new token to on-call via the secure channel.
# Revoke cached copies in the ops vault.

Rotate the Alertmanager token

wrangler secret put ALERTMANAGER_TOKEN

# Update Alertmanager's webhook config:
# k8s/mainnet/61-alertmanager.yaml → receivers[name=status-page].webhookConfigs[].httpConfig.authorization.credentials
# Live Kubernetes secret key: sigil-alertmanager-secrets/status-page-alertmanager-token

kubectl -n sigil-platform exec deploy/alertmanager -- /bin/wget -qO- localhost:9093/-/reload

When the status page itself is broken

The page polls state.json every 30 s. If the file is missing or malformed, the page renders “Status data unavailable” with a yellow dot — not green, not silent. Failure modes the page handles gracefully:
  • state.json 404 → “Status data unavailable”.
  • state.json malformed → “Status data unavailable”.
  • R2 outage → page falls back to the last cached state in the browser (edge cache TTL = 30 s).
Failure modes that need operator intervention:
  • index.html 404 → page fully down. R2 is the single point of failure for v1. Mitigation: keep a local copy of index.html in the repo and serve it from a personal machine through a temporary Cloudflare tunnel.
  • DNS hijack → out of scope; see Cloudflare DNSSEC posture.

Verifying the deployment

# Page reachable.
curl -sI https://status.sigil.ml | head -1
# Expected: HTTP/2 200

# State endpoint reachable.
curl -s https://status.sigil.ml/state.json | jq '.summary, .summary_state'

# Worker accepts a manual incident.
curl -X POST https://status.sigil.ml/incidents \
  -H "Authorization: Bearer $OPERATOR_TOKEN" \
  -d '{"severity":"warn","summary":"Smoke test","message":"runbook verification"}'

Cost

R2 storage < 1 GiB → ~0.015/mo.Workerrequests<100k/dayfreetier.Customdomainfree.Total 0.015/mo. Worker requests < 100 k/day → free tier. Custom domain → free. Total ~0.20/month.

See also