Skip to main content

microvms_core/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2//! The composition root for the MicroVMs client (ARCH-8). It wires production edges
3//! into application use cases and re-exports the domain, application, edges, and
4//! protocol APIs at the paths the CLI and bindings consume (ARCH-1).
5//!
6//! # What this crate is for
7//!
8//! The domain owns rules and values without I/O; the application owns use cases
9//! against ports; the edges implement those ports. This crate provides the
10//! production constructors while keeping callers independent of that wiring.
11//! It depends on neither the CLI nor the bindings (ARCH-4).
12//!
13//! `docs/PLATFORM.md` records dated observations of AWS Lambda MicroVMs.
14//! Shared rules turn those observations into validation and defaults so callers
15//! receive the same behavior through Rust, the CLI, Python, and Node.js.
16//!
17//! # How strongly a trap is closed
18//!
19//! The spec ranks each closure, strongest first:
20//!
21//! * **S1, inexpressible** — the mistake cannot be written down. [`sizing::SizeClass`]
22//!   is closed over the documented baselines; [`hooks::RunHookTimeout`] and
23//!   [`hooks::BuildHookTimeout`] are separate types with no conversion between them,
24//!   so a 3600-second build timeout cannot reach a field that caps at 60.
25//! * **S2, expressible but rejected** — the mistake can be written and the client
26//!   refuses it locally, before any control-plane call, with an error naming the
27//!   `docs/PLATFORM.md` finding. Weaker because the guard is code that can regress,
28//!   but it costs seconds rather than a build cycle. Every boundary where a bare
29//!   integer or string still has to be judged lands here:
30//!   [`sizing::SizeClass::from_baseline_mib`], and `Region::from_str`, which refuses a
31//!   malformed region name.
32//! * **S3, correct by default and overridable** — weakest, because it protects the
33//!   caller who does nothing and abandons the one who overrides. An S3 closure must
34//!   say what the override costs.
35//!
36//! A region is any well-formed name since 1.3.0 (#743): AWS adds MicroVMs regions faster
37//! than a release can follow, so TRAP-6 is a report rather than a refusal. A call that
38//! fails with the null-message `AccessDeniedException` in a region outside
39//! [`region::MEASURED_REGIONS`] names the region's availability as a cause to verify.
40//!
41//! # Every error message names its finding
42//!
43//! A local reject explains itself by naming the `docs/PLATFORM.md` section that
44//! measured the behavior, because the codes and the guards exist precisely so a
45//! reader can go to the measurement rather than to a constraint. "access denied" sends
46//! someone to their IAM policy; the message a null-message denial in an unmeasured region
47//! actually raises sends them to the region first, then to CloudTrail.
48//!
49//! # A guard that cannot fail is worse than no guard
50//!
51//! Carried over from the Python era unchanged: every guard here has a falsification —
52//! a specific plausible edit that must turn a specific test red. "Delete the feature
53//! and the test fails" does not count. The clearest case is TRAP-13 in
54//! [`sizing`]: every documented peak is exactly four times its baseline, so a test
55//! against the shipped table cannot tell a table lookup from `baseline * 4`, and the
56//! guard has to drive the lookup over a table where the pattern does not hold.
57//!
58//! # Layout
59//!
60//! This crate is the composition root (ARCH-8). The code lives in the crates below it, and
61//! every item they export is re-exported here at the path it had in 0.10:
62//!
63//! * `microvms-domain`: the rules and values, with no I/O (ARCH-6): [`error`], [`region`],
64//!   [`sizing`], [`hooks`], [`constants`], [`cost`], and the pure halves of [`names`],
65//!   [`preflight`], [`identity`] and [`provision`].
66//! * `microvms-app`: the use cases, written only against ports (ARCH-7): [`control`],
67//!   [`session`], [`sandbox`], [`agents`], and the ports themselves, [`clock`], [`entropy`] and
68//!   [`adapters`].
69//! * `microvms-edges`: the production port implementations: the signed transport, the reqwest
70//!   backend, the sockets, the name registry on disk, [`provision`]'s fetch, and
71//!   [`env`](mod@env), the one process-environment lookup.
72//! * `protocol`, the wire contract shared with the daemon.
73//!
74//! What this crate adds is the wiring: [`prelude`]'s constructors (`ControlPlane::new`,
75//! `Sandbox::new`, `Session::connect` and the rest) put the production transport, clock,
76//! entropy and adapters into each type's port-taking constructor, and the free functions that
77//! read the production clock or pool keep their paths here. `use microvms_core::prelude::*;`
78//! keeps a 0.10 call compiling. The shared test doubles are in `testing`, behind the
79//! `test-support` feature.
80
81// CLI-7: a print macro panics when its stream's reader has gone (#216). Every write goes
82// through a checked writer instead.
83#![deny(clippy::print_stdout, clippy::print_stderr)]
84
85pub mod identity;
86pub mod prelude;
87
88// The rules and values live in `microvms-domain`, which can't do I/O (ARCH-6). Re-exported
89// whole, so every `microvms_core::` path they had still resolves (ARCH-1).
90pub use microvms_domain::{constants, cost, duration, error, hooks, region, sizing};
91
92// Whole modules that live in one crate below.
93pub use microvms_app::{defaults, sandbox};
94pub use microvms_edges::{env, provision, secret_input};
95
96// Re-exported so consumers name wire types through this crate rather than
97// depending on `protocol` directly — the CLI's thinness guard counts on that.
98pub use protocol;
99
100pub use error::{Error, ErrorKind, WireKind};
101pub use hooks::{BuildHookTimeout, RunHookTimeout};
102pub use region::Region;
103pub use sizing::SizeClass;
104
105/// The crate's own version, for a `doctor` or `manifest` command to report.
106pub const VERSION: &str = env!("CARGO_PKG_VERSION");
107
108// The README's example is the first code a crates.io reader copies, so it compiles as a
109// doctest (`no_run`: it launches a billed VM).
110#[cfg(doctest)]
111#[doc = include_str!("../README.md")]
112struct ReadmeExample;
113
114/// The edges' S3 and STS request helpers stay out of this crate's API: they were
115/// `pub(crate)` in 0.10, and one of them returns an `aws-sigv4` type, so exporting it would
116/// tie this crate's semver to that one's.
117///
118/// ```compile_fail
119/// let _ = microvms_core::control::services::s3_signing_settings();
120/// ```
121#[cfg(doctest)]
122struct EdgesHelpersStayPrivate;
123
124// Each module below is a use-case module from `microvms-app` with the production pieces
125// `microvms-edges` supplies for it, at the paths both had when they were one crate. A local
126// item or module shadows a glob, which is how a submodule that has halves in both crates is
127// merged.
128
129pub mod adapters {
130    //! The adapters port and its production implementation.
131    pub use microvms_app::adapters::*;
132    pub use microvms_edges::adapters::SystemAdapters;
133}
134
135pub mod clock {
136    //! The one clock port and tokio's clock.
137    pub use microvms_app::clock::*;
138    pub use microvms_edges::clock::{SystemClock, TokioClock};
139}
140
141pub mod entropy {
142    //! The entropy port and the OS random pool.
143    pub use microvms_app::entropy::*;
144    pub use microvms_edges::entropy::OsEntropy;
145}
146
147pub mod names {
148    //! Named MicroVMs: the record, the store port, and the CLI's registry on disk.
149    pub use microvms_app::names::*;
150    pub use microvms_edges::names::{FileNameStore, state_root};
151}
152
153pub mod preflight {
154    //! Whether a harness can launch here, checked before it queues work (#223).
155    //!
156    //! The checks are `microvms_app::preflight`'s; [`preflight`] runs them over the
157    //! production control plane.
158    pub use microvms_app::preflight::*;
159
160    use crate::control::ControlPlane;
161    use crate::prelude::ControlPlaneExt as _;
162    use crate::region::Region;
163
164    /// Runs the three checks for `region`, or for the region the environment names.
165    ///
166    /// The bindings' `preflight`. See the module docs of `microvms_app::preflight` for what
167    /// it checks, what it does not, and why nothing it does bills.
168    pub async fn preflight(region: Option<Region>) -> PreflightReport {
169        let resolved = match region {
170            Some(region) => Ok(region),
171            None => Region::from_env(&crate::env::process),
172        };
173        preflight_with(resolved, ControlPlane::new).await
174    }
175}
176
177pub mod agents {
178    //! The coding-agent helpers `docs/AGENT-VMS.md` specifies, and the Bedrock minter.
179    pub use microvms_app::agents::*;
180
181    use crate::error::Error;
182
183    pub mod bedrock {
184        //! A Bedrock bearer token from the caller's own AWS credentials (AGENT-4).
185        pub use microvms_app::agents::bedrock::*;
186        pub use microvms_edges::agents::bedrock::*;
187    }
188
189    /// The start request for one task (AGENT-7), with an exec id from the production clock
190    /// when `options` carries none. [`prompt_request_with`] over [`crate::session::mint_exec_id`].
191    pub fn prompt_request(
192        spec: &AgentSpec,
193        task: &str,
194        options: &PromptOptions,
195    ) -> Result<crate::protocol::exec::StartRequest, Error> {
196        prompt_request_with(spec, task, options, crate::session::mint_exec_id)
197    }
198}
199
200pub mod control {
201    //! The control-plane client, with the signed transport and the build services.
202    pub use microvms_app::control::*;
203    pub use microvms_edges::clock::SystemClock;
204    pub use microvms_edges::control::SignedBuildServices;
205
206    pub mod context {
207        //! A task's build context, and reading one from a directory (#221, IMAGE-7).
208        pub use microvms_app::control::context::*;
209        pub use microvms_edges::control::context::from_dir;
210    }
211
212    pub mod project {
213        //! A project directory's dependency files: the pairing rule, and reading the pair
214        //! from a directory (#74, #264).
215        pub use microvms_app::control::project::*;
216        pub use microvms_edges::control::project::read_project_files;
217    }
218    pub use project::read_project_files;
219
220    pub mod services {
221        //! The STS and S3 calls `ensure_image` makes, and their SigV4 implementation.
222        pub use microvms_app::control::services::*;
223        // Named, not globbed: the edges' request helpers are `pub(crate)` there, and a glob
224        // would make the next one that goes `pub` (and its aws-sigv4 types) core's API too.
225        pub use microvms_edges::control::services::SignedBuildServices;
226    }
227
228    pub mod transport {
229        //! The control plane's calls, the transport port, and the signed transport.
230        pub use microvms_app::control::transport::*;
231        pub use microvms_edges::control::transport::SignedTransport;
232    }
233
234    pub mod token {
235        //! Idempotency tokens, unique per attempt by construction (TRAP-1).
236        pub use microvms_app::control::token::*;
237        use microvms_edges::entropy::OsEntropy;
238
239        /// An image-create idempotency token, unique per attempt, from the OS random pool.
240        ///
241        /// [`create_token_with`] over the OS pool. `scope` is a label only, folded in beside
242        /// the nonce; see the module docs of `microvms_app::control::token` for the
243        /// fifteen-hour wedge that closure exists to prevent. An unavailable pool panics. A
244        /// control plane mints through its own source instead, so a launch refuses rather
245        /// than panics.
246        pub fn create_token(scope: &str) -> String {
247            create_token_with(scope, &OsEntropy).expect("the OS random pool is available")
248        }
249
250        /// A run idempotency token, unique per attempt, from the OS random pool. Same rule as
251        /// [`create_token`].
252        pub fn run_token(scope: &str) -> String {
253            run_token_with(scope, &OsEntropy).expect("the OS random pool is available")
254        }
255    }
256}
257
258pub mod session {
259    //! The in-VM client, with the reqwest backend and the sockets.
260    pub use microvms_app::session::*;
261    pub use microvms_edges::clock::TokioClock;
262    pub use microvms_edges::session::*;
263
264    pub mod http {
265        //! The HTTP seam and the production backend.
266        pub use microvms_app::session::http::*;
267        pub use microvms_edges::session::http::ReqwestBackend;
268    }
269
270    pub mod proxy {
271        //! The two-header proxy auth and the mint schedule, on tokio's clock in production.
272        pub use microvms_app::session::proxy::*;
273        pub use microvms_edges::clock::TokioClock;
274    }
275
276    pub mod exec {
277        //! The exec handle and the cursor-driven stream, with the production exec id.
278        pub use microvms_app::session::exec::*;
279
280        pub use super::mint_exec_id;
281    }
282
283    /// A fresh exec id from the production clock: `x-` plus 16 hex characters.
284    ///
285    /// [`exec_id_at`] over tokio's clock. Code holding a [`Session`] mints through the
286    /// session's clock instead, so a test that pins the clock pins the id.
287    pub fn mint_exec_id() -> String {
288        use microvms_app::clock::Clock as _;
289        exec_id_at(TokioClock::new().unix_now())
290    }
291}
292
293pub mod workspace {
294    //! Directory transfer: the manifest, the diff and the sync pass, over the real filesystem's
295    //! walk, hash, pack and guarded extraction (#260).
296    pub use microvms_app::workspace::*;
297    pub use microvms_edges::workspace::*;
298}
299
300#[cfg(feature = "test-support")]
301pub mod testing {
302    //! The shared test doubles, for a dependent's tests: `microvms_app::testing`.
303    pub use microvms_app::testing::*;
304}
305
306#[cfg(test)]
307mod tests {
308    use std::collections::HashSet;
309
310    /// The OS-pool tokens are distinct per call. The nonce is the pool's draw, so this is the
311    /// pool's distinctness as the token functions see it (TRAP-1).
312    ///
313    /// **Falsification**: mint `create_token` from a fixed source and the set collapses to one.
314    #[test]
315    fn the_os_pool_tokens_never_repeat() {
316        let minted: HashSet<String> = (0..200)
317            .flat_map(|_| {
318                [
319                    crate::control::token::create_token("img"),
320                    crate::control::token::run_token("img"),
321                ]
322            })
323            .collect();
324        assert_eq!(minted.len(), 400);
325    }
326
327    /// Two production exec ids differ, even minted in one instant.
328    #[test]
329    fn production_exec_ids_are_distinct() {
330        let minted: HashSet<String> = (0..200).map(|_| crate::session::mint_exec_id()).collect();
331        assert_eq!(minted.len(), 200);
332    }
333}
334
335pub mod history;
336pub mod ledger;