You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #786 (sub-issue C; decision record in the 2026-08-13 comment there). Post-GA fast-follow — the pre-GA bar is A+B+D.
Goal
The commit gate's flat refusal of non-allowlisted keys becomes a pending, host-verified, out-of-band approval: the dashboard proposes ("change payout wallet to 4A…xyz — approve?"), the operator approves on Telegram, the host verifies, deny-on-timeout, per-operator rate limit. The #338 semantics, extended from its two fixed verbs to gate-refused config commits, reusing its shipped surface (telegram.control.allowed_ids / confirm_timeout).
Hard constraints (unchanged from the decomposition)
The host verifies the approval. The shipped ControlGate lives in the dashboard container and is documented as UX, not the boundary — a wallet swap cannot trust a container-relayed "approved". The host runner holds the bot token via config.json and drives its own confirm exchange. This split — container proposes, host independently verifies — has no peer precedent (none of Talos/HAOS/TrueNAS/OPNsense/Synology/UniFi/umbrel run a comparable model). It is the highest-engineering-risk item in the parity roadmap; do not estimate it as "add more keys to Telegram two-way control commands (restart / apply) with access control #338".
The never-approve set stays refused even with approval (decision Q2): ssh.enabled/ssh.authorized_key; the approval channel's own identity as a block (telegram.bot_token/chat_id/telegram.control.*); dashboard.control.enabled; the tamper-evidence toggles (telegram.events.wallet_changed, clearnet_exposed); and dashboard.auth.password — it doubles as the root console password every boot, so changing it is root-credential rotation and stays physical-only (D). Peer evidence is convergent: no studied project lets the admin channel's own trust anchor be changed through itself.
DEST-flagged destructive changes stay refused in v1 (decision Q5): node local↔remote switches and the Data-dir moves are now dashboard-confirmable but assert_safe_dir is a blocklist, not an allowlist #728 data-dir destination rule are reachable only via D or a local session. No peer extends remote approval to its most destructive actions; TrueNAS instead adds typed re-entry friction locally. A future destructive-ops channel needs its own threat model, not a bundled extension of this one.
v1 approvable set
The non-DEST sensitive keys: wallet addresses, view keys/scan heights, healthchecks.ping_url, notifications.*, stratum password/TLS/bind, node credentials, onion exposure, LAN-access toggles. Config editor surfaces the class as _approval_keys, same pattern as _editable_keys/_confirm_keys.
Known accepted limitation
Telegram is the only approval channel and its identity is in the never-approve set — a lost or rotated bot token disables C until a D (physical) recovery. Same shape as Talos's CA: no revocation list, physical rotation only. Stated here so it is a decision, not a discovery.
Coverage
Tier 1 heavy (the gate is pure shell, the prompt/diff logic pure Python); tier 4 for one live Telegram approval round-trip on the bench.
Dependencies
Nothing in code; operationally requires Telegram already configured — a box without it bootstraps via D, by design.
Part of #786 (sub-issue C; decision record in the 2026-08-13 comment there). Post-GA fast-follow — the pre-GA bar is A+B+D.
Goal
The commit gate's flat refusal of non-allowlisted keys becomes a pending, host-verified, out-of-band approval: the dashboard proposes ("change payout wallet to 4A…xyz — approve?"), the operator approves on Telegram, the host verifies, deny-on-timeout, per-operator rate limit. The #338 semantics, extended from its two fixed verbs to gate-refused config commits, reusing its shipped surface (
telegram.control.allowed_ids/confirm_timeout).Hard constraints (unchanged from the decomposition)
ControlGatelives in the dashboard container and is documented as UX, not the boundary — a wallet swap cannot trust a container-relayed "approved". The host runner holds the bot token via config.json and drives its own confirm exchange. This split — container proposes, host independently verifies — has no peer precedent (none of Talos/HAOS/TrueNAS/OPNsense/Synology/UniFi/umbrel run a comparable model). It is the highest-engineering-risk item in the parity roadmap; do not estimate it as "add more keys to Telegram two-way control commands (restart / apply) with access control #338".ssh.enabled/ssh.authorized_key; the approval channel's own identity as a block (telegram.bot_token/chat_id/telegram.control.*);dashboard.control.enabled; the tamper-evidence toggles (telegram.events.wallet_changed,clearnet_exposed); anddashboard.auth.password— it doubles as the root console password every boot, so changing it is root-credential rotation and stays physical-only (D). Peer evidence is convergent: no studied project lets the admin channel's own trust anchor be changed through itself.v1 approvable set
The non-DEST sensitive keys: wallet addresses, view keys/scan heights,
healthchecks.ping_url,notifications.*, stratum password/TLS/bind, node credentials, onion exposure, LAN-access toggles. Config editor surfaces the class as_approval_keys, same pattern as_editable_keys/_confirm_keys.Known accepted limitation
Telegram is the only approval channel and its identity is in the never-approve set — a lost or rotated bot token disables C until a D (physical) recovery. Same shape as Talos's CA: no revocation list, physical rotation only. Stated here so it is a decision, not a discovery.
Coverage
Tier 1 heavy (the gate is pure shell, the prompt/diff logic pure Python); tier 4 for one live Telegram approval round-trip on the bench.
Dependencies
Nothing in code; operationally requires Telegram already configured — a box without it bootstraps via D, by design.