pub struct ObservationCell { /* private fields */ }Expand description
Per-key continuity state machine (plan §3.4 transitions). Owns
the observation, both deadlines, and the projection; callers feed
it only gate-admitted beats (super::IncarnationSeqGate)
and drive time explicitly through expire_if_due — no hidden
clock reads, so every SI-0 test controls the timeline.
Implementations§
Source§impl ObservationCell
impl ObservationCell
Sourcepub fn register(now: Instant, own_interval: Duration, factor: u32) -> Self
pub fn register(now: Instant, own_interval: Duration, factor: u32) -> Self
Interest registration (plan §3.4): continuity starts
Unestablished and the establishment deadline starts counting
— before any beat arrives there is no promised_cadence, so
the window derives from the consumer’s own D alone.
Sourcepub fn update_interval(&mut self, own_interval: Duration)
pub fn update_interval(&mut self, own_interval: Duration)
SI-4 review (interval update): the consumer’s — or the relay’s aggregate — own D moved. Recompute the CURRENT deadline for the new window WITHOUT resetting continuity and without waiting for a fresh beat: the deadline’s anchor (the last qualifying event) is preserved by shifting the deadline by the window delta, so tightening pulls the deadline earlier and loosening pushes it later, exactly. A no-op when the interval is unchanged; skipped once Expired (the deadline is meaningless there — the next beat rebuilds state as usual).
Sourcepub fn on_admitted_beat(&mut self, now: Instant, beat: DeliveredBeat)
pub fn on_admitted_beat(&mut self, now: Instant, beat: DeliveredBeat)
Apply one gate-admitted beat.
- A continuity-bearing beat establishes (from
Unestablished, Established, or Expired — a resumed live
stream is how continuity recovers) and re-arms the
suspicion deadline from
max(promised_cadence, own D). - A warm-start beat updates the attested content but NEVER touches continuity or either deadline: a chain of strictly-newer cached beats is not a live stream, so it must neither establish nor postpone expiry (SI-0 tests 13/14).
- Continuity never carries across an incarnation boundary: a warm-started beat from a NEW incarnation expires ESTABLISHED continuity (§4.7) — the earned optimism the old stream can no longer vouch for — until the new stream establishes on its own live beat. An Unestablished cell has no earned optimism to revoke: a cached Ready still projects Unknown and a cached NotReady still projects NotReady (pessimism is safe), both bounded by the unchanged establishment deadline.
- Continuity never carries across a GENERATION boundary either (v4.1, §3.4) — but a generation change is a redefinition, not a failure signal: the cell resets to a fresh observation (Unestablished, establishment deadline restarted), so a warm-started NotReady under the new generation still projects (pessimism is safe) while a warm-started Ready must earn continuity anew.
Sourcepub fn expire_if_due(&mut self, now: Instant)
pub fn expire_if_due(&mut self, now: Instant)
Drive the clock: past-deadline Unestablished OR Established states expire (plan §3.4 — “Unestablished expires too”).
Sourcepub fn disrupt(&mut self, reason: DisruptReason)
pub fn disrupt(&mut self, reason: DisruptReason)
Force-expire (plan §4.7): route withdrawal, path failure, generation change, scope-validation failure.
Sourcepub fn projected(&self) -> ProjectedReadiness
pub fn projected(&self) -> ProjectedReadiness
Current projection (no observation yet → Unknown).
Sourcepub const fn continuity(&self) -> Continuity
pub const fn continuity(&self) -> Continuity
Current continuity state.
Sourcepub fn observation(&self) -> Option<&ReadinessObservation>
pub fn observation(&self) -> Option<&ReadinessObservation>
The latest admitted observation, if any.
Sourcepub const fn last_disrupt(&self) -> Option<DisruptReason>
pub const fn last_disrupt(&self) -> Option<DisruptReason>
The reason for the last forced expiry, if continuity was disrupted rather than timed out.