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 operator_id: Arc<OperatorId>,
    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.

§operator_id: Arc<OperatorId>

Operator identity attributed to every signed CloudEvent this server emits (ADR-0019 §Decision, §Implementation roadmap step 2).

Resolved from CELLOS_SERVER_OPERATOR_ID at startup; falls back to DEFAULT_OPERATOR_ID. Wrapped in Arc so the Clone-able AppState does not pay a string allocation on every handler call. cellos-core exposes OperatorId as a Clone + Hash newtype, but we still want to share one heap allocation across the many Principal::Operator { id: state.operator_id.as_ref().clone() } construction sites; Arc<OperatorId> is the cheapest honest shape.

§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.

The operator identity defaults to DEFAULT_OPERATOR_ID; callers that need a different identity (production main, tests covering principal attribution) chain AppState::with_operator. Keeping the constructor signature stable preserves every existing test that builds AppState::new(None, "test") — the principal migration (ADR-0019 step 2) ships without churning unrelated test setups.

Source

pub fn with_operator(self, operator_id: impl Into<String>) -> Self

Override the operator identity used to attribute admission-emitted CloudEvents. ADR-0019 §Implementation roadmap step 2: every signed event MUST declare its principal explicitly; this is where the server-side Principal::Operator is anchored.

Source

pub fn principal(&self) -> Principal

Build the Principal every server-emitted signed CloudEvent attributes to. Pre-ADR-0019-step-3 we only emit Principal::Operator; the structured variant is preserved so a later wave can swap in Principal::Delegate for MCP-bridge admission without churning every emission site again.

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<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

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