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
124
125
126
127
128
129
130
131
132
//! Operator-assigned trust for `workload-host` targets (ADR-099 section 3).
//!
//! The operator's peer list is authoritative on every sync: a listed peer
//! gets the listed tier (raised or lowered), and a known remote target
//! that is no longer listed is demoted to `discovered`, which governance
//! never places on. Demoted targets stay known so their placed instances
//! can still be stopped and unloaded. Addresses in `keep` (this node's own
//! in-process host, Seeds) are not touched.
//!
//! Trust is bound to a node key, not to an address. A listed peer with a
//! `key` gets its tier only for that key: a known target at the address
//! with another key is demoted, and the pinned key is learned by a
//! `describe` that refuses any other signer. A listed peer without a key
//! gives its tier to the key first seen at the address; a different key
//! answering there later is never learned under it (`refresh` requires
//! the stored key).
use clawft_types::placement::TrustTier as FactsTier;
use super::plane::{PlacementControlPlane, PlaneError, TargetInfo};
use crate::workload_pkg::codec::hex_decode_exact;
/// One operator-listed `workload-host`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OperatorPeer {
/// Where it listens.
pub addr: String,
/// Operator-assigned tier.
pub tier: FactsTier,
/// The node key the tier is for; `None` trusts the key first seen at
/// `addr`.
pub key: Option<[u8; 32]>,
}
impl OperatorPeer {
/// A peer trusted on first use.
pub fn new(addr: impl Into<String>, tier: FactsTier) -> Self {
Self {
addr: addr.into(),
tier,
key: None,
}
}
/// Only the node with `key` gets the tier.
pub fn with_key(mut self, key: [u8; 32]) -> Self {
self.key = Some(key);
self
}
/// Whether this entry covers the known target `t` (`known` is every
/// known target): a pinned entry covers its key at its address; an
/// unpinned one only the first key learned at its address.
fn covers(&self, t: &TargetInfo, known: &[TargetInfo]) -> bool {
if self.addr != t.addr {
return false;
}
match self.key {
Some(k) => hex_decode_exact::<32>(&t.public_key) == Some(k),
None => known
.iter()
.filter(|o| o.addr == self.addr)
.min_by_key(|o| (o.learned_ms, o.node_id.clone()))
.is_some_and(|first| first.node_id == t.node_id),
}
}
}
impl PlacementControlPlane {
/// Set the operator-assigned tier of a known target (targets map and
/// facts cache). False if the node is not known.
pub fn set_tier(&self, node_id: &str, tier: FactsTier) -> bool {
let known = self
.targets
.write()
.ok()
.and_then(|mut t| t.get_mut(node_id).map(|e| e.tier = tier))
.is_some();
self.facts.set_trust_tier(node_id, tier);
// A Seed is addressed in-process: governance reads the tier held
// beside its adapter.
let seed = self
.seeds
.write()
.ok()
.and_then(|mut s| {
s.get_mut(node_id).map(|e| {
e.1 = tier;
// The host's own gate calls read this live.
e.0.set_node_tier(super::facts::governance_tier(tier));
})
})
.is_some();
self.persist();
known || seed
}
/// Make the known targets match the operator's `peers`. Returns one
/// error per peer that could not be learned (it is retried on the next
/// sync).
pub async fn apply_operator_peers(
&self,
peers: &[OperatorPeer],
keep: &[&str],
) -> Vec<(String, PlaneError)> {
let known = self.targets();
for t in &known {
let want = match peers.iter().find(|p| p.covers(t, &known)) {
Some(p) => p.tier,
None if keep.contains(&t.addr.as_str()) => continue,
None => FactsTier::Discovered,
};
if t.tier != want {
tracing::info!(node = %t.node_id, addr = %t.addr, from = ?t.tier, to = ?want,
"workload peer tier changed by operator policy");
self.set_tier(&t.node_id, want);
}
}
let mut failed = Vec::new();
for p in peers {
// Satisfied by the first key learned at the address (unpinned)
// or by the pinned key; otherwise learn it (a pinned key only).
if known.iter().any(|t| p.covers(t, &known)) {
continue;
}
if let Err(e) = self.add_target_expecting(&p.addr, p.tier, p.key).await {
failed.push((p.addr.clone(), e));
}
}
failed
}
}