---
title: Manifest Reference
description: 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

```rust
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

```rust
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:

```text
<module>.<resource>.<action>
```

Good examples:

```text
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:

```rust
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:

```rust
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](/docs/runtime-console) to verify the host-visible result.
