1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! `decdn node rotate-key` — the operator key-rotation runbook
//! (`adr/appendix-operator-key-rotation.md`) as a command (#1034).
//!
//! The runbook's decision tree branches on *which* key is being rotated, and
//! the branches share almost nothing: one is a single transaction that changes
//! no economic state, the other moves the entire on-chain identity across a
//! 14-day window. So `--key` selects a submodule rather than a code path
//! inside one function:
//!
//! - `iroh` — rebind the wire `NodeId` through `CapacityBond.bindNodeId`.
//! - `eth` — migrate the Ethereum address: deregister → unbond → re-onboard.
//!
//! What they do share is the failure this command exists to prevent. A node
//! whose local key is not the on-chain-bound one is **un-slashable**:
//! `SlashJudge._checkRegistered(challengedNode, nodeId)` resolves the challenged
//! *address* through `nodeIdOf` and requires the cited id to equal the bound
//! one, so evidence gathered against the key the node is actually serving under
//! reverts — `NodeIdMismatch` when the operator is bound to some other id,
//! `NodeNotRegistered` when it is bound to none. Either way the challenge
//! fails, which is a protocol fault rather than an outage: the node keeps
//! serving and keeps getting paid while its bond is unreachable. No
//! ordering of steps here may produce that state even transiently, which is
//! what fixes the sequencing in both submodules — the iroh path commits
//! `node.secret` only after the bind confirms, and the eth path reads chain
//! state to pick its phase instead of trusting a flag about what already ran.
//!
//! Neither path restarts the daemon. The iroh key is not hot-reloadable
//! (`crates/node/src/runtime/reload.rs` covers `observability.log_level`,
//! `cache.pinned_hashes`, and `security.*` — nothing that rebuilds the iroh
//! endpoint), and a command that must keep working with
//! the daemon *down* cannot depend on its admin RPC. Drain, stop, rotate,
//! restart stays the operator's sequence; the command names the steps it did
//! not take.
pub
pub
use ;
use Path;
use cli;
/// Dispatch a `decdn node rotate-key` invocation to the path `--key` names.
pub async
/// What the confirmation gate decides, before any IO. Split from
/// [`confirm_or_bail`] the way `deregister::decide_confirmation` is, so the
/// gate is testable without a TTY.
pub
/// Decide whether to prompt. Same rule as `decdn node deregister`: every run
/// that reaches the gate is about to submit, so there is no "not needed" arm.
pub const
/// Disclose the consequences and, on a terminal, wait for `y`.
///
/// `--yes` suppresses the *prompt*, never the disclosure — "don't ask" must not
/// silently become "don't tell", the rule `unbond` and `deregister` both
/// follow. `disclosure` writes the path-specific wording; `subject` names the
/// operation in the two refusal messages ("rotation cancelled" and so on).
///
/// # Errors
///
/// Fails if the disclosure or prompt cannot be written, if stdin cannot be
/// read, if the run is headless without `--yes`, or if the operator declines.
pub