Skip to main content

Loader

Struct Loader 

Source
pub struct Loader;
Expand description

Declarative loader — diffs EntryTrees incrementally.

Confluence (Thm 73) correctness condition: regardless of entry application order, the quiescent context must equal static assembly of the final EntryTree. reconcile is the field-level diff that callers use to drive Fiber::refresh / Fiber::reload without manual wiring.

Persisted to ENTRIES_PATH (config/entries.json) or CORDIS_ENTRIES_TOON_PATH (config/cordis-entries.toon via toon-format 0.4.1 when toon feature is enabled). Never writes ares.toml.

Implementations§

Source§

impl Loader

Source

pub async fn move_entry( ctx: &Arc<Context>, current: &mut EntryTree, journal: &LoaderJournal, id: &str, target: Option<&str>, position: usize, ) -> Result<MoveOutcome, CordisError>

Relocate the subtree rooted at id under target (None = root) at position, then make the LIVE kernel agree with the moved tree.

Validation and the rename cascade are EntryTree::move_entry (pure, error → tree untouched). Fiber handling then goes through the contexts-equivalence gate:

  • Equivalent composition (same multiset of plugin/config/disabled/ isolate across both trees — what every pure structural move is): NOOP. Every journaled record is re-keyed old → new with its fiber id PRESERVED (the existing registration fiber handle is refreshed in place — epoch label + ledger annotation — never disposed or re-created), so consumers keep resolving the same live instances.
  • Different composition (mixed edits rode along): fall back to the standard staged Self::apply reconcile, which restarts renamed entries through Retire + Begin.

The shared CurrentEntries view (when provided) is synced to the post-move tree either way, so a follow-up disk reload diffs cleanly instead of seeing phantom Retire/Begin pairs for the renames.

Source§

impl Loader

Source

pub async fn apply( ctx: &Arc<Context>, current: &mut EntryTree, desired: &EntryTree, journal: &LoaderJournal, ) -> Vec<AppliedAction>

Reconcile current toward desired, executing every action for real.

Unlike Loader::execute_action (kept for compatibility), this orchestrator resolves entry payloads from desired so Begin and RebuildFiber instantiate with the entry’s actual config (fixing the log-only/Value::Null behavior), and Retire disposes the live fiber recorded in journal.

Two-phase STAGED apply: phase one constructs and verifies every replacement candidate without mutating any live entry (config pre-flight trials, entry resolution); phase two applies the verified candidates in dependency order. On the first failing verification the batch aborts BEFORE any mutation — nothing has been touched, so no rollback is needed. On a failure DURING phase two, every already-applied change is reverted (config restored, rebuilt fibers disposed) so the live tree serves the originals; the failing step’s AppliedAction reports Err naming it.

Failure policy: on any failure current is left unchanged so a retry re-diffs cleanly. Returns per-action outcomes.

Config-only patches on Active fibers go through the existing update path (Self::trial_config_verified pre-flight + Fiber::update) instead of stop+start — the factory runs only inside the scratch trial, so apply counts stay flat across pure config changes.

Source§

impl Loader

Source

pub fn detect_cycles(ctx: &Arc<Context>) -> Vec<Vec<u64>>

Run dependency-cycle detection over every entry this loader has instantiated.

The post-apply inject graph is reconstructed by [crate::cycles::build_dependency_graph] from the lazily-provided [crate::cycles::CycleLedger] plus registry lookups; returns one path per detected cycle (closed, canonical rotation) and an empty vec for a healthy graph or library deployments without ledger/registry state.

Source

pub fn detect_cycle_entry_ids(ctx: &Arc<Context>) -> Vec<Vec<String>>

Self::detect_cycles with every fiber id resolved to its owning entry id via the LoaderJournal (untracked fibers fall back to their stringified id) — the shape admin surfaces report.

Source§

impl Loader

Source

pub fn new() -> Loader

Source

pub fn persist_path() -> &'static str

Canonical persistence path (config/entries.json).

Source

pub fn toon_path() -> &'static str

Alternative toon persistence path (config/cordis-entries.toon).

Source

pub fn load_from_file(path: &Path) -> Result<EntryTree, CordisError>

Load an EntryTree from a TOML file (config/cordis-entries.toml).

Expected format:

[[entry]]
id = "calculator"
plugin = "CalculatorService"
disabled = false

[entry.config]
Source

pub fn reconcile( &self, current: &EntryTree, desired: &EntryTree, ) -> Vec<LoaderAction>

Incremental diff current → desired producing ordered LoaderActions.

Rules (per-field dispatch):

  • missing id in currentBegin (if not disabled)
  • id in current but not desiredRetire
  • plugin changed → RebuildFiber
  • config changed → UpdateConfig
  • disabled toggled → Retire / Begin
  • isolate or intercept changed → RebuildFiber
Source

pub fn execute_action(action: &LoaderAction, ctx: &Arc<Context>)

Execute a reconciliation action against the context.

Begin / RebuildFiber require the plugin factory from the crate::PluginRegistry; when it is not provided (or no factory is registered under the entry’s plugin name) these arms fall back to log-only. Startup instantiation of new entries goes through Loader::instantiate instead, which reports per-entry results.

The crate::LoaderJournal (when provided as a Service) makes the UpdateConfig and Retire arms real: UpdateConfig stores the new config, bumps generation, and calls Fiber::update when the journal knows the live fiber id (leaning on crate::RegistryService::get_fiber to resolve it); Retire clears the record and bumps generation. When the journal is absent both arms stay log-only.

Source

pub async fn reload_current( ctx: &Arc<Context>, path: &Path, current: &mut EntryTree, desired_composed: &EntryTree, journal: &LoaderJournal, ) -> Option<Vec<AppliedAction>>

Diff the caller-supplied composed desired_composed tree (includes resolved, groups flattened, configs interpolated — see compose_all) against the CurrentEntries-style current tree and apply for real.

This is the runtime hot-reload primitive shared by the file watcher and the admin reload endpoint. Callers own parsing + composition; returns per-action outcomes for the diff that was applied.

Source

pub fn take_trial_validation(entry_id: &str) -> Option<ValidationError>

Per-entry stash of the most recent structured validation failures from Self::trial_config_verified pre-flights.

AppliedAction rows carry plain strings, so the admin PATCH surface could not answer 4xx with machine-readable issues. Trials record here keyed by entry id (crate::error::stash_trial_validation); the HTTP layer consumes the slot after a failed apply. Slots mirror the LATEST trial outcome — recording a non-validation error clears the entry, and consumption removes it.

Source

pub async fn replace_provider( &self, ctx: &Arc<Context>, plugin_name: &str, config: Value, journal: &LoaderJournal, ) -> Result<u64, CordisError>

Broker a rolling provider replacement with zero absence window (paper §6 semantics).

Resolves the live registration from the crate::LoaderJournal by plugin label (first journaled entry whose plugin matches — the same label also selects the replacement factory from the crate::PluginRegistry, mirroring how admins name a running provider), trials that factory with the NEW config OUT-OF-BAND on a scratch child context exactly like Self::rebuild_fiber_verified, and only then swaps: the new instances are bridged in as intercept overrides (intercept lookups precede store lookups, so get keeps resolving), the old fiber retires, and the bridged values are promoted into the store under a fresh registration fiber before the bridge drops. Consumers observe no gap: every lookup stays satisfied at every instant because the key never becomes unprovided.

The old fiber is disposed DIRECTLY through its registration fiber instead of going through [Context::remove] — this deliberately bypasses the public guarded-withdrawal check. The guard exists to refuse removals that would leave active consumers UNRESOLVED; here resolution stays continuous by construction (the bridge is installed before disposal), which is precisely why the broker may bypass it. Genuine withdrawals (the admin retire endpoint) must keep using the guarded path.

Failure policy: a failing trial returns Err and leaves the old provider serving untouched; the journal advances only on success (generation bump + new fiber id).

Root-realm only for now: if the trial produces services carrying an isolate label, the call fails with CordisError::Configuration naming the limitation — isolated lookups skip intercept overrides, so the bridge mechanism cannot cover them.

Source

pub fn instantiate( ctx: &Arc<Context>, plugin_name: &str, config: &Value, entry_id: &str, ) -> Result<u64, CordisError>

Instantiate one entry by plugin name through the crate::PluginRegistry.

Looks up the factory registered under plugin_name, invokes it with (ctx, config) so the plugin lands via Context::plugin (single-source discipline applies), and returns the resulting fiber id. When the crate::LoaderJournal is provided, the successful instantiation records {plugin, config, fiber_id: Some(fid), generation+1} so later UpdateConfig / Retire actions can resolve the live fiber. Missing registry or missing factory are CordisError::Configuration.

Source

pub fn instantiate_entry( ctx: &Arc<Context>, entry: &Entry, ) -> Result<u64, CordisError>

Instantiate one Entry, applying isolate / intercept onto ctx.

intercept is bound first so the factory can read EntryIntercept. After the factory provides, newly inserted TypeIds are labeled with isolate so get_isolated matches the entry’s realm.

Trait Implementations§

Source§

impl Clone for Loader

Source§

fn clone(&self) -> Loader

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

impl Debug for Loader

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for Loader

Source§

fn default() -> Loader

Returns the “default value” for a type. Read more
Source§

impl Service for Loader

Source§

fn name(&self) -> &'static str

Source§

fn init( &self, _ctx: &Arc<Context>, ) -> Pin<Box<dyn Future<Output = Result<Option<Box<dyn Disposable>>, CordisError>> + Send + '_>>

Source§

fn check(&self) -> bool

Availability predicate for this service instance. 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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

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

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> ErasedDestructor for T
where T: 'static,

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

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> MaybeSendSync for T

Source§

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

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
Source§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
Source§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
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> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

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

Source§

type Error = !

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