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>
impl<'i> ActionContext<'i>
Sourcepub fn new(
world_seed: [u8; 32],
instance_id: InstanceId,
tick: Tick,
principal: Principal,
caps: CapabilityMask,
) -> Self
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.
Sourcepub fn with_actor(self, actor: Option<ActorId>) -> Self
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.
Sourcepub fn acting_actor(&self) -> Option<ActorId>
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.
Sourcepub fn with_view(self, view: &'i InstanceView<'i>) -> Self
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).
Sourcepub fn with_idempotency_index(self, index: &'i dyn IdempotencyIndex) -> Self
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).
Sourcepub fn with_actor_handle_index(self, index: &'i dyn ActorHandleIndex) -> Self
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.
Sourcepub fn caps(&self) -> CapabilityMask
pub fn caps(&self) -> CapabilityMask
Caller capability mask (L2-granted authority set).
Sourcepub fn instance_id(&self) -> InstanceId
pub fn instance_id(&self) -> InstanceId
Instance identifier.
Sourcepub fn next_id(&mut self, type_code: u32) -> Result<EntityId, ActionError>
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.
Sourcepub fn emit_event<E>(&mut self, event: &E) -> Result<(), ActionError>where
E: Serialize + ArkheEvent,
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).
Sourcepub fn spawn_entity_for<C: ArkheComponent>(
&mut self,
) -> Result<EntityId, ActionError>
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.
Sourcepub fn set_component<C: ArkheComponent>(
&mut self,
entity: EntityId,
component: &C,
) -> Result<(), ActionError>
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).
Sourcepub fn remove_component<C: ArkheComponent>(
&mut self,
entity: EntityId,
prior_size: u64,
) -> Result<(), ActionError>
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.
Sourcepub fn drain_ops(&mut self) -> Vec<Op>
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.
Sourcepub fn ops(&self) -> &[Op]
pub fn ops(&self) -> &[Op]
Borrow the accumulated Op buffer without draining — for tests
and debug tooling.
Sourcepub fn idempotency_lookup(&self, key: &[u8; 16]) -> Option<(EntityId, Tick)>
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.
Sourcepub fn read<C: ArkheComponent>(
&self,
entity: EntityId,
) -> Result<Option<C>, ActionError>
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 toentityin the view.Err(ActionError::InvalidInput)— the view returned bytes but postcard decoding failed (version drift or corrupt state).
Sourcepub fn staged_read<C: ArkheComponent>(
&self,
entity: EntityId,
) -> Result<Option<C>, ActionError>
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.
Sourcepub fn actor_by_handle(
&self,
shell: ShellId,
handle: &BoundedString<32>,
) -> Option<ActorId>
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.
Sourcepub fn authenticated_actor_user(
&self,
actor: ActorId,
) -> Result<Option<UserId>, ActionError>
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 authenticatedUserBinding.Ok(None)— no view or no binding (anonymous / pre-bind).Err(...)— view bytes failed to decode (state corruption).
Sourcepub fn user_gdpr_status(
&self,
user: UserId,
) -> Result<Option<GdprStatus>, ActionError>
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.
Sourcepub fn ensure_actor_eligible(
&self,
actor: ActorId,
scheduled_tick: Tick,
) -> Result<(), ActionError>
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 (seecrate::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:dispatchinjects the caller identity the auth layer resolved (it is the single source of truth — there is no wire actor field to substitute), binds the kernelInstanceView, and runs this same check on that injected actor BEFOREsubmit, 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:GdprEraseUsertransitions the user’sUserGdprStatetoErasurePending(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.
Sourcepub fn preview_next_id_for<C: ArkheComponent>(
&self,
) -> Result<EntityId, ActionError>
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.
Sourcepub fn drain_events(&mut self) -> Vec<EventRecord>
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.
Sourcepub fn events(&self) -> &[EventRecord]
pub fn events(&self) -> &[EventRecord]
Borrow the accumulated event buffer without draining — for tests and debug tooling.