turnframe_runtime/lib.rs
1//! `turnframe-runtime`: what a user's turn actually does. The model proposes meaning,
2//! deterministic code decides effects, committed events decide claims.
3//!
4//! [`orchestrator::Orchestrator::handle_turn`] runs the pipeline of spec §23, one stage
5//! per module, so a trace, a phase marker and a replay record point at the same place.
6//! The invariants the stages make concrete are tabulated in `docs/architecture.md`, and
7//! where each signal fires in `docs/telemetry.md`.
8//!
9//! | Stage | Module | What it decides |
10//! | --- | --- | --- |
11//! | configuration | [`config`] | how much autonomy the model gets, which risk classes a sandbox refuses, the conservative defaults |
12//! | budget | [`budget`] | what a turn may spend, and which bound stopped it |
13//! | understand | `understand` (private) | what the turn asks, read by small verified model tasks into one [`Understanding`](turnframe_core::understanding::Understanding) |
14//! | resolve | [`resolve`] | which record "the Ferri trip" is, or that it is a question; never a guess (I8) |
15//! | policy | [`policy`] | whether a command may run now, and if not, which card would authorize it |
16//! | reduce | [`reduce`] | every act's explicit result for the whole turn (§13, I11) |
17//! | interactions | [`interactions`] | durable cards, persisted before any sentence refers to them (§15) |
18//! | resume | [`resume`] | what a card remembers, so answering it continues the act it interrupted |
19//! | execute | [`execute`] | admission before effect, optimistic concurrency, the outbox, one atomic commit (§16) |
20//! | compose | [`compose`] | receipts from committed events, one answer per question, and an acknowledgement written from the turn's outcome and reviewed |
21//! | stream | [`stream`] | nothing that states an outcome goes on the wire before the commit (§18.5) |
22//! | trace | [`trace`] | every event and model call of a turn, as one JSON line each |
23//! | recover | [`recover`] | after a crash: resume by idempotency key, regenerate the answer, or reconcile |
24//! | dispatch | [`dispatch`] | the external-effect saga: claim a due outbox row, send it, settle it |
25//! | planning | [`planning`] | the pipeline stopped before the first effect, for a path running beside an existing one |
26//! | divergence | [`divergence`] | what the two paths disagreed about |
27//!
28//! # Example
29//!
30//! Configure the runtime and check that a sandbox refuses what §11.4 says it
31//! must.
32//!
33//! ```
34//! use turnframe_core::prelude::*;
35//! use turnframe_runtime::config::{OrchestrationMode, OrchestratorConfig, ResourceBudget,
36//! SandboxAcknowledgement};
37//!
38//! let config = OrchestratorConfig::conservative();
39//! config.validate()?;
40//! assert_eq!(config.mode, OrchestrationMode::Deterministic);
41//!
42//! let sandbox = OrchestrationMode::sandboxed_autonomous(
43//! ResourceBudget::conservative(),
44//! SandboxAcknowledgement::i_accept_unreviewed_autonomous_writes(),
45//! );
46//! assert!(sandbox.allows_risk(RiskClass::ReversibleLowRisk));
47//! assert!(!sandbox.allows_risk(RiskClass::ExternalRegulated));
48//! # Ok::<(), turnframe_runtime::config::ConfigError>(())
49//! ```
50//!
51//! Wiring a whole orchestrator needs a workflow registry, a provider pool and a
52//! set of stores; the runnable version lives in the integration tests, where
53//! [`tests/support/mod.rs`] assembles the sample trip and traveler domains
54//! against the in-memory stores and a scripted provider.
55//!
56//! [`tests/support/mod.rs`]: https://github.com/turnframe-rs/turnframe/blob/main/crates/turnframe-runtime/tests/support/mod.rs
57
58#![forbid(unsafe_code)]
59#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
60// `OrchestratorError` is the library's canonical failure family (spec §24) and
61// it is deliberately wide: it carries the domain rejection, the revision
62// conflict or the store failure that caused it, because a caller that has to
63// re-read a `Display` string to find out whether an effect may exist has been
64// handed the wrong type. It is returned at most once per turn, on a path that
65// already did I/O, so boxing it would move an allocation onto the happy path to
66// save copying a value on the failing one.
67#![allow(clippy::result_large_err)]
68
69/// The crate README, compiled as a doc-test so its example cannot rot.
70#[cfg(doctest)]
71#[doc = include_str!("../README.md")]
72mod readme {}
73
74pub mod attachments;
75pub mod budget;
76pub mod compose;
77pub mod config;
78pub mod conversation;
79pub mod copy;
80pub mod dispatch;
81pub mod divergence;
82pub mod effort;
83pub mod execute;
84pub mod interactions;
85mod narrate;
86pub mod orchestrator;
87pub mod planning;
88pub mod policy;
89pub mod recover;
90pub mod reduce;
91pub mod resolve;
92pub mod resume;
93mod signals;
94pub mod stream;
95pub mod trace;
96mod turn;
97mod understand;