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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! R-D1 stage-8 capacity pass for the credential-attach arm (split from
//! [`super::ops_attach`] under the 500-code-line lens).
//!
//! # Lane p0-39: this arm has no refusal left
//!
//! Every scope this pass once walked has stopped refusing. The three shared
//! pools are TTL-bounded with a reporting tripwire; the two per-participant
//! scopes are retention windows that displace their own oldest member rather
//! than turn an arrival away. What the pass still does is real work: it
//! observes the shared pools, plans the displacement the commit will apply,
//! and reserves the ledger entries atomically with that plan.
use liminal_protocol::lifecycle::{
CredentialAttachCapacityCounters, ReceiptDeadlines, select_credential_attach_capacity,
};
use liminal_protocol::wire::CredentialAttachRequest;
use crate::metrics::ReceiptWindowScope;
use super::barrier::OperationFacts;
use super::capacity::{
CapacityReservation, OccupancyEntry, ReservationEffects, ResourceKind, ServerCapacity,
Stage8Choice, Stage8Outcome,
};
use super::occupancy::ProvenanceMember;
use super::state::{ConversationAuthority, Slot, StateError};
impl ConversationAuthority {
/// Runs the stage-8 receipt/provenance window pass for one authorized
/// credential attach.
///
/// # Errors
///
/// Propagates the ledger's own u64-domain and configuration invariants.
pub(super) fn attach_stage8<'cap>(
&self,
request: &CredentialAttachRequest,
slot: &Slot,
operation_facts: &OperationFacts,
server_capacity: &'cap ServerCapacity,
deadlines: &ReceiptDeadlines,
) -> Result<AttachStage8<'cap>, StateError> {
let now = u128::from(operation_facts.now_ms);
let limits = operation_facts.receipt_limits;
// Shared pools: observed and reported, never consulted for admission.
self.observe_conversation_provenance_pool(now)?;
let token = request.attach_attempt_token.into_bytes();
let mut inserts = vec![OccupancyEntry {
expires_at: deadlines.receipt_expires_at(),
conversation_id: self.conversation_id,
participant_id: request.participant_id,
kind: ResourceKind::AttachReceipt,
token,
}];
// Which fingerprint this commit retains is board #37's question, and
// `Slot::incoming_provenance_member` is its single answer — see there.
//
// ONE displacement plan, applied to the ledger here and to the slot in
// `install_attach_receipt`. Two sites deriving "the oldest" separately
// is exactly how a live sequence and its cold replay drift apart, so
// they call the same two functions over the same pre-commit slot.
//
// Every key below is byte-identical to the one `capacity_contribution`
// re-derives from the post-commit slot, so a later replay fold is
// idempotent and cannot double count.
let incoming = slot.incoming_provenance_member();
let displaced = incoming.map_or_else(Vec::new, |incoming| {
slot.plan_provenance_displacement(incoming, limits.provenance_participant_window)
});
let enrollment_token = self.enrollment_token_bytes(request.participant_id)?;
if let Some(incoming) = incoming
&& !displaced.contains(&incoming)
{
inserts.push(self.provenance_entry(request.participant_id, incoming, enrollment_token));
}
let displaced_entries: Vec<OccupancyEntry> = displaced
.iter()
.filter(|member| Some(**member) != incoming)
.map(|member| self.provenance_entry(request.participant_id, *member, enrollment_token))
.collect();
let effects = ReservationEffects {
conversation_id: self.conversation_id,
identity_reserved: false,
inserts,
};
// Receipts this commit will retire early, applied only at confirm.
//
// This list is also what makes the LiveReceiptParticipant window's
// displacement a no-op in practice: every live receipt that window
// counts for this participant is retired by this very commit, so
// post-commit occupancy is exactly one whatever the window size is.
// That is the wedge the old wall created — an attach refused by the
// receipt it was about to end.
let retire = self.retired_receipts(request.participant_id, slot, enrollment_token);
let counters = attach_window_counters(
limits.live_receipt_participant_window,
slot.live_receipt_occupancy(now),
limits.provenance_participant_window,
slot.provenance_occupancy(now)?,
)?;
let tripwires = limits.shared_pool_tripwires;
let outcome = server_capacity.admit(now, tripwires, effects, |_server| {
// The crate selector is TOTAL: it produces a commit for every
// input, so this arm has no `Refuse` branch to build.
Ok(Stage8Choice::Admit(select_credential_attach_capacity(
counters,
)))
})?;
Ok(match outcome {
Stage8Outcome::Refused(response) => AttachStage8::Refused(response),
Stage8Outcome::Reserved(reservation, commit) => {
self.record_displacements(
request.participant_id,
commit.live_receipt_participant().displaced(),
displaced.len(),
limits.provenance_participant_window,
)?;
AttachStage8::Reserved {
reservation,
retire,
displace: displaced_entries,
}
}
})
}
/// The receipt entries this commit ends early: the superseded attach
/// receipt and, on the first rotation, the ended enrollment receipt.
///
/// This list is also why the `LiveReceiptParticipant` window needs no
/// eviction of its own: every live receipt that window counts for this
/// participant is retired here, so post-commit occupancy is exactly one
/// whatever the window size is. The old wall's wedge was precisely this —
/// an attach refused by the receipt it was about to end.
fn retired_receipts(
&self,
participant_id: u64,
slot: &Slot,
enrollment_token: [u8; 16],
) -> Vec<OccupancyEntry> {
let mut retire = Vec::new();
if let Some(previous) = slot.attach.as_ref() {
retire.push(OccupancyEntry {
expires_at: previous.receipt_expires_at,
conversation_id: self.conversation_id,
participant_id,
kind: ResourceKind::AttachReceipt,
token: previous.token.into_bytes(),
});
}
if slot.enrollment_receipt_ended.is_none() {
retire.push(OccupancyEntry {
expires_at: slot.enrollment_receipt_expires_at,
conversation_id: self.conversation_id,
participant_id,
kind: ResourceKind::EnrollmentReceipt,
token: enrollment_token,
});
}
retire
}
/// Counts and logs the displacements this commit performs.
///
/// Displacement is silent to the arriving client by design, so this is the
/// only record that a bound did work — "silent to experience, loud to
/// record". A bound that neither refuses nor discloses would hide exactly
/// what the old wall at least made loud.
///
/// # Errors
///
/// Returns a [`StateError`] invariant if the displaced count leaves the
/// u64 domain.
fn record_displacements(
&self,
participant_id: u64,
live_receipt_displaced: bool,
provenance_displaced: usize,
window: u64,
) -> Result<(), StateError> {
if live_receipt_displaced {
crate::metrics::receipt_entries_displaced(
ReceiptWindowScope::LiveReceiptParticipant,
1,
);
}
let displaced = u64::try_from(provenance_displaced).map_err(|_| {
StateError::invariant("displaced provenance count exceeds the u64 domain")
})?;
if displaced > 0 {
crate::metrics::receipt_entries_displaced(
ReceiptWindowScope::ProvenanceParticipant,
displaced,
);
tracing::debug!(
conversation_id = self.conversation_id,
participant_id,
displaced,
window,
"participant provenance window displaced its oldest fingerprints so a newer one \
of the same participant could land"
);
}
Ok(())
}
/// Builds the ledger entry one provenance-window member occupies.
const fn provenance_entry(
&self,
participant_id: u64,
member: ProvenanceMember,
enrollment_token: [u8; 16],
) -> OccupancyEntry {
match member {
ProvenanceMember::Enrollment { expires_at } => OccupancyEntry {
expires_at,
conversation_id: self.conversation_id,
participant_id,
kind: ResourceKind::EnrollmentProvenance,
token: enrollment_token,
},
ProvenanceMember::Attach { expires_at, token } => OccupancyEntry {
expires_at,
conversation_id: self.conversation_id,
participant_id,
kind: ResourceKind::AttachProvenance,
token,
},
}
}
}
/// Builds credential attach's two per-participant window counters.
///
/// A window whose configured size was lowered beneath retained occupancy is
/// NOT an error here, and that is the whole point of the change: the counter is
/// clamped to its full state, the selector displaces, and the commit's own plan
/// sheds however many members it takes to reach the new size. A lowered number
/// can no longer wedge a boot.
fn attach_window_counters(
live_receipt_window: u64,
live_receipt_occupied: u64,
provenance_window: u64,
provenance_occupied: u64,
) -> Result<CredentialAttachCapacityCounters, StateError> {
Ok(CredentialAttachCapacityCounters::new(
window_counter(live_receipt_window, live_receipt_occupied)?,
window_counter(provenance_window, provenance_occupied)?,
))
}
/// One window counter, clamping over-window occupancy to exactly full.
fn window_counter(
window: u64,
occupied: u64,
) -> Result<liminal_protocol::lifecycle::CapacityCounter, StateError> {
liminal_protocol::lifecycle::CapacityCounter::try_new(window, occupied.min(window)).map_err(
|error| {
StateError::invariant(format!(
"validated per-participant window rejected: {error:?}"
))
},
)
}
/// Arm-level result of the attach stage-8 pass.
pub(super) enum AttachStage8<'cap> {
/// A refusal produced by the ledger's own invariants. The receipt scopes
/// contribute no refusal of their own; this arm exists because
/// [`Stage8Outcome`] is shared with enrollment, which still refuses on
/// identity capacity.
Refused(liminal_protocol::wire::CredentialAttachResponse),
/// Reserved; confirmed with `retire` and `displace` after the durable
/// append.
Reserved {
/// Stage-8 reservation guard (rolls back unless confirmed).
reservation: CapacityReservation<'cap>,
/// Receipt entries the commit ends early.
retire: Vec<OccupancyEntry>,
/// Provenance entries the participant's own window displaced.
displace: Vec<OccupancyEntry>,
},
}