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 Runtime Console can see: routes, admin surfaces, runtime functions,
runtime schedules, events, lifecycle work, Console packages, 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::{
ConsoleArea, ConsolePackage, ConsoleSurface, ModuleHttpMethod,
ModuleHttpRoute, ModuleManifest,
};
pub fn manifest() -> ModuleManifest {
ModuleManifest::builder("billing")
.capabilities(vec![
"billing.invoice.read".to_owned(),
"billing.invoice.write".to_owned(),
])
.dependencies(vec!["auth".to_owned()])
.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()),
}])
.console(vec![ConsoleSurface {
name: "billing-invoices".to_owned(),
label: "Invoices".to_owned(),
area: ConsoleArea::Operations,
route: "/billing/invoices".to_owned(),
package: ConsolePackage {
name: "@acme/lenso-billing-console".to_owned(),
export: "billingConsoleModule".to_owned(),
},
icon: Some("Receipt".to_owned()),
required_capabilities: vec!["billing.invoice.read".to_owned()],
navigation: None,
}])
.build()
}
Fields
| Field | Purpose |
|---|---|
name |
Stable module id, such as auth, billing, or support-ticket. |
dependencies |
Other modules that must be installed first. Use this for hard runtime assumptions such as auth. |
capabilities |
Dot-separated permission names that routes, admin surfaces, runtime work, or Console pages can reference. |
http_routes |
Module-owned HTTP route metadata. Linked modules still mount real Axum routes through bindings. Remote route proxying uses this metadata for host-visible route intent. |
admin |
Schema-driven, declarative custom, or embedded custom operator surfaces. |
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 |
Runtime Console frontend surfaces backed by trusted packages or hosted bundles. |
story_display |
Console story-display metadata for timeline and inspection surfaces. |
Builder methods
ModuleManifest::builder("billing")
.dependencies(vec!["auth".to_owned()])
.capabilities(vec!["billing.invoice.read".to_owned()])
.http_routes(vec![/* ModuleHttpRoute */])
.admin(/* AdminSchema */)
.declarative_admin(/* AdminDeclarativeSurface */)
.embedded_admin(/* AdminEmbeddedSurface */)
.runtime(/* RuntimeSurface */)
.events(/* EventSurface */)
.lifecycle(/* LifecycleSurface */)
.console(vec![/* ConsoleSurface */])
.story_display(vec![/* StoryDisplayDescriptor */])
.build();
Use admin, declarative_admin, and embedded_admin as alternatives. A module
has one admin surface in its manifest; it can still expose multiple Console
surfaces through console.
Capability naming
Use dot-separated lowercase names:
<module>.<resource>.<action>
Good examples:
billing.invoice.read
billing.invoice.write
support.ticket.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 admin 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()),
}
Keep these values operator-friendly:
pathshould be the host-visible path.capabilityshould match a value incapabilities.display_nameshould be compact enough for timeline nodes.story_titleshould 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 Runtime 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. |
admin.schema |
Empty schema or missing read capability. |
admin.declarative |
Empty pages, missing query values, or invalid fallback schema references. |
admin.embedded |
Non-HTTPS entry URL, missing origin allowlist, or invalid fallback permissions. |
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 data-admin entity;
- 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 Runtime Console to verify the host-visible result.