Skip to main content

7.2 Entry point and hooks

// app/src/bin/main.rs
use bo_app::app::App;
use loco_rs::cli;
use migration::Migrator;

#[tokio::main]
async fn main() -> loco_rs::Result<()> {
cli::main::<App, Migrator>().await // source-verified signature
}
// app/src/app.rs
use std::{path::Path, sync::Arc};
use async_trait::async_trait;
use loco_rs::{
app::{AppContext, Hooks, Initializer},
bgworker::{BackgroundWorker, Queue},
boot::{create_app, BootResult, StartMode},
config::Config,
controller::AppRoutes,
environment::Environment,
task::Tasks,
Result,
};
use migration::Migrator;
use crate::{controllers, platform::Platform, settings::Settings, workers};

pub struct App;

#[async_trait]
impl Hooks for App {
fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") }

async fn boot(mode: StartMode, environment: &Environment, config: Config) -> Result<BootResult> {
create_app::<Self, Migrator>(mode, environment, config).await // VERIFY argument list
}

/// Take over logging so traces go to OpenTelemetry. Returning true tells Loco not to install its own logger.
fn init_logger(ctx: &AppContext) -> Result<bool> {
let s: Settings = ctx.config.settings()?;
if !s.telemetry.enabled { return Ok(false); }
bo_telemetry::init(&s.telemetry.service_name, &s.telemetry.otlp_endpoint)
.map_err(|e| loco_rs::Error::Message(e.to_string()))?;
Ok(true)
}

/// Load every YAML definition, validate, build adapters, compile workflows, publish as shared state.
/// A failure here stops the process: an invalid definition must never run on defaults.
async fn after_context(ctx: AppContext) -> Result<AppContext> {
let settings: Settings = ctx.config.settings()?;
let platform = Platform::load(&ctx, &settings)
.await
.map_err(|e| loco_rs::Error::Message(format!("config/agents invalid: {e}")))?;
ctx.shared_store.insert(Arc::new(platform)); // source-verified: SharedStore::insert<T>
Ok(ctx)
}

fn routes(_ctx: &AppContext) -> AppRoutes {
AppRoutes::with_default_routes() // VERIFY: adds /_ping and /_health
.add_route(controllers::agui::routes())
.add_route(controllers::agents::routes())
.add_route(controllers::documents::routes())
.add_route(controllers::webhooks::routes())
}

async fn initializers(_ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
Ok(vec![]) // TODO(impl): per-tenant rate-limit layer from policies.yaml
}

async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> {
queue.register(workers::ingest::IngestWorker::build(ctx)).await?;
queue.register(workers::drive_workflow::DriveWorkflowWorker::build(ctx)).await?;
Ok(())
}

fn register_tasks(tasks: &mut Tasks) {
tasks.register(crate::tasks::config_validate::ConfigValidate);
tasks.register(crate::tasks::schema_export::SchemaExport);
tasks.register(crate::tasks::retention::RetentionPurge);
tasks.register(crate::tasks::approval_expiry::ApprovalExpiry);
}

async fn truncate(_ctx: &AppContext) -> Result<()> { Ok(()) } // TODO(impl): test helper
async fn seed(_ctx: &AppContext, _base: &Path) -> Result<()> { Ok(()) }
}

/// Helper used by controllers, workers and tasks.
pub fn platform(ctx: &AppContext) -> Result<Arc<Platform>> {
ctx.shared_store
.get::<Arc<Platform>>() // source-verified: SharedStore::get<T: Clone>
.ok_or_else(|| loco_rs::Error::Message("platform not initialised".into()))
}