Skip to content

feat: close the sponsord operational gaps (top-up hold guard, monitoring, lifecycle, Compose gate page) - #102

Merged
thiras merged 6 commits into
mainfrom
feat/sponsord-ops-coverage
Oct 7, 2026
Merged

thiras merged 6 commits into
mainfrom
feat/sponsord-ops-coverage

Conversation

@thiras

@thiras thiras commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

An audit against upstream decdn/sponsord @ ce104e7 found every config setting already wired, but several operational gaps. This closes all of them except a Kubernetes path for sponsord, which is out of scope.

Changes

Top-up hold guard (ansible/roles/sponsord). Upstream #41 makes the keeper hold further pool top-ups while one is unconfirmed (sponsord_pool_topup_unconfirmed_since_unix > 0). A restart forgets the hold, and the pool can then be refilled twice.

  • tasks/topup-hold.yml reads /metrics at the address the running process listens on.
  • It refuses a restart (and a decommission stop) while a hold is on, or while /metrics does not answer.
  • It passes a daemon that has not bound yet: upstream starts the keeper only after binding, so such a daemon holds nothing.
  • The restart-inputs comparison is now the role's only restart trigger. The tasks that write an input no longer notify the handler themselves, so no write can restart sponsord past the guard.
  • Override: ANSIBLE_ARGS='-e {"sponsord_restart_ignore_topup_hold":true}'.

Log levels. sponsord_log_level and sponsord_onramp_log_level are rendered as RUST_LOG. The onramp is built without tracing's env-filter, so it accepts only a level or target=level pairs, and the role refuses anything else.

Backup and decommission.

  • make backup archives sponsord's credentials, encrypted on the host: the treasury keystore and password, the API token and the Turnstile secret.
  • make decommission checks every sponsord host for a held top-up first, then removes the node, the onramp (stopping and disabling the role's Caddy) and sponsord, and tears down Alloy.
  • One typed confirmation per run, and its prompt lists every service the run stops on those hosts.
  • /etc/sponsord is kept, and nothing on-chain is touched.

Monitoring. charts/decdn-node/files/monitoring/sponsord/ adds a dashboard and 8 alert rules:

  • down;
  • keeper failing;
  • stale pool check, and no successful check since start;
  • top-up held, and held over 2 h;
  • empty pool;
  • sponsord's own request errors.

The rules have promtool unit tests, run from lint-helm against a copy with the annotations stripped. The chart does not render this subdirectory. The treasury wallet's own balance is not covered, because sponsord does not export it.

Compose. The onramp mounts /etc/sponsord/onramp-gate/ read-only for ONRAMP_GATE_TEMPLATE. The README adds the manual hold check before a restart, and a manual backup command.

Known gap

A restart this repo does not cause, such as a crash or a package upgrade restarting services, still forgets a hold. It is documented, and SponsordTopupHeld is how an operator learns of a hold. A "hold lost to a restart" alert and a needrestart exclusion could follow.

Verification

  • make lint, make lint-ansible, make lint-helm, make lint-compose, make test-scripts.
  • make security: no new high findings. The one new medium is the loopback HTTP /metrics probe, the same accepted finding as the existing /healthz probe.
  • make molecule SCENARIOS='sponsord sponsord-install validation-sponsord sponsord-onramp sponsord-onramp-lifecycle lifecycle' all pass. They cover:
    • the hold refusal, override and clear;
    • undoing a change during a hold;
    • an unreadable /metrics;
    • the backup archive decrypted and its members checked;
    • decommission refused for a held top-up and for a missing or wrong confirmation, a real run, then a second run.
  • Every sponsord_* name in the rules and the dashboard checked against upstream crates/sponsord/src/metrics.rs.

🤖 Generated with Claude Code

thiras and others added 5 commits October 7, 2026 21:40
…sord_log_level

A restart forgets sponsord's hold on an unconfirmed top-up (upstream #41), and
the pool can then be refilled twice. The role now reads /metrics before it
flushes a pending restart and fails while the hold is on, leaving the restart
for the next run; sponsord_restart_ignore_topup_hold overrides it. Also renders
sponsord_log_level as RUST_LOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
backup.yml archives sponsord's credentials (treasury keystore and password, API
token, Turnstile secret), encrypted on the host. decommission.yml checks every
sponsord host for a held top-up, then removes the node, the onramp (and the
role's Caddy) and sponsord, with one typed confirmation per host, and tears down
Alloy on either kind of host. Covered by molecule/sponsord-onramp-lifecycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
files/monitoring/sponsord/ holds a dashboard (uid decdn-sponsord) and rules for
keeper failures, a stale pool read, a held top-up, an empty pool and request
errors, with promtool unit tests in lint-helm. The chart does not render them:
sponsord has no Kubernetes path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arts

The onramp mounts /etc/sponsord/onramp-gate/ read-only for ONRAMP_GATE_TEMPLATE
(a fixed directory, so no setting can mount a credential into the page server).
The README adds the manual top-up hold check before a restart, a backup
command, and notes the onramp ignores RUST_LOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sponsord: the restart-inputs comparison is the only restart trigger, and the
  top-up hold guard runs before it queues anything, so a write landing back on
  the recorded content can no longer restart past the guard (nor can
  --force-handlers). The guard probes the address the running process listens
  on, passes a daemon that has not bound yet, requires sponsord's keeper series,
  accepts a labelled gauge, retries briefly, and words its refusal for a restart
  or a stop. The documented override is the JSON form the boolean check accepts.
- decommission: check the hold again right before sponsord's stop (the prompt
  can wait minutes); each prompt lists every service the run stops on its hosts.
- backup: the Turnstile secret at the onramp role's path, required on onramp hosts.
- sponsord_onramp_log_level: the onramp does read RUST_LOG (as Targets: a level
  or target=level pairs); correct the docs that said it ignored it.
- alerts: SponsordRequestFailures fires on a code's first appearance (upstream
  exports it from its first error, already non-zero); a pool never read since
  start is SponsordPoolNeverChecked (3h), not "stale" after 15m. Tests model the
  exporter as it is, with internal-code and resolve cases.
- tests: a revert during a hold, an unreadable /metrics, backup and log-level
  negatives, the decommission play order, a second decommission run, no
  confirmation left behind by a refusal.
- docs: co-located caveats in lifecycle.md, the gate page's path rule, safer
  manual check and backup in the Compose README, stale scenario and galaxy text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 7, 2026 19:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Secret exposure, host-cap enforcement, backup integrity, and validation issues remain unresolved.

5 open findings
What changed in this PR

Adds sponsord operational safeguards, lifecycle automation, monitoring assets, log controls, and Compose gate-page support.

Changes:

  • Guards sponsord restarts/decommissioning during unconfirmed top-ups.
  • Adds encrypted backups, multi-service decommissioning, and log-level configuration.
  • Adds sponsord dashboards, alerts, tests, and Compose documentation.
File Description
tests/​scripts-test.sh Extends lifecycle and Compose checks.
Makefile Passes the Prometheus image to chart tests.
docs/​lifecycle.md Documents sponsord lifecycle procedures.
compose/​tests/​invariants.jq Allows the gate-page mount.
compose/​sponsord-onramp.env.example Documents gate templates and logging.
compose/​README.md Adds hold, backup, and gate-page guidance.
compose/​compose.yaml Mounts the custom gate-page directory.
charts/​decdn-node/​tests/​sponsord-alerts_test.yml Tests sponsord alert behavior.
charts/​decdn-node/​tests/​render-test.sh Validates sponsord monitoring assets.
charts/​decdn-node/​files/​monitoring/​sponsord/​prometheus-alerts.yml Adds sponsord alert rules.
charts/​decdn-node/​files/​monitoring/​sponsord/​dashboard-sponsord.json Adds the sponsord dashboard.
charts/​decdn-node/​files/​monitoring/​README.md Documents sponsord monitoring.
charts/​decdn-node/​CHANGELOG.md Records monitoring additions.
ansible/​roles/​sponsord/​templates/​sponsord.env.j2 Renders RUST_LOG.
ansible/​roles/​sponsord/​tasks/​topup-hold.yml Implements the top-up hold guard.
ansible/​roles/​sponsord/​tasks/​source.yml Removes direct restart notification.
ansible/​roles/​sponsord/​tasks/​main.yml Centralizes guarded restart decisions.
ansible/​roles/​sponsord/​tasks/​install.yml Removes installer restart notifications.
ansible/​roles/​sponsord/​tasks/​decommission.yml Adds sponsord decommissioning.
ansible/​roles/​sponsord/​tasks/​backup.yml Adds encrypted credential backups.
ansible/​roles/​sponsord/​README.md Documents new sponsord operations.
ansible/​roles/​sponsord/​defaults/​main.yml Adds lifecycle, logging, and guard defaults.
ansible/​roles/​sponsord_onramp/​templates/​sponsord-onramp.env.j2 Renders onramp RUST_LOG.
ansible/​roles/​sponsord_onramp/​tasks/​main.yml Validates onramp logging configuration.
ansible/​roles/​sponsord_onramp/​tasks/​decommission.yml Adds onramp/Caddy decommissioning.
ansible/​roles/​sponsord_onramp/​README.md Documents logging and removal.
ansible/​roles/​sponsord_onramp/​defaults/​main.yml Adds logging and lifecycle defaults.
ansible/​roles/​decdn_node/​tasks/​decommission.yml Shares confirmation across services.
ansible/​roles/​decdn_node/​README.md Notes co-located sponsord removal.
ansible/​README.md Updates lifecycle and scenario documentation.
ansible/​playbooks/​decommission.yml Decommissions all deployed services.
ansible/​playbooks/​backup.yml Backs up sponsord credentials.
ansible/​molecule/​validation-sponsord/​converge.yml Adds negative validation cases.
ansible/​molecule/​sponsord/​verify.yml Verifies sponsord logging output.
ansible/​molecule/​sponsord/​side_effect.yml Exercises the hold guard.
ansible/​molecule/​sponsord/​molecule.yml Configures log-level coverage.
ansible/​molecule/​sponsord/​files/​sponsord-stub Simulates hold metrics.
ansible/​molecule/​sponsord-onramp/​side_effect.yml References lifecycle coverage.
ansible/​molecule/​sponsord-onramp/​molecule.yml Documents shared scenarios.
ansible/​molecule/​sponsord-onramp-lifecycle/​verify.yml Verifies backup and decommission results.
ansible/​molecule/​sponsord-onramp-lifecycle/​side_effect.yml Exercises lifecycle operations.
ansible/​molecule/​sponsord-onramp-lifecycle/​molecule.yml Defines the lifecycle scenario.
ansible/​molecule/​sponsord-install/​molecule.yml Mirrors logging test configuration.
ansible/​Makefile Registers the lifecycle scenario.
ansible/​galaxy/​README.md Documents collection lifecycle features.
ansible/​galaxy/​galaxy.yml Updates collection description.
ansible/​galaxy/​CHANGELOG.md Records sponsord operational changes.
AGENTS.md Adds sponsord operational invariants.
.claude/​skills/​grafana-dashboards/​SKILL.md Adds sponsord monitoring guidance.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ansible/playbooks/decommission.yml Outdated
Comment thread compose/compose.yaml Outdated
Comment thread ansible/roles/sponsord/tasks/backup.yml Outdated
Comment thread ansible/roles/sponsord_onramp/tasks/main.yml Outdated
Comment thread charts/decdn-node/tests/render-test.sh Outdated
… logging

- decommission.yml: the first play enforces decdn_decommission_max_hosts against
  every host the run touches (each role checks only its own play, so a node-only
  and a sponsord-only host both passed), then checks for a held top-up.
- compose: the onramp starts through a pinned /bin/sh check that refuses an
  ONRAMP_GATE_TEMPLATE resolving outside /etc/sponsord/onramp-gate/; upstream
  would serve any file, and /run/secrets holds the daemon token and the
  Turnstile secret. invariants.jq pins the script byte for byte (no flags).
- backup: every archived credential must be a regular file; tar stores a
  symlink as the link, not the key.
- log levels: each comma-separated part must be a level or target=level; a
  misspelled level or a bare non-level word silences the onramp (and reads as a
  target for sponsord).
- render-test.sh: the local promtool test step honours PROMTOOL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thiras
thiras merged commit 66ac138 into main Oct 7, 2026
39 checks passed
@thiras
thiras deleted the feat/sponsord-ops-coverage branch October 7, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants