Skip to main content

frame_core/runtime/
mod.rs

1//! The composed component runtime — the ONLY composition surface embedding
2//! applications touch (scaffold-unification design, §5.2, finding B6).
3//!
4//! # Why this wrapper exists (the composition story)
5//!
6//! Before this module, every embedding host — frame-host's `runtime.rs` and
7//! every `frame new` scaffold in the wild — hand-wrote the same five beamr
8//! compositions, each of which hides a trap:
9//!
10//! 1. **Scheduler composition.** A bare `Scheduler::with_services` silently
11//!    installs an EMPTY built-in-function registry, so every `erlang:*`
12//!    import in hot-loaded bytecode resolves Deferred and guard-BIF dispatch
13//!    kills the process at its first arithmetic instruction with
14//!    `InvalidOperand("guard bif native import")` — the composition defect
15//!    attributed in `frame-host/attribution/gcbif-wedge/` (Artemis's
16//!    2026-07-18 probe report, Round 2). [`ComponentRuntime::compose`]
17//!    absorbs the fix, [`crate::composition::compose_scheduler`]: gate-1 BIF
18//!    population BEFORE construction, so load-time import resolution binds
19//!    `erlang:*` to Native entries.
20//! 2. **Support-module hot load.** A component's FFI bytecode is hot-loaded
21//!    directly on the scheduler, outside the registry's lifecycle — and the
22//!    registry's deferred-BIF wall did not cover it.
23//!    [`ComponentRuntime::load_support_module`] applies the same wall (the
24//!    crate-internal `composition::deferred_erlang_imports` scan both paths
25//!    share) and returns an opaque [`SupportModuleHandle`] instead of a
26//!    beamr atom.
27//! 3. **Support-module unload.** The correct teardown is a dance: purge any
28//!    retained old generation, delete the current one, then VERIFY the module
29//!    is actually gone. [`ComponentRuntime::unload_support_module`] performs
30//!    and verifies it; the single-use handle makes a double unload
31//!    unrepresentable.
32//! 4. **Mailbox acknowledgement values.** BEAM mailbox integers must fit the
33//!    small-integer term payload, and the low integers are usually claimed by
34//!    a child's message protocol (liveness, stop). Deriving an
35//!    acknowledgement from identifier bytes therefore means folding into
36//!    `reserved_below ..= Term::SMALL_INT_MAX` — previously hand-rolled
37//!    against `Term::SMALL_INT_MAX` in generated code, now
38//!    [`mailbox_integer`].
39//! 5. **Teardown discipline.** `Scheduler::shutdown` is not cleanup: ordered
40//!    stop must have drained every process tree first.
41//!    [`ComponentRuntime::shutdown`] refuses while
42//!    [`ComponentRuntime::live_process_count`] is nonzero.
43//!
44//! Zero beamr types appear in this module's public signatures: bytecode is
45//! `&[u8]`, modules are opaque handles, mailbox messages are the `i64` the
46//! registry already speaks. The runtime's beamr dependency (and its feature
47//! selection) is therefore declared in exactly one place — frame-core's own
48//! manifest — instead of being fossilized into every generated application
49//! (finding B6). [`crate::composition`] remains public as the wrapper's
50//! ground truth for embedders that need raw scheduler access; applications
51//! and frame-host itself compose only through this module.
52
53mod component_runtime;
54mod error;
55mod mailbox;
56
57pub use component_runtime::{
58    ComponentRuntime, RuntimePolicy, SupportModuleHandle, SupportModuleRef, SupportSwapper,
59};
60pub use error::RuntimeError;
61pub use mailbox::mailbox_integer;