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
impl Loader
Sourcepub async fn move_entry(
ctx: &Arc<Context>,
current: &mut EntryTree,
journal: &LoaderJournal,
id: &str,
target: Option<&str>,
position: usize,
) -> Result<MoveOutcome, CordisError>
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::applyreconcile, 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
impl Loader
Sourcepub async fn apply(
ctx: &Arc<Context>,
current: &mut EntryTree,
desired: &EntryTree,
journal: &LoaderJournal,
) -> Vec<AppliedAction>
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
impl Loader
Sourcepub fn detect_cycles(ctx: &Arc<Context>) -> Vec<Vec<u64>>
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.
Sourcepub fn detect_cycle_entry_ids(ctx: &Arc<Context>) -> Vec<Vec<String>>
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
impl Loader
pub fn new() -> Loader
Sourcepub fn persist_path() -> &'static str
pub fn persist_path() -> &'static str
Canonical persistence path (config/entries.json).
Sourcepub fn toon_path() -> &'static str
pub fn toon_path() -> &'static str
Alternative toon persistence path (config/cordis-entries.toon).
Sourcepub fn load_from_file(path: &Path) -> Result<EntryTree, CordisError>
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]Sourcepub fn reconcile(
&self,
current: &EntryTree,
desired: &EntryTree,
) -> Vec<LoaderAction>
pub fn reconcile( &self, current: &EntryTree, desired: &EntryTree, ) -> Vec<LoaderAction>
Incremental diff current → desired producing ordered LoaderActions.
Rules (per-field dispatch):
- missing
idincurrent→Begin(if not disabled) idincurrentbut notdesired→Retirepluginchanged →RebuildFiberconfigchanged →UpdateConfigdisabledtoggled →Retire/Beginisolateorinterceptchanged →RebuildFiber
Sourcepub fn execute_action(action: &LoaderAction, ctx: &Arc<Context>)
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.
Sourcepub async fn reload_current(
ctx: &Arc<Context>,
path: &Path,
current: &mut EntryTree,
desired_composed: &EntryTree,
journal: &LoaderJournal,
) -> Option<Vec<AppliedAction>>
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.
Sourcepub fn take_trial_validation(entry_id: &str) -> Option<ValidationError>
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.
Sourcepub async fn replace_provider(
&self,
ctx: &Arc<Context>,
plugin_name: &str,
config: Value,
journal: &LoaderJournal,
) -> Result<u64, CordisError>
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.
Sourcepub fn instantiate(
ctx: &Arc<Context>,
plugin_name: &str,
config: &Value,
entry_id: &str,
) -> Result<u64, CordisError>
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.
Sourcepub fn instantiate_entry(
ctx: &Arc<Context>,
entry: &Entry,
) -> Result<u64, CordisError>
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§
Auto Trait Implementations§
impl Freeze for Loader
impl RefUnwindSafe for Loader
impl Send for Loader
impl Sync for Loader
impl Unpin for Loader
impl UnsafeUnpin for Loader
impl UnwindSafe for Loader
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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 moreimpl<T> MaybeSendSync for T
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
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
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
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
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.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
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.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
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.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
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.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
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.