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
//! Reporting a watch loop's *failed* attempts to reach its store.
//!
//! A watch loop is the half of a store `dynamic-config` cannot see.
//! [`RemoteSink::apply`] records a delivery, so a working watch keeps
//! [`RemoteStatus`] current — but a loop whose stream broke, whose blocking
//! query is erroring or whose credential was refused delivers nothing, and
//! without this says nothing: `dynamic_config_remote_up` would report the
//! last *delivery* rather than the last *attempt*, and a store that stopped
//! answering an hour ago would look healthy until something called
//! `refresh_remote`.
//!
//! # Why a type rather than an `Option<RemoteSink>` in seven crates
//!
//! Because the seven watch loops do not agree on anything else. Their
//! signatures already differ — blocking against async, a `Watching` token or
//! a cancelled future — so a second `watch_reporting_to` method in each crate
//! would be seven new methods with seven doc comments saying the same thing.
//! What they *do* agree on is that a failure site is one line, and that the
//! line must be impossible to get wrong: [`Attempts::failed`] is infallible,
//! is a no-op when nobody asked for reporting, and cannot be given anything
//! but an error.
//!
//! # Which attempts report, in all seven crates
//!
//! Three rules, and each store crate's documentation carries the table its
//! own loop makes of them:
//!
//! 1. **A failure the loop survives by retrying reports.** The stream is
//! down, the last delivery is old, and nothing else would say so.
//! 2. **A recovery that worked stays silent.** Only a delivery or a fetch
//! clears the streak, so reporting a token that turned over on a healthy
//! cluster would drive `remote_up` to zero and leave it there.
//! 3. **A refusal that never asked the store reports nowhere.** No format, a
//! key shape that cannot be watched, TLS material that will not build a
//! client: [`RemoteStatus::reachable`] is *whether the store answered the
//! last time it was asked*, and none of those ask. They are returned to
//! the caller, who is the one holding the mistake.
//!
//! Rule 3 is the one 0.6.1's audit settled. Two crates reported such a
//! refusal and two did not, each with a test asserting its half; what decided
//! it is that a status carries a kind and a path and **no message**, so a
//! `remote_up = 0` for a source typo is an alert about the store that nothing
//! downstream can correct.
//!
//! # What it deliberately does not do
//!
//! It does not touch the document, the fetch count or the clock. A failed
//! attempt moves the failure streak and the last failure and nothing else,
//! so `dynamic_config_remote_last_fetch_seconds` keeps *ageing* while
//! `dynamic_config_remote_up` goes to zero — which is the pair an alert
//! wants. A failure that reset the staleness clock would hide the half of
//! the story that says how long the served document has been stale.
//!
//! [`RemoteSink::apply`]: dynamic_config::RemoteSink::apply
//! [`RemoteStatus`]: dynamic_config::RemoteStatus
//! [`RemoteStatus::reachable`]: dynamic_config::RemoteStatus::reachable
use ;
/// Where a watch loop reports an attempt that came back with nothing.
///
/// Default is *nobody asked*, which is what a source built without
/// `reporting_to` carries and what makes [`failed`](Self::failed) free.
;