toolkit_zero/lib.rs
1//! # toolkit-zero
2//!
3//! A feature-selective Rust utility crate. Declare only the functionality your
4//! project requires via Cargo feature flags; each feature compiles exclusively
5//! the code it depends on, with no extraneous overhead.
6//!
7//! ---
8//!
9//! ## Table of Contents
10//!
11//! 1. [Feature flags](#feature-flags)
12//! 2. [Serialization](#serialization)
13//! 3. [Socket — server](#socket--server)
14//! 4. [Socket — client](#socket--client)
15//! 5. [Location](#location)
16//! 6. [Encryption — Timelock](#encryption--timelock)
17//! 7. [Dependency Graph — BuildTimeFingerprint](#dependency-graph--buildtimefingerprint)
18//! 8. [Backend deps](#backend-deps-1)
19//!
20//! ---
21//!
22//! ## Feature flags
23//!
24//! | Feature | Enables | Exposes |
25//! |---|---|---|
26//! | `serialization` | ChaCha20-Poly1305 authenticated encryption (seal / open) | [`serialization`] |
27//! | `socket-server` | Authenticated encryption + typed HTTP server builder | [`socket::server`] |
28//! | `socket-client` | Authenticated encryption + typed HTTP client builder | [`socket::client`] |
29//! | `socket` | Both `socket-server` and `socket-client` | both |
30//! | `location-browser` | Browser-based geolocation | [`location::browser`] |
31//! | `location` | Alias for `location-browser` | [`location`] |
32//! | `enc-timelock-keygen-now` | Time-lock key derivation from the system clock | [`encryption::timelock::derive_key_now`] |
33//! | `enc-timelock-keygen-input` | Time-lock key derivation from a caller-supplied time | [`encryption::timelock::derive_key_at`] |
34//! | `enc-timelock-async-keygen-now` | Async variant of `enc-timelock-keygen-now` | [`encryption::timelock::derive_key_now_async`] |
35//! | `enc-timelock-async-keygen-input` | Async variant of `enc-timelock-keygen-input` | [`encryption::timelock::derive_key_at_async`] |
36//! | `encryption` | All four `enc-timelock-*` features | [`encryption::timelock`] |
37//! | `dependency-graph-build` | Attach a normalised dependency-graph snapshot (`fingerprint.json`) at build time | [`dependency_graph::build`] |
38//! | `dependency-graph-capture` | Read the embedded `fingerprint.json` snapshot at runtime | [`dependency_graph::capture`] |
39//! | `backend-deps` | Re-exports all third-party deps used by each active module | `*::backend_deps` |
40//!
41//! ```toml
42//! [dependencies]
43//! # ChaCha20-Poly1305 authenticated encryption (seal / open)
44//! toolkit-zero = { version = "4", features = ["serialization"] }
45//!
46//! # HTTP server only
47//! toolkit-zero = { version = "4", features = ["socket-server"] }
48//!
49//! # HTTP client only
50//! toolkit-zero = { version = "4", features = ["socket-client"] }
51//!
52//! # Both sides of the socket
53//! toolkit-zero = { version = "4", features = ["socket"] }
54//!
55//! # Geolocation (bundles socket-server automatically)
56//! toolkit-zero = { version = "4", features = ["location"] }
57//!
58//! # Full time-lock encryption suite
59//! toolkit-zero = { version = "4", features = ["encryption"] }
60//!
61//! # Attach build-time fingerprint in build.rs
62//! # [build-dependencies]
63//! toolkit-zero = { version = "4", features = ["dependency-graph-build"] }
64//!
65//! # Read build-time fingerprint at runtime
66//! # [dependencies]
67//! toolkit-zero = { version = "4", features = ["dependency-graph-capture"] }
68//!
69//! # Re-export deps alongside socket-server
70//! toolkit-zero = { version = "4", features = ["socket-server", "backend-deps"] }
71//! ```
72//!
73//! ---
74//!
75//! ## Serialization
76//!
77//! The `serialization` feature exposes **ChaCha20-Poly1305 authenticated encryption**
78//! (IETF AEAD): [`serialization::seal`] encodes any [`bincode`]-encodable value and
79//! encrypts it with a random 12-byte nonce; [`serialization::open`] verifies the
80//! Poly1305 tag and decodes it back. A tampered ciphertext or wrong key is always
81//! detected and rejected.
82//!
83//! Keys implement `AsRef<str>` — plain `&str` literals and `String` both work.
84//! Key material is stored in `Zeroizing<String>` internally and wiped on drop.
85//!
86//! The two entry points are [`serialization::seal`] and [`serialization::open`].
87//! Because a fresh nonce is generated per call, encrypting the same value twice
88//! produces different ciphertexts (semantic security).
89//!
90//! Three attribute macros are also available via the `serialization` feature:
91//!
92//! | Macro | Purpose |
93//! |---|---|
94//! | [`serialization::serializable`] | derive `Encode+Decode` + inject `seal`/`open` methods |
95//! | [`serialization::serialize`] | inline seal to a variable or a file |
96//! | [`serialization::deserialize`] | inline open from a variable blob or a file |
97//!
98//! ```rust,ignore
99//! use toolkit_zero::serialization::{seal, open, Encode, Decode};
100//!
101//! #[derive(Encode, Decode, Debug, PartialEq)]
102//! struct Point { x: f64, y: f64 }
103//!
104//! let p = Point { x: 1.0, y: -2.0 };
105//! let blob = seal(&p, None::<&str>).unwrap(); // default key
106//! let back: Point = open(&blob, None::<&str>).unwrap();
107//! assert_eq!(p, back);
108//!
109//! let blob2 = seal(&p, Some("my-key")).unwrap(); // &str key — no .to_string() needed
110//! let back2: Point = open(&blob2, Some("my-key")).unwrap();
111//! assert_eq!(p, back2);
112//! ```
113//!
114//! ---
115//!
116//! ## Socket — server
117//!
118//! The `socket-server` feature exposes a fluent, type-safe builder API for
119//! declaring and serving HTTP routes. Begin each route with
120//! [`socket::server::ServerMechanism`], optionally attach a JSON body expectation,
121//! URL query parameters, or shared state, then finalise with `.onconnect(handler)`.
122//! Register routes on a [`socket::server::Server`] and serve them with a single
123//! call to `.serve(addr).await`.
124//!
125//! The [`socket::server::reply!`] macro is the primary way to construct responses.
126//!
127//! ```rust,ignore
128//! use toolkit_zero::socket::server::{Server, ServerMechanism, reply, Status};
129//! use serde::{Deserialize, Serialize};
130//! use std::sync::{Arc, Mutex};
131//!
132//! #[derive(Deserialize, Serialize, Clone)]
133//! struct Item { id: u32, name: String }
134//!
135//! #[derive(Deserialize)]
136//! struct NewItem { name: String }
137//!
138//! #[derive(Deserialize)]
139//! struct Filter { page: u32 }
140//!
141//! # async fn run() {
142//! let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
143//!
144//! let mut server = Server::default();
145//! server
146//! // Plain GET — no body, no state
147//! .mechanism(
148//! ServerMechanism::get("/health")
149//! .onconnect(|| async { reply!() })
150//! )
151//! // POST with a JSON body
152//! .mechanism(
153//! ServerMechanism::post("/items")
154//! .json::<NewItem>()
155//! .onconnect(|body: NewItem| async move {
156//! reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
157//! })
158//! )
159//! // GET with shared state
160//! .mechanism(
161//! ServerMechanism::get("/items")
162//! .state(store.clone())
163//! .onconnect(|state: Arc<Mutex<Vec<Item>>>| async move {
164//! let items = state.lock().unwrap().clone();
165//! reply!(json => items)
166//! })
167//! )
168//! // GET with URL query parameters
169//! .mechanism(
170//! ServerMechanism::get("/items/search")
171//! .query::<Filter>()
172//! .onconnect(|f: Filter| async move {
173//! let _ = f.page;
174//! reply!()
175//! })
176//! );
177//!
178//! server.serve(([127, 0, 0, 1], 8080)).await;
179//! # }
180//! ```
181//!
182//! Authenticated-encrypted routes are also supported via
183//! [`socket::server::ServerMechanism::encryption`] and
184//! [`socket::server::ServerMechanism::encrypted_query`].
185//! The body or query is decrypted (ChaCha20-Poly1305) before the handler is called;
186//! a wrong key or corrupt payload returns `403 Forbidden` automatically.
187//!
188//! ### Background server
189//!
190//! Call [`socket::server::Server::serve_managed`] to receive a
191//! [`socket::server::BackgroundServer`] handle. The server starts immediately;
192//! the handle exposes [`addr()`](socket::server::BackgroundServer::addr),
193//! [`rebind(addr)`](socket::server::BackgroundServer::rebind) (graceful restart on a
194//! new port, all routes preserved),
195//! [`mechanism(route)`](socket::server::BackgroundServer::mechanism) (hot-plug a new
196//! route with no restart), and [`stop()`](socket::server::BackgroundServer::stop).
197//!
198//! ### `#[mechanism]` attribute macro
199//!
200//! The [`socket::server::mechanism`] attribute macro is a concise alternative to
201//! the builder calls above. It replaces the decorated `async fn` in-place with the
202//! equivalent `server.mechanism(...)` statement — no separate registration step
203//! required. All 10 builder combinations are supported: plain, `json`, `query`,
204//! `state`, `encrypted`, `encrypted_query`, and every `state + …` combination.
205//!
206//! ```rust,ignore
207//! use toolkit_zero::socket::server::{Server, mechanism, reply, Status};
208//! use serde::{Deserialize, Serialize};
209//!
210//! #[derive(Deserialize)]
211//! struct NewItem { name: String }
212//!
213//! #[derive(Serialize, Clone)]
214//! struct Item { id: u32, name: String }
215//!
216//! # async fn run() {
217//! let mut server = Server::default();
218//!
219//! #[mechanism(server, GET, "/health")]
220//! async fn health() { reply!() }
221//!
222//! #[mechanism(server, POST, "/items", json)]
223//! async fn create_item(body: NewItem) {
224//! reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
225//! }
226//!
227//! server.serve(([127, 0, 0, 1], 8080)).await;
228//! # }
229//! ```
230//!
231//! See [`socket::server::mechanism`] for the full syntax reference.
232//!
233//! ---
234//!
235//! ## Socket — client
236//!
237//! The `socket-client` feature exposes a fluent [`socket::client::Client`] for
238//! issuing HTTP requests. Construct a client from a
239//! [`socket::client::Target`] (a localhost port or a remote URL), select an HTTP
240//! method, optionally attach a body or query parameters, and call `.send().await`
241//! (async) or `.send_sync()` (blocking).
242//!
243//! ```rust,ignore
244//! use toolkit_zero::socket::client::{Client, Target};
245//! use serde::{Deserialize, Serialize};
246//!
247//! #[derive(Deserialize, Serialize, Clone)]
248//! struct Item { id: u32, name: String }
249//!
250//! #[derive(Serialize)]
251//! struct NewItem { name: String }
252//!
253//! #[derive(Serialize)]
254//! struct Filter { page: u32 }
255//!
256//! # async fn run() -> Result<(), reqwest::Error> {
257//! // Async-only client with a 5-second timeout — safe inside #[tokio::main]
258//! use std::time::Duration;
259//! use toolkit_zero::socket::client::ClientBuilder;
260//! let client = ClientBuilder::new(Target::Localhost(8080))
261//! .timeout(Duration::from_secs(5))
262//! .build_async();
263//!
264//! // Plain GET
265//! let items: Vec<Item> = client.get("/items").send().await?;
266//!
267//! // POST with JSON body
268//! let created: Item = client
269//! .post("/items")
270//! .json(NewItem { name: "widget".into() })
271//! .send()
272//! .await?;
273//!
274//! // GET with query params
275//! let page: Vec<Item> = client
276//! .get("/items")
277//! .query(Filter { page: 2 })
278//! .send()
279//! .await?;
280//!
281//! // Synchronous client — must be built outside any async runtime
282//! let sync_client = ClientBuilder::new(Target::Localhost(8080)).build_sync();
283//! let _: Item = sync_client.delete("/items/1").send_sync()?;
284//! # Ok(())
285//! # }
286//! ```
287//!
288//! Authenticated-encrypted requests are available via
289//! [`socket::client::RequestBuilder::encryption`] and
290//! [`socket::client::RequestBuilder::encrypted_query`].
291//! The body or query parameters are sealed (ChaCha20-Poly1305) before the wire send;
292//! the sealed response is opened automatically.
293//!
294//! ### `#[request]` attribute macro
295//!
296//! The [`socket::client::request`] attribute macro is a concise alternative to
297//! the builder calls above. It replaces the decorated `fn` in-place with a `let`
298//! binding that performs the HTTP request. The **function name** becomes the
299//! binding name; the **return type** becomes `R` in the `.send::<R>()` turbofish.
300//! The function body is discarded. A return type annotation is **required**.
301//!
302//! All five builder modes are supported: plain, `json`, `query`, `encrypted`,
303//! and `encrypted_query`. Each mode accepts either `async` (`.send::<R>().await?`)
304//! or `sync` (`.send_sync::<R>()?`).
305//!
306//! ```rust,ignore
307//! use toolkit_zero::socket::client::{ClientBuilder, Target, request};
308//! use serde::{Deserialize, Serialize};
309//!
310//! #[derive(Deserialize, Serialize, Clone)]
311//! struct Item { id: u32, name: String }
312//!
313//! #[derive(Serialize)]
314//! struct NewItem { name: String }
315//!
316//! # async fn run() -> Result<(), reqwest::Error> {
317//! use std::time::Duration;
318//! let client = ClientBuilder::new(Target::Localhost(8080))
319//! .timeout(Duration::from_secs(5))
320//! .build_async();
321//!
322//! // Plain async GET
323//! #[request(client, GET, "/items", async)]
324//! async fn items() -> Vec<Item> {}
325//!
326//! // POST with JSON body
327//! #[request(client, POST, "/items", json(NewItem { name: "widget".into() }), async)]
328//! async fn created() -> Item {}
329//!
330//! // Synchronous DELETE
331//! #[request(client, DELETE, "/items/1", sync)]
332//! fn deleted() -> Item {}
333//! # Ok(())
334//! # }
335//! ```
336//!
337//! See [`socket::client::request`] for the full syntax reference.
338//!
339//! ---
340//!
341//! ## Location
342//!
343//! The `location` (or `location-browser`) feature provides browser-based geographic
344//! coordinate acquisition. A temporary HTTP server is bound on a randomly assigned
345//! local port, the system default browser is directed to a consent page, and the
346//! coordinates are submitted via the standard Web Geolocation API. The server
347//! shuts itself down upon receiving a result.
348//!
349//! Two entry points are available in [`location::browser`]:
350//!
351//! | Function | Context |
352//! |---|---|
353//! | [`location::browser::__location__`] | Blocking — safe from sync or async |
354//! | [`location::browser::__location_async__`] | Async — preferred inside `#[tokio::main]` |
355//!
356//! ```rust,ignore
357//! use toolkit_zero::location::browser::{__location__, __location_async__, PageTemplate};
358//!
359//! // Blocking — works from sync main or from inside a Tokio runtime
360//! match __location__(PageTemplate::default()) {
361//! Ok(data) => println!("lat={:.6} lon={:.6} ±{:.0}m",
362//! data.latitude, data.longitude, data.accuracy),
363//! Err(e) => eprintln!("location error: {e}"),
364//! }
365//!
366//! // Async — preferred when already inside #[tokio::main]
367//! # async fn run() {
368//! match __location_async__(PageTemplate::default()).await {
369//! Ok(data) => println!("lat={:.6} lon={:.6}", data.latitude, data.longitude),
370//! Err(e) => eprintln!("location error: {e}"),
371//! }
372//! # }
373//! ```
374//!
375//! The [`location::browser::PageTemplate`] enum controls what the user sees:
376//! a plain single-button page, a checkbox-gated variant, or a fully custom HTML
377//! document.
378//!
379//! ### `#[browser]` attribute macro
380//!
381//! The [`location::browser::browser`] attribute macro is a concise alternative
382//! to calling `__location__` / `__location_async__` directly. It replaces the
383//! decorated `fn` item with an inline location-capture statement; the function
384//! **name** becomes the binding that holds the resulting
385//! [`location::browser::LocationData`], and the [`location::browser::PageTemplate`]
386//! is built from the macro arguments.
387//!
388//! ```rust,ignore
389//! use toolkit_zero::location::browser::{browser, LocationData, LocationError};
390//!
391//! async fn get_location() -> Result<LocationData, LocationError> {
392//! // async, plain Default template
393//! #[browser]
394//! fn loc() {}
395//! Ok(loc)
396//! }
397//!
398//! async fn get_location_tickbox() -> Result<LocationData, LocationError> {
399//! // async, Tickbox with consent text
400//! #[browser(tickbox, title = "Verify Location", consent = "I agree")]
401//! fn loc() {}
402//! Ok(loc)
403//! }
404//!
405//! fn get_location_sync() -> Result<LocationData, LocationError> {
406//! // blocking, custom title
407//! #[browser(sync, title = "My App")]
408//! fn loc() {}
409//! Ok(loc)
410//! }
411//! ```
412//!
413//! See [`location::browser::browser`] for the full argument reference.
414//!
415//! ---
416//!
417//! ## Encryption — Timelock
418//!
419//! The `encryption` feature (or any `enc-timelock-*` sub-feature) exposes a
420//! **time-locked key derivation** scheme. A 32-byte key is derived from a
421//! time string through a three-pass KDF chain:
422//!
423//! > **Argon2id** (pass 1) → **scrypt** (pass 2) → **Argon2id** (pass 3)
424//!
425//! The key is reproducible only when the same time value, precision, format,
426//! and salts are supplied. An additional passphrase may be incorporated for a
427//! combined time × passphrase security model.
428//!
429//! **Features:**
430//!
431//! | Feature | Enables |
432//! |---|---|
433//! | `enc-timelock-keygen-now` | [`encryption::timelock::timelock`]`(None)` — decryption path (key from system clock) |
434//! | `enc-timelock-keygen-input` | [`encryption::timelock::timelock`]`(Some(t))` — encryption path (key from explicit time) |
435//! | `enc-timelock-async-keygen-now` | [`encryption::timelock::timelock_async`]`(None)` — async decryption path |
436//! | `enc-timelock-async-keygen-input` | [`encryption::timelock::timelock_async`]`(Some(t))` — async encryption path |
437//! | `encryption` | All four of the above |
438//!
439//! **Presets:** [`encryption::timelock::KdfPreset`] provides named parameter sets
440//! tuned per platform: `Balanced`, `Paranoid`, `BalancedMac`, `ParanoidMac`,
441//! `BalancedX86`, `ParanoidX86`, `BalancedArm`, `ParanoidArm`, and `Custom(KdfParams)`.
442//!
443//! ```rust,ignore
444//! use toolkit_zero::encryption::timelock::*;
445//!
446//! // Encryption side — caller sets the unlock time
447//! let salts = TimeLockSalts::generate();
448//! let kdf = KdfPreset::BalancedMac.params();
449//! let at = TimeLockTime::new(14, 30).unwrap();
450//! // params = None → _at (encryption) path
451//! let enc_key = timelock(
452//! Some(TimeLockCadence::None),
453//! Some(at),
454//! Some(TimePrecision::Minute),
455//! Some(TimeFormat::Hour24),
456//! Some(salts.clone()),
457//! Some(kdf),
458//! None,
459//! ).unwrap();
460//!
461//! // Pack all settings (incl. salts + KDF params) into a self-contained header;
462//! // store it in the ciphertext — salts and KDF params are not secret.
463//! let header = pack(TimePrecision::Minute, TimeFormat::Hour24,
464//! &TimeLockCadence::None, salts, kdf);
465//!
466//! // Decryption side — load header from ciphertext; call at 14:30 local time.
467//! // params = Some(header) → _now (decryption) path
468//! let dec_key = timelock(
469//! None, None, None, None, None, None,
470//! Some(header),
471//! ).unwrap();
472//! // enc_key.as_bytes() == dec_key.as_bytes() when called at 14:30 local time
473//! ```
474//!
475//! ---
476//!
477//! ## Dependency Graph — BuildTimeFingerprint
478//!
479//! Two features; one for each side of the boundary.
480//!
481//! **`dependency-graph-build`** (goes in `[build-dependencies]`):
482//! [`dependency_graph::build::generate_fingerprint`] runs `cargo metadata`, hashes
483//! `Cargo.lock` and every `.rs` file under `src/`, captures the profile, target
484//! triple, rustc version, and active features, then writes a compact, normalised
485//! JSON document to `$OUT_DIR/fingerprint.json`. Pass `true` to also export a
486//! pretty-printed copy alongside `Cargo.toml`.
487//! [`dependency_graph::build::export`] optionally writes a pretty-printed copy
488//! alongside `Cargo.toml` as a standalone call (useful when you want to combine it
489//! with a separate `generate_fingerprint(false)` invocation).
490//!
491//! **`dependency-graph-capture`** (goes in `[dependencies]`):
492//! [`dependency_graph::capture::parse`] deserialises the embedded snapshot into a
493//! typed [`dependency_graph::capture::BuildTimeFingerprintData`] struct.
494//!
495//! ### `#[dependencies]` attribute macro
496//!
497//! The [`dependency_graph::capture::dependencies`] attribute macro is a concise
498//! alternative to the manual `include_str!` + `parse()` boilerplate. Apply it to
499//! an empty `fn`; the function name becomes the binding.
500//!
501//! ```rust,ignore
502//! use toolkit_zero::dependency_graph::capture::dependencies;
503//!
504//! fn show() -> Result<(), Box<dyn std::error::Error>> {
505//! #[dependencies] // → let data: BuildTimeFingerprintData = parse(...)?;
506//! fn data() {}
507//! println!("{} v{}", data.package.name, data.package.version);
508//! Ok(())
509//! }
510//!
511//! fn raw() -> &'static [u8] {
512//! #[dependencies(bytes)] // → let raw: &'static [u8] = include_str!(...).as_bytes();
513//! fn raw() {}
514//! raw
515//! }
516//! ```
517//!
518//! See [`dependency_graph::capture::dependencies`] for the full syntax reference.
519//!
520//! ### Sections captured in `fingerprint.json`
521//!
522//! | Section | Contents |
523//! |---|---|
524//! | `package` | crate name + version |
525//! | `build` | profile, opt-level, target triple, rustc version, active feature flags |
526//! | `deps` | full normalised `cargo metadata` graph (sorted, no absolute paths) |
527//! | `cargo_lock_sha256` | SHA-256 of `Cargo.lock` (comment lines stripped) |
528//! | `source` | SHA-256 of every `.rs` file under `src/` |
529//!
530//! ### Setup
531//!
532//! ```toml
533//! [dependencies]
534//! toolkit-zero = { version = "4", features = ["dependency-graph-capture"] }
535//!
536//! [build-dependencies]
537//! toolkit-zero = { version = "4", features = ["dependency-graph-build"] }
538//! ```
539//!
540//! `build.rs`:
541//!
542//! ```rust,ignore
543//! fn main() {
544//! // Pass true to also export a pretty-printed copy alongside Cargo.toml.
545//! toolkit_zero::dependency_graph::build::generate_fingerprint(cfg!(debug_assertions))
546//! .expect("fingerprint generation failed");
547//! }
548//! ```
549//!
550//! `src/main.rs` (or any binary):
551//!
552//! ```rust,ignore
553//! use toolkit_zero::dependency_graph::capture;
554//!
555//! const BUILD_TIME_FINGERPRINT: &str = include_str!(concat!(env!("OUT_DIR"), "/fingerprint.json"));
556//!
557//! fn main() {
558//! let data = capture::parse(BUILD_TIME_FINGERPRINT).expect("failed to parse fingerprint");
559//! println!("{} v{}", data.package.name, data.package.version);
560//! println!("target : {}", data.build.target);
561//! println!("lock : {}", data.cargo_lock_sha256);
562//!
563//! let raw: &[u8] = BUILD_TIME_FINGERPRINT.as_bytes();
564//! println!("{} bytes", raw.len());
565//! }
566//! ```
567//!
568//! ### Risks and considerations
569//!
570//! * **Not tamper-proof** — the fingerprint is embedded as plain text in the
571//! binary's read-only data section and is readable by anyone with access to
572//! the binary. It is informational in nature; it does not constitute a
573//! security boundary.
574//! * **Export file** — `export(true)` writes `fingerprint.json` to the crate root.
575//! Add it to `.gitignore` to prevent accidental commits.
576//! * **Build-time overhead** — `cargo metadata` runs on every rebuild triggered
577//! by the `cargo:rerun-if-changed` directives (changes to `src/`, `Cargo.toml`,
578//! or `Cargo.lock`).
579//! * **Feature scope** — `build.features` captures active features of the crate
580//! being built, not toolkit-zero's own features.
581//! * **Path stripping** — absolute and machine-specific paths are removed from
582//! `cargo metadata` output so the fingerprint is stable across machines.
583//! * **Compile-time only** — the snapshot reflects the build environment at
584//! compile time; it does not change at runtime.
585//!
586//! ---
587//!
588//! ## Backend deps
589//!
590//! The `backend-deps` feature appends a `backend_deps` sub-module to each active
591//! module. Every such sub-module re-exports via `pub use` all third-party crates
592//! used internally by the parent module, allowing downstream crates to access
593//! those dependencies without separate `Cargo.toml` declarations.
594//!
595//! | Module | Path | Re-exports |
596//! |---|---|---|
597//! | serialization | [`serialization::backend_deps`] | `bincode`, `base64`, `zeroize` |
598//! | socket (server) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `bytes`, `serde_urlencoded`, `hyper`, `hyper_util`, `http`, `http_body_util` |
599//! | socket (client) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `reqwest` |
600//! | location | [`location::backend_deps`] | `tokio`, `serde`, `webbrowser`, `rand` |
601//! | encryption (timelock) | [`encryption::timelock::backend_deps`] | `argon2`, `scrypt`, `zeroize`, `chrono`, `rand`; `tokio` (async variants only) |
602//! | dependency_graph | [`dependency_graph::backend_deps`] | `serde_json`; `sha2` (build side only) |
603//!
604//! Enabling `backend-deps` without any other feature compiles successfully but
605//! exposes no symbols; every re-export within `backend_deps` is individually
606//! gated on the corresponding parent feature.
607
608#[cfg(any(feature = "socket", feature = "socket-server", feature = "socket-client"))]
609pub mod socket;
610
611#[cfg(any(feature = "location", feature = "location-browser"))]
612pub mod location;
613
614#[cfg(feature = "serialization")]
615pub mod serialization;
616
617#[cfg(any(feature = "encryption", feature = "enc-timelock-keygen-now", feature = "enc-timelock-keygen-input", feature = "enc-timelock-async-keygen-now", feature = "enc-timelock-async-keygen-input"))]
618pub mod encryption;
619
620#[cfg(any(feature = "dependency-graph-build", feature = "dependency-graph-capture"))]
621#[path = "dependency-graph/mod.rs"]
622pub mod dependency_graph;