Skip to main content

AppState

Struct AppState 

Source
pub struct AppState {
    pub nats: Option<Client>,
    pub jetstream: Option<Context>,
    pub formations: Arc<RwLock<BTreeMap<Uuid, FormationRecord>>>,
    pub cells: Arc<RwLock<BTreeMap<String, CellRecord>>>,
    pub api_token: ApiToken,
    pub applied_cursor: Arc<AtomicU64>,
}

Fields§

§nats: Option<Client>

NATS connection used both by the WebSocket bridge and by future projection replay. Option because tests can drive the router without a live broker.

§jetstream: Option<Context>

JetStream context (ADR-0011, ADR-0015). Populated alongside nats once the server confirms the CELLOS_EVENTS stream exists. Option so router tests without a live broker still build the state cleanly; the WS bridge falls through to a “broker not configured” close if this is None.

§formations: Arc<RwLock<BTreeMap<Uuid, FormationRecord>>>

In-memory projection of formations. UUID-keyed for stable lookup.

§cells: Arc<RwLock<BTreeMap<String, CellRecord>>>

In-memory projection of cells.

§api_token: ApiToken

Bearer token required on every non-public route.

§applied_cursor: Arc<AtomicU64>

Highest JetStream stream-sequence the projection has applied (ADR-0015 §D2). The snapshot endpoint reports this as cursor so clients can open /ws/events?since=<cursor> and know they will not miss any event after the snapshot.

The WebSocket bridge bumps this monotonically as it forwards frames (see ws.rs). Until the bridge is migrated to a real JetStream consumer, this counter is per-process and resets on restart — that is acceptable for the MVP because every browser reconnect repeats the snapshot fetch.

Implementations§

Source§

impl AppState

Source

pub fn new(nats: Option<NatsClient>, api_token: impl Into<String>) -> Self

Constructor used by both main.rs and the test harness.

Source

pub fn with_jetstream(self, ctx: JsContext) -> Self

Attach a JetStream context. Called from main.rs after ensure_stream succeeds. Returns the same AppState for builder-style chaining in tests.

Source

pub fn cursor(&self) -> u64

Current snapshot cursor — the highest seq the server has applied.

Red-team wave 2 (LOW-W2A-1): Acquire is sufficient here. We need to see writes that happen-before any successful bump_cursor (i.e. the projection-apply write inside apply_event_payload); the previous SeqCst enforced a global total order with every other SeqCst operation in the process (counter fetch_adds in sni_proxy, etc.) for no extra correctness gain on this read.

Source

pub fn bump_cursor(&self, seq: u64)

Bump the snapshot cursor to at least seq. Monotonic; out-of-order values are ignored. Used by the WebSocket bridge as it forwards frames.

Red-team wave 2 (LOW-W2A-1): downgraded from SeqCst to AcqRel/Acquire. The CAS-loop structure (re-load on conflict, strict seq > current gate) guarantees monotonicity independent of the memory ordering chosen. AcqRel on success publishes the new cursor to every subsequent cursor() reader (snapshot GET handlers, primarily); Acquire on the reload picks up the observed value from the winner of the conflict.

Source

pub async fn apply_event_payload(&self, payload: &[u8]) -> Result<ApplyOutcome>

Apply a single CloudEvent payload (raw JSON bytes from JetStream) to the in-memory projection.

This is shared between the boot-time replay path (jetstream::replay_projection) and the live WebSocket bridge (ws.rs). Centralising the apply logic guarantees that what the server reconstructs on startup is exactly what it applies live — there is no “replay-only” code path that could drift from the live one.

CloudEvent type discriminates the transition. Two event families mutate state today:

  • formation.v1.* — server-emitted formation lifecycle, drives the formations projection (ADR-0010 admission gate output).
  • cell.lifecycle.v1.* and cell.command.v1.completed — supervisor-emitted cell lifecycle (ARCH-001). Without this, the server replays cell events from JetStream but never updates the cells map and GET /v1/cells returns []. The cell projector landed alongside the formation projector so cellctl get cells reflects what the supervisor actually ran.

Unknown event types still advance the JetStream cursor at the caller (the apply contract is independent of whether the projection mutated), so audit gaps surface as ApplyOutcome::Ignored in the replay log rather than silent drops.

Trait Implementations§

Source§

impl Clone for AppState

Source§

fn clone(&self) -> AppState

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,