Skip to main content

appcore_core/
builder.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: builder.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/05/29 20:47:35 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/06/04 11:51:30 by dnettoRaw
8//      ###########      S: 0.6.0
9// =============================================================================
10
11//! Minimal runtime builder for wiring one plugin into core registries.
12
13use crate::audit::AuditLog;
14use crate::bus::CommandBus;
15use crate::command::CommandRegistry;
16use crate::decision::{DecisionEngine, DecisionRegistry};
17use crate::error::{RuntimeError, RuntimeResult};
18use crate::event::EventRegistry;
19use crate::event_bus::EventBus;
20use crate::identity::RuntimeIdentity;
21use crate::ids::NodeId;
22use crate::lifecycle::RuntimeLifecycle;
23use crate::plugin::AppPlugin;
24use crate::runtime::RuntimeInstance;
25use crate::state::StateRegistry;
26use appcore_contracts::ApplicationManifestV1;
27
28/// Builder that aggregates one plugin manifest and public registries.
29#[derive(Debug, Default)]
30pub struct RuntimeBuilder {
31    application_manifest: Option<ApplicationManifestV1>,
32    identity: Option<RuntimeIdentity>,
33    command_registry: CommandRegistry,
34    event_registry: EventRegistry,
35    state_registry: StateRegistry,
36    decision_registry: DecisionRegistry,
37    decision_engine: DecisionEngine,
38    command_bus: CommandBus,
39    event_bus: EventBus,
40    audit_log: AuditLog,
41    lifecycle: RuntimeLifecycle,
42}
43
44impl RuntimeBuilder {
45    /// Creates an empty low-level Runtime composition builder.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Registers the primary plugin and derives its node-scoped manifest.
51    pub fn with_plugin<P: AppPlugin + ?Sized>(
52        &mut self,
53        plugin: &P,
54        node_id: NodeId,
55    ) -> RuntimeResult<&mut Self> {
56        if self.application_manifest.is_some() {
57            return Err(RuntimeError::PluginAlreadyRegistered);
58        }
59
60        let application_manifest = plugin.application_manifest();
61        let identity = plugin.identity(node_id);
62        plugin.register_commands(&mut self.command_registry)?;
63        plugin.register_events(&mut self.event_registry)?;
64        plugin.register_states(&mut self.state_registry)?;
65        plugin.register_decisions(&mut self.decision_registry)?;
66        plugin.register_decision_nodes(&mut self.decision_engine)?;
67        self.sync_decision_registry_with_engine()?;
68        plugin.register_handlers(&mut self.command_bus)?;
69        self.application_manifest = Some(application_manifest);
70        self.identity = Some(identity);
71
72        Ok(self)
73    }
74
75    /// Adds behavior from another plugin without replacing identity.
76    pub fn with_additional_plugin<P: AppPlugin + ?Sized>(
77        &mut self,
78        plugin: &P,
79    ) -> RuntimeResult<&mut Self> {
80        plugin.register_commands(&mut self.command_registry)?;
81        plugin.register_events(&mut self.event_registry)?;
82        plugin.register_states(&mut self.state_registry)?;
83        plugin.register_decisions(&mut self.decision_registry)?;
84        plugin.register_decision_nodes(&mut self.decision_engine)?;
85        self.sync_decision_registry_with_engine()?;
86        plugin.register_handlers(&mut self.command_bus)?;
87        Ok(self)
88    }
89
90    fn sync_decision_registry_with_engine(&mut self) -> RuntimeResult<()> {
91        for name in self.decision_engine.node_names() {
92            if !self.decision_registry.contains(name) {
93                self.decision_registry.register_name(name)?;
94            }
95        }
96        Ok(())
97    }
98
99    /// Returns the primary application manifest, when configured.
100    pub fn application_manifest(&self) -> Option<&ApplicationManifestV1> {
101        self.application_manifest.as_ref()
102    }
103
104    /// Returns declared commands.
105    pub fn commands(&self) -> &CommandRegistry {
106        &self.command_registry
107    }
108
109    /// Returns declared events.
110    pub fn events(&self) -> &EventRegistry {
111        &self.event_registry
112    }
113
114    /// Returns declared states.
115    pub fn states(&self) -> &StateRegistry {
116        &self.state_registry
117    }
118
119    /// Returns declared decision node names.
120    pub fn decisions(&self) -> &DecisionRegistry {
121        &self.decision_registry
122    }
123
124    /// Returns the configured command bus.
125    pub fn command_bus(&self) -> &CommandBus {
126        &self.command_bus
127    }
128
129    /// Produces an immutable Runtime instance.
130    pub fn build(self) -> RuntimeResult<RuntimeInstance> {
131        let application_manifest = self
132            .application_manifest
133            .ok_or(RuntimeError::MissingManifest)?;
134        let identity = self.identity.ok_or(RuntimeError::MissingManifest)?;
135
136        Ok(RuntimeInstance {
137            application_manifest,
138            identity,
139            command_registry: self.command_registry,
140            event_registry: self.event_registry,
141            state_registry: self.state_registry,
142            decision_registry: self.decision_registry,
143            decision_engine: self.decision_engine,
144            command_bus: self.command_bus,
145            event_bus: self.event_bus,
146            audit_log: self.audit_log,
147            lifecycle: self.lifecycle,
148        })
149    }
150}
151
152#[cfg(test)]
153mod builder_tests;