Skip to main content

ActionContext

Struct ActionContext 

Source
pub struct ActionContext<'i> { /* private fields */ }
Expand description

Per-Action compute context.

The 'i lifetime is currently a phantom on the public API surface. The kernel keeps Effect<'i, _> private, so the forge-side 'i brand cannot be wired to a kernel-side brand cross-call; the position is preserved so a future L0 expansion can switch to a real-brand binding without breaking the forge signature.

Implementations§

Source§

impl<'i> ActionContext<'i>

Source

pub fn new( world_seed: [u8; 32], instance_id: InstanceId, tick: Tick, principal: Principal, caps: CapabilityMask, ) -> Self

Construct a fresh context. The world_seed is a non-exportable L0 configuration secret; tests may pass any fixed 32-byte value.

Source

pub fn with_actor(self, actor: Option<ActorId>) -> Self

Inject the authenticated acting actor — the single source of truth for the identity on whose behalf this Action runs. The runtime threads the caller’s authenticated ActorId here at the dispatch boundary (via the kernel submit actor channel + the crate::bridge); user-scoped compute bodies read it through ActionContext::acting_actor instead of trusting a wire-supplied field. None = system / unauthenticated.

Source

pub fn acting_actor(&self) -> Option<ActorId>

The authenticated acting actor injected at the dispatch boundary.

This is the identity the caller’s auth layer verified, threaded through the kernel submit actor channel — NOT a wire-controlled payload field. A user-scoped compute body uses this as both the eligibility subject (ActionContext::ensure_actor_eligible) and the recorded author/creator, so actor-substitution is structurally impossible. None = no authenticated identity (system / unauthenticated caller); a user-scoped action must reject in that case.

Source

pub fn with_view(self, view: &'i InstanceView<'i>) -> Self

Attach an L0 InstanceView snapshot — enables ActionContext::read lookups. Method-chain builder style so callers can write ActionContext::new(...).with_view(&view).

Source

pub fn with_idempotency_index(self, index: &'i dyn IdempotencyIndex) -> Self

Attach an IdempotencyIndex backend — enables ActionContext::idempotency_lookup to consult the L2 PG UNIQUE INDEX (or equivalent). Without this call the lookup always returns None (forward-compat stub).

Source

pub fn with_actor_handle_index(self, index: &'i dyn ActorHandleIndex) -> Self

Attach an ActorHandleIndex backend — enables ActionContext::actor_by_handle to enforce E-actor-3 uniqueness Without this call actor_by_handle always returns None, so handle-collision rejection only fires when an L2 implementation is bound.

Source

pub fn tick(&self) -> Tick

Current tick.

Source

pub fn principal(&self) -> &Principal

Caller principal (authorization subject).

Source

pub fn caps(&self) -> CapabilityMask

Caller capability mask (L2-granted authority set).

Source

pub fn instance_id(&self) -> InstanceId

Instance identifier.

Source

pub fn next_id(&mut self, type_code: u32) -> Result<EntityId, ActionError>

Derive the next EntityId for a given type_code. Increments the internal per-context sequence so repeated calls within the same tick produce distinct entities.

Source

pub fn emit_event<E>(&mut self, event: &E) -> Result<(), ActionError>
where E: Serialize + ArkheEvent,

Emit an event. Dual-path — the payload is appended to the EventRecord buffer (drained by drain_events, the L1 pipeline::process_action surface) and pushed onto the L0 Op::EmitEvent accumulator (drained via ActionContext::drain_ops by the L2 service layer’s bridge into the kernel).

Source

pub fn spawn_entity_for<C: ArkheComponent>( &mut self, ) -> Result<EntityId, ActionError>

Spawn a fresh entity in the namespace of C. Allocates an EntityId via ActionContext::next_id using C::TYPE_CODE as the id-derivation namespace, and pushes a matching Op::SpawnEntity into the accumulator.

The component itself is not attached here — follow with ActionContext::set_component to attach the payload.

Source

pub fn set_component<C: ArkheComponent>( &mut self, entity: EntityId, component: &C, ) -> Result<(), ActionError>

Attach (or replace) component component on entity. Serializes the payload via postcard and pushes a Op::SetComponent. The runtime ledger uses the encoded size for quota accounting (L0 memory budget enforcement).

Source

pub fn remove_component<C: ArkheComponent>( &mut self, entity: EntityId, prior_size: u64, ) -> Result<(), ActionError>

Detach component C from entity. prior_size must match the size the matching SetComponent reported — the ledger uses it to balance the memory budget (L0 quota discipline). The kernel does not currently expose a back-channel for the runtime to look up the prior size; callers pass it explicitly so the size delta in the emitted Op::RemoveComponent is balanced against the preceding Op::SetComponent.

Source

pub fn drain_ops(&mut self) -> Vec<Op>

Drain accumulated L0 Ops. The L2 service layer calls this after compute() returns, wraps each Op in Effect<'i, Unverified>, and submits through the authorize → dispatch path.

Source

pub fn ops(&self) -> &[Op]

Borrow the accumulated Op buffer without draining — for tests and debug tooling.

Source

pub fn idempotency_lookup(&self, key: &[u8; 16]) -> Option<(EntityId, Tick)>

Idempotency-key lookup. Consults the attached IdempotencyIndex (see ActionContext::with_idempotency_index) if one is bound; otherwise returns None for forward-compat.

The production path uses PG UNIQUE INDEX; the L0 kernel may layer a WAL-scan fallback beneath the same interface.

Source

pub fn read<C: ArkheComponent>( &self, entity: EntityId, ) -> Result<Option<C>, ActionError>

Read a component from the attached InstanceView (NC2 contract).

Returns:

  • Ok(Some(component)) — the view is bound and the component was decoded successfully.
  • Ok(None) — no view bound, or the component is not attached to entity in the view.
  • Err(ActionError::InvalidInput) — the view returned bytes but postcard decoding failed (version drift or corrupt state).
Source

pub fn staged_read<C: ArkheComponent>( &self, entity: EntityId, ) -> Result<Option<C>, ActionError>

Read a component, taking into account Op mutations staged so far in the current compute() call. This is the staging-aware sibling of ActionContext::read — same return shape, but a same-tick set_component (or remove_component) earlier in the compute body shadows the view’s bytes.

The Op accumulator is scanned in reverse, so the most-recent mutation wins. A RemoveComponent makes the staged read return Ok(None) even if the view still holds the prior value.

Used by E-act-7 reassign rejection: a compute that issues a set_component::<EntityShellId> for an entity already shadowed by a staged op must still see the staged shell, not the stale view.

Source

pub fn actor_by_handle( &self, shell: ShellId, handle: &BoundedString<32>, ) -> Option<ActorId>

Resolve (ShellId, BoundedString<32>) → ActorId via the bound ActorHandleIndex. Returns None if no index is attached or the handle is unused.

The compute path uses this to enforce E-actor-3 uniqueness before spawning a fresh ActorProfile.

Source

pub fn authenticated_actor_user( &self, actor: ActorId, ) -> Result<Option<UserId>, ActionError>

Resolve the backing UserId for an authenticated ActorId via the staged-aware UserBinding read. Returns:

  • Ok(Some(user_id)) — actor has an authenticated UserBinding.
  • Ok(None) — no view or no binding (anonymous / pre-bind).
  • Err(...) — view bytes failed to decode (state corruption).
Source

pub fn user_gdpr_status( &self, user: UserId, ) -> Result<Option<GdprStatus>, ActionError>

Resolve the GDPR status of user via the staged-aware UserGdprState read. Returns Ok(None) when no lifecycle pointer is reachable; the compute caller decides whether that is a soft pass (Tier-0 / pre-Bootstrap dev, or a user registered before the pointer existed — treated as Active) or a hard reject.

Source

pub fn ensure_actor_eligible( &self, actor: ActorId, scheduled_tick: Tick, ) -> Result<(), ActionError>

E-user-3 C3 helper — fail an actor-originated compute when the actor’s backing user is in an erasure-lifecycle state (GdprStatus::ErasurePending or the terminal Erased). Ok(None) from the underlying reads (no view bound, anonymous actor, etc.) is treated as a soft pass.

The soft pass means this check is only effective when a view is bound. Two paths bind one:

  • Direct path — callers that build the context via ActionContext::new(...).with_view(&view) enforce the gate in-compute.
  • Dispatch path — actions driven through RuntimeService::dispatch (forge-platform) run with a viewless bridge context (see crate::bridge), so this in-compute call soft-passes. For that path the gate is enforced at the L2 admission boundary on the authenticated acting actor: dispatch injects the caller identity the auth layer resolved (it is the single source of truth — there is no wire actor field to substitute), binds the kernel InstanceView, and runs this same check on that injected actor BEFORE submit, rejecting an erasure-pending action before it reaches the WAL. The actor this gate evaluates is therefore the authenticated caller, never a trusted-by-default wire field — the gate is sound. It is also live: GdprEraseUser transitions the user’s UserGdprState to ErasurePending (a blind write, valid on the viewless path), so from the instant erasure is requested this gate rejects the user’s subsequent actor-originated actions.
Source

pub fn preview_next_id_for<C: ArkheComponent>( &self, ) -> Result<EntityId, ActionError>

Preview the EntityId that the next ActionContext::next_id / ActionContext::spawn_entity_for call would produce, without bumping the internal sequence counter.

Useful for pre-spawn validation (e.g. E-act-5 Activity self-loop rejection): compute the predicted id, compare against the target, then commit the actual spawn.

Source

pub fn drain_events(&mut self) -> Vec<EventRecord>

Drain accumulated events. Called by the pipeline after compute() returns so the WAL append path can stream them in sequence order.

Source

pub fn events(&self) -> &[EventRecord]

Borrow the accumulated event buffer without draining — for tests and debug tooling.

Auto Trait Implementations§

§

impl<'i> Freeze for ActionContext<'i>

§

impl<'i> !RefUnwindSafe for ActionContext<'i>

§

impl<'i> Send for ActionContext<'i>

§

impl<'i> Sync for ActionContext<'i>

§

impl<'i> Unpin for ActionContext<'i>

§

impl<'i> UnsafeUnpin for ActionContext<'i>

§

impl<'i> !UnwindSafe for ActionContext<'i>

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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