Skip to content
Lenso
English
Esc
navigateopen⌘Jpreview
On this page

Manifest Reference

Understand the serializable ModuleManifest contract that every Lenso module exposes.

ModuleManifest is the pure-data contract for a module. It describes what the host and Lenso Console can see: Business API routes, runtime functions, runtime schedules, events, lifecycle work, Console surfaces, dependencies, and capabilities.

Executable behavior does not live in the manifest. Linked Modules attach behavior through LinkedBinding. Independently running Services expose their own provider APIs and declare which Modules they provide. The pure manifest remains serializable in either topology.

Minimal manifest

use lenso::{
    ConsoleSurface, ConsoleSurfacePresentation, ModuleHttpMethod,
    ModuleHttpRoute, ModuleManifest, ModuleRequirement,
};

pub fn manifest() -> ModuleManifest {
    ModuleManifest::builder("acme/billing")
        .capabilities(vec![
            "billing.invoice.read".to_owned(),
            "billing.invoice.write".to_owned(),
        ])
        .requires(vec![
            ModuleRequirement::new("lenso/auth", "*").expect("valid requirement"),
        ])
        .http_routes(vec![ModuleHttpRoute {
            method: ModuleHttpMethod::Get,
            path: "/v1/billing/invoices".to_owned(),
            capability: Some("billing.invoice.read".to_owned()),
            display_name: Some("List invoices".to_owned()),
            story_title: Some("Billing invoices".to_owned()),
            operation: None,
        }])
        .console(vec![ConsoleSurface {
            name: "billing-invoices".to_owned(),
            label: "Invoices".to_owned(),
            route: "/billing/invoices".to_owned(),
            presentation: ConsoleSurfacePresentation::Esm {
                entry: "invoices".to_owned(),
            },
            icon: Some("receipt".to_owned()),
            required_capabilities: vec!["billing.invoice.read".to_owned()],
            navigation: None,
        }])
        .build()
}

Fields

Field Purpose
module_id Canonical fully qualified Module id, such as lenso/auth or acme/billing.
requires Other fully qualified Modules that must be installed first, with an explicit version requirement and optional required capabilities.
capabilities Dot-separated permission names that routes, runtime work, or Console pages can reference.
http_routes Module-owned HTTP route metadata. Linked Modules mount real Axum routes through bindings; Service-provided Modules retain the same declaration.
runtime Runtime function and schedule declarations. The declaration is manifest data; handlers live in the loading source and scheduling stays host-owned.
events Event handler declarations. The host registers and invokes the backing handler through the source binding.
lifecycle Startup checks and activation jobs. The host validates and schedules these entries instead of giving modules arbitrary startup callbacks.
console Declarative or ESM Console surfaces. Executable UI is bound by digest in the same Module Release, never by a same-origin package export.
story_display Console story-display metadata for timeline and inspection surfaces.

Builder methods

ModuleManifest::builder("acme/billing")
    .requires(vec![
        ModuleRequirement::new("lenso/auth", "*").expect("valid requirement"),
    ])
    .capabilities(vec!["billing.invoice.read".to_owned()])
    .http_routes(vec![/* ModuleHttpRoute */])
    .runtime(/* RuntimeSurface */)
    .events(/* EventSurface */)
    .lifecycle(/* LifecycleSurface */)
    .console(vec![/* ConsoleSurface */])
    .story_display(vec![/* StoryDisplayDescriptor */])
    .build();

Capability naming

Use dot-separated lowercase names:

<module>.<resource>.<action>

Good examples:

billing.invoice.read
billing.invoice.write
support.tickets.assign
auth.session.revoke

Avoid names such as BillingRead, billing/read, or read_invoice. Manifest lints warn when a capability name is not dot-separated lowercase, and they also warn when routes or Console permissions reference capabilities that the manifest does not declare.

Route metadata

ModuleHttpRoute is metadata:

ModuleHttpRoute {
    method: ModuleHttpMethod::Post,
    path: "/v1/billing/invoices/{invoice_id}/void".to_owned(),
    capability: Some("billing.invoice.write".to_owned()),
    display_name: Some("Void invoice".to_owned()),
    story_title: Some("Invoice voided".to_owned()),
    operation: None,
}

Keep these values operator-friendly:

  • path should be the host-visible path.
  • capability should match a value in capabilities.
  • display_name should be compact enough for timeline nodes.
  • story_title should read naturally when the route starts a business story.

Runtime schedules

RuntimeSurface.schedules declares when the host should enqueue a runtime function:

ScheduledFunctionDeclaration {
    name: "send-daily-digest".to_owned(),
    function_name: "billing.invoice.send_digest.v1".to_owned(),
    cron: "0 8 * * MON-FRI".to_owned(),
    input: serde_json::json!({ "window": "previous_day" }),
}

Schedules use 5-field UTC cron. The scheduled function must also appear in RuntimeSurface.functions; the host validates that relationship, persists schedule state, and enqueues normal runtime.function_runs when the schedule is due.

Lint categories

The host exposes manifest_lints through module metadata, and Lenso Console groups them into categories:

Category Common issue
module Missing module name.
capability Bad naming or referenced-but-undeclared capability.
routes Duplicate method/path, missing display metadata, or missing remote route capability.
runtime Missing function names, duplicate functions, bad queue names, or invalid retry policy.
runtime.schedule Missing schedule names, invalid cron expressions, or schedules that reference unknown functions.
events Missing event names or duplicate handler names.
lifecycle Startup checks or activation jobs that reference unknown runtime functions.
console Duplicate routes, missing labels, invalid package shape, or reserved navigation workspace ids.

Treat error lints as blockers. Treat warning lints as operator-experience debt unless the page explicitly says the warning is acceptable for a local spike.

When to change the manifest

Change the manifest whenever the host or Console needs new metadata:

  • a new public route or route capability;
  • a new Business API operation or operation capability;
  • a new module-owned Console page;
  • a new runtime function, schedule, or event handler;
  • a lifecycle startup check or activation job;
  • a new dependency such as auth;
  • a new capability that will be referenced by another surface.

After changing manifest shape, run the relevant local checks and open Lenso Console to verify the host-visible result.

Last updated on August 13, 2026

Was this page helpful?