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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! Whether a contract refusal is NEW INFORMATION, and therefore worth saying.
//!
//! A refused worker redials forever. Task #94 measured the liminal transport
//! re-logging the identical full diagnosis on every dial — 2/second, 12 MB/hour,
//! *"observability buried"*. Naming the refusal on the gRPC path too (#147 R1)
//! would have given that defect a second host, so the naming and the decision
//! to repeat it land together.
//!
//! **The rule is a transition, not a cap.** The full diagnosis is emitted when
//! the refusal for a [`RefusalSite`] is new or different; an identical
//! repeat carries nothing the first line did not, so it is silence. There is no
//! interval, no count, and no configured value anywhere in this module —
//! suppressing a message that says exactly what the last one said is
//! correctness, not rate limiting.
//!
//! # Why the key is what it is
//!
//! The obvious key is `(identity, task_queue, node)` — and **`identity` is a
//! client-supplied string**. A refused worker that varies its identity per dial
//! would grow this map forever, and since a refused connection never enters the
//! registry, nothing else would ever evict it. "Bounded by construction" would
//! have been false while the code looked entirely right.
//!
//! ⇒ **A map keyed on anything the refused party controls cannot be bounded by
//! construction.** The client's part rides in the VALUE, where it can inform but
//! never allocate.
//!
//! ## The correction of 2026-08-06, and why it was needed
//!
//! The first cut of this module keyed on `(task_queue, node)` and its own doc
//! called both *"server-derived"*. **They are not.** Both arrive on the wire in
//! the worker's registration request, on both transports
//! (`worker_grpc.rs:652-653`, `liminal_transport.rs:1571-1572`). Moving
//! `identity` out of the key while leaving `node` in it defeated exactly half of
//! the trap the design had named, and the stated bound was false while — again —
//! the code looked entirely right.
//!
//! `task_queue` turned out to be bounded anyway, but by a mechanism nobody had
//! written down: a queue with no reachable deployed contract demands nothing, so
//! it ADMITS ([`aion::Engine::worker_contracts_for_admission`] returns an empty
//! admission) and never reaches this map. `node` had no such gate at all —
//! `dispatch_can_reach` demands every UNPINNED action of every node, so a
//! refused worker varying its advertised node grew the map without limit.
//!
//! ⇒ The site is now [`RefusalSite`], which the gate constructs from the
//! CATALOG rather than from the request. A node names a site only when some
//! demanded action is pinned to it; otherwise the refusal is node-independent —
//! every node owes the same actions and fails identically — and it is one site.
//! The bound is the catalog's: queues × (their pinned nodes + 1), plus one for
//! an unreadable catalog. **Nothing a refused party can say allocates an entry.**
//!
//! That collapse is not merely a bound; it is more truthful. Two connections
//! owing an identical action set and failing identically are the same fault, and
//! reporting them separately was #94 volume for no information.
//!
//! One consequence is accepted rather than capped: two DIFFERENTLY broken
//! workers alternating on one queue flip the value on every dial, so both are
//! named every time. In that shape the refusal reason genuinely changes each
//! dial, so every message carries information the previous one did not. Any
//! mechanism quiet enough to suppress it would suppress new information by
//! design, which is the defect this whole change exists to kill.
use HashMap;
use Mutex;
/// Where a refusal happened, in terms **the server controls**.
///
/// This is a type rather than a tuple so that a caller cannot pass the node it
/// read off the wire by accident: the distinguishing node is derived from the
/// deployed catalog by [`crate::worker::contracts::refusal_site`], and a raw
/// `Option<&str>` from a registration request looks exactly like a correct
/// argument at the call site. The first cut of this module had that bug.
/// The refusal a site is currently sitting in.
///
/// The whole refusal is kept rather than a hash of it because two callers need
/// it: the transition rule needs to know whether this refusal differs from the
/// last, and the start-time availability hint needs to be able to SAY what the
/// refusal was. A digest can answer the first question and nothing else, and a
/// hint that can only say "something was refused" is the guess-in-the-grammar-
/// of-a-diagnosis this change exists to delete.
/// Remembers the last contract refusal per [`RefusalSite`], so an unchanged one
/// can be met with silence.
///
/// Shared by every registration transport through the connected-worker
/// registry: the registry knows who was admitted, this knows who was turned
/// away, and both callers of the admission gate already hold it. One shared
/// record is what stops the two transports drifting apart again — the whole
/// finding behind #147 was one rule maintained in two places.