Documentation
¶
Overview ¶
Package registry collects per-module declarations (routes, events, schedules, permissions) for the manifest endpoint to expose to the platform on deploy.
Routes are recorded by Module.Platform/Public/Internal as the developer registers handlers. Permissions are recorded by Module.RequirePermission. Events and schedules are recorded by Module.OnEvent / Module.Emits / Module.Cron (issue #9). The registry exposes empty defaults so the manifest payload shape is stable even when nothing is registered.
Index ¶
- Constants
- func ValidateDepID(kind, spec string)
- func ValidateName(kind, name string)
- type Dependency
- type MCPDigestFunc
- type MCPResourceDecl
- type MCPResourceHandler
- type MCPToolDecl
- type MCPToolHandler
- type MetricDecl
- type ModuleUI
- type OutboundContribution
- type Permission
- type Registry
- func (r *Registry) AbsorbInfra()
- func (r *Registry) AbsorbsInfra() bool
- func (r *Registry) AddDependency(dep Dependency) bool
- func (r *Registry) AddEmit(name string) bool
- func (r *Registry) AddExposedTable(name string)
- func (r *Registry) AddMCPResource(rc MCPResourceDecl) bool
- func (r *Registry) AddMCPTool(tool MCPToolDecl) bool
- func (r *Registry) AddMetric(d MetricDecl) bool
- func (r *Registry) AddOutboundContribution(c OutboundContribution)
- func (r *Registry) AddPermission(name string, roles []string)
- func (r *Registry) AddPermissionWithMeta(p Permission)
- func (r *Registry) AddRoute(scope Scope, method, path string)
- func (r *Registry) AddSchedule(name, cron, path string) bool
- func (r *Registry) AddSubscribe(name, path string) bool
- func (r *Registry) AddTask(task Task) bool
- func (r *Registry) Dependencies() []Dependency
- func (r *Registry) Description() string
- func (r *Registry) DescriptionLabels() map[string]string
- func (r *Registry) Emits() []string
- func (r *Registry) ExposedTables() []string
- func (r *Registry) MCPResource(name string) (MCPResourceDecl, bool)
- func (r *Registry) MCPResources() []MCPResourceDecl
- func (r *Registry) MCPTool(name string) (MCPToolDecl, bool)
- func (r *Registry) MCPTools() []MCPToolDecl
- func (r *Registry) Metrics() []MetricDecl
- func (r *Registry) OutboundContributions() []OutboundContribution
- func (r *Registry) PermissionRoles(name string) ([]string, bool)
- func (r *Registry) Permissions() []Permission
- func (r *Registry) Readme() map[string]string
- func (r *Registry) RequireStorage()
- func (r *Registry) Routes() map[Scope][]Route
- func (r *Registry) Schedules() []Schedule
- func (r *Registry) SetDescription(s string)
- func (r *Registry) SetDescriptionLabel(l i18n.Label)
- func (r *Registry) SetReadme(m map[string]string)
- func (r *Registry) SetUI(ui ModuleUI)
- func (r *Registry) StorageRequired() bool
- func (r *Registry) Subscribes() map[string]string
- func (r *Registry) Tasks() []Task
- func (r *Registry) UI() *ModuleUI
- type Route
- type Schedule
- type Scope
- type Task
- type TaskCompute
- type UIComponent
- type UIPage
- type UIProp
Constants ¶
const ( UISurfaceMain = "" UISurfaceSettings = "settings" )
Known UIPage.Surface values. Empty string defaults to the main surface so existing module manifests keep working unchanged.
Variables ¶
This section is empty.
Functions ¶
func ValidateDepID ¶
func ValidateDepID(kind, spec string)
ValidateDepID rejects module IDs that don't match the bare or owner-prefixed shape. kind is the user-facing API name so startup panics identify the declaration that failed. Used in place of ValidateName, which is too restrictive (it forbids `/` and `@`).
func ValidateName ¶
func ValidateName(kind, name string)
ValidateName rejects names that are empty, contain a path separator (/, \), contain a dot-segment (..), contain whitespace, or contain a null byte. The name is concatenated into a URL path by the SDK's event/cron handlers, so any character that chi might normalize or that an HTTP client cannot transmit safely is forbidden.
SECURITY: this is the registry-level invariant. Bypassing the user-facing Module.OnEvent / Cron / Emits API and calling AddSubscribe / AddSchedule / AddEmit directly still triggers this guard, so the SDK's manifest cannot contain a name that would mismatch the chi route table or would serialize to malformed JSON for downstream consumers.
Null byte (\x00) is blocked because it produces valid JSON but breaks many downstream consumers (shell, log parsers, PostgreSQL text columns). Unicode whitespace beyond ASCII is not blocked — chi's pattern matcher is byte-oriented and a Unicode-whitespace name would simply produce a dead handler, not a security issue.
kind is the user-facing API name (e.g., "OnEvent", "Cron", "Emits", "Record") used in the panic message so callers see which call failed validation.
Types ¶
type Dependency ¶
type Dependency struct {
ID string `json:"id"`
Version string `json:"version,omitempty"`
Optional bool `json:"optional,omitempty"`
Tables []string `json:"tables,omitempty"`
Events []string `json:"events,omitempty"`
}
Dependency is a declared dependency on another module. Optional=true means the consumer integrates with the dep opportunistically (typically co-located with an event subscription via ms.OptionalDependOn); Optional=false means the platform must install the dep before this module.
Version is a SemVer constraint string (npm-style: "^1.2.0", "~1.2.0", ">=1.2.0 <2.0.0", "1.x", exact "1.2.3", or "" for any). The platform catalog enforces matching at install time.
Tables and Events list relations and event names the consumer wants from the dep's `mod_<id>` schema. The catalog validates each name against the dep's manifest at install time, then issues the corresponding GRANT SELECT (Tables) and event-routing rules (Events) after app-owner approval. The consumer's binary doesn't need the dep's manifest to compile — names are strings.
type MCPDigestFunc ¶
type MCPDigestFunc func(result json.RawMessage) (json.RawMessage, error)
MCPDigestFunc turns a full tool result into a compact stand-in. Type-erased like MCPToolHandler; ms.ToolDigest builds one from a typed function.
type MCPResourceDecl ¶
type MCPResourceDecl struct {
Name string `json:"name"`
Description string `json:"description"`
Schema json.RawMessage `json:"schema,omitempty"`
Handler MCPResourceHandler `json:"-"`
}
MCPResourceDecl is a registered MCP resource.
type MCPResourceHandler ¶
type MCPResourceHandler func(ctx context.Context) (json.RawMessage, error)
MCPResourceHandler returns the current content of an MCP resource on demand.
type MCPToolDecl ¶
type MCPToolDecl struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
// Permission is the slug-qualified module permission gating the tool
// (ms.ToolPermission). Empty means none declared — the platform falls
// back to its membership floor.
Permission string `json:"permission,omitempty"`
// Annotations is the MCP spec's per-tool hint object, built from the
// Tool* options. Absent when the module declared none — which means
// UNKNOWN to every reader, never "safe".
Annotations json.RawMessage `json:"annotations,omitempty"`
Handler MCPToolHandler `json:"-"`
// Digest compresses THIS tool's result for replay on later turns. nil when
// the module declared none, in which case a caller that needs to shrink the
// result has to truncate it blindly.
Digest MCPDigestFunc `json:"-"`
}
MCPToolDecl is a registered MCP tool. Name, Description, and schemas appear in the manifest (via MCPToolExport, omitting Handler). Handler is invoked by the /__mirrorstack/mcp/tools/call route.
type MCPToolHandler ¶
type MCPToolHandler func(ctx context.Context, args json.RawMessage) (json.RawMessage, error)
MCPToolHandler is the type-erased handler signature used after generic MCPTool registration at the SDK level has wrapped the typed handler.
type MetricDecl ¶
type MetricDecl struct {
Name string `json:"name"`
Kind string `json:"kind,omitempty"`
AggregationKey string `json:"aggregationKey,omitempty"`
Unit string `json:"unit,omitempty"`
Price *int64 `json:"price,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
UnitLabels map[string]string `json:"unitLabels,omitempty"`
}
MetricDecl is a declared usage metric (ms.Meter). Exposed in the manifest so the platform populates its metric_definitions catalog (kind, unit, customer price) at install/publish — BEFORE any usage event arrives, so the catalog is authoritative for how a metric aggregates and is priced.
Kind is "counter" (additive; SUM) or "gauge" (absolute level; MAX/integral). For a custom metric Kind is always set; for a reserved infra.*/platform.* PRICE-OVERRIDE entry Kind and Unit are EMPTY (omitempty) — the platform catalog supplies the kind/unit, the module only overrides the customer price. AggregationKey is empty for legacy aggregation or "subject" for a Gauge declared with ms.BySubject; the platform maps that Gauge to keyed peak/MAX. Unit is a display unit (e.g. "order", "byte"); empty when undeclared. Price is the per-unit CUSTOMER price in micro-dollars and is omitempty: a metric may be metered without a declared price (PriceSet distinguishes a declared 0 from "no price", carried via the pointer being non-nil). Labels are per-locale display strings (locale → text), resolved at registration from the module's i18n catalogs (mirrors Permission.Labels). omitempty: a metric that declares no label ships no key and the platform falls back to the raw metric name. UnitLabels are per-locale display strings for the metric's Unit (locale → text), resolved the same way (mirrors Labels). omitempty: a metric that declares no unit label ships no key and the platform falls back to the raw Unit identifier. Distinct from Unit, which stays the untranslated billing unit used for per-unit pricing aggregation.
type ModuleUI ¶
type ModuleUI struct {
Components []UIComponent `json:"components"`
DefaultPages []UIPage `json:"defaultPages"`
}
ModuleUI is the module's declared UI surface. Two parts:
- Components: stable names for independently mountable bundle components. Each declaration maps its manifest name to a separate bundle export and carries the prop schema used by hosts and composition tooling.
- DefaultPages: the module's own React pages, mounted by the platform under /apps/<app-slug>/<module-slug><Route>. Each page is a full React export — the module has total layout freedom.
A module without a UI surface omits the ms.RegisterUI call entirely; Registry.UI() returns nil and the manifest omits the field.
type OutboundContribution ¶
type OutboundContribution struct {
Host string `json:"host"` // host module spec/id, e.g. "oauth-core"
// Constraint is the SemVer range of the host this contribution was authored
// against. The catalog needs it to refuse a contribution when the installed
// host version falls outside that range. Empty means any version, as for
// Dependency.Version.
Constraint string `json:"constraint,omitempty"`
Slot string `json:"slot"` // slot key on the host, e.g. "auth-provider"
Payload json.RawMessage `json:"payload"` // typed payload, validated by the host's slot at registration
}
OutboundContribution declares that this module pushes a payload INTO another module's contribution slot — the contributor side of Provide. Like Dependency it is pure manifest metadata: the module's binary never makes the call. The platform catalog (or the CLI dev runner) reads it at install / tunnel-up, validates Host+Slot against the host's provides, and — after app-owner approval — performs the registration on the contributor's behalf. The contributor declares WHAT and WHERE; it never holds the host's address or credentials.
type Permission ¶
type Permission struct {
Name string `json:"name"`
Roles []string `json:"roles"`
DefaultRole string `json:"defaultRole,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Descriptions map[string]string `json:"descriptions,omitempty"`
}
Permission is a declared module permission. Exposed in the manifest so the platform can surface "what does this module need" on its install screen.
DefaultRole is the role key the permission lands on by default (e.g. "viewer"); admin is always implicit and never stored. It is informational metadata for the platform UI — the enforced role set is Roles.
Labels and Descriptions are per-locale display strings (locale → text), resolved at registration from the module's i18n catalogs. Both are omitempty: a module that declares no Label/Description ships neither key, and the platform falls back to the raw permission name.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the per-module registry of routes/events/schedules/tasks/permissions. All operations are safe for concurrent use.
func (*Registry) AbsorbInfra ¶
func (r *Registry) AbsorbInfra()
AbsorbInfra declares that this module passes EVERY platform infrastructure metric through to the app owner at a price of 0 — it bills through its own meters and adds no infrastructure passthrough on top.
🔴 IT SETS A FLAG, IT DOES NOT ENUMERATE ANYTHING. The platform expands the set from its own catalog at publish, so a metric added next quarter is absorbed with no republish. A module that instead listed the infra metric names would be wrong the first time the platform renamed one — which has already happened once: infra.compute.ms was re-chartered to infra.compute.walltime.ms and then deleted, and every module still naming the old one silently declared nothing.
An explicit Meter("infra.X", Price(n)) is applied AFTER the absorb and WINS, so "absorb everything except egress, which I mark up" is expressible.
func (*Registry) AbsorbsInfra ¶
AbsorbsInfra reports whether AbsorbInfra was declared.
func (*Registry) AddDependency ¶
func (r *Registry) AddDependency(dep Dependency) bool
AddDependency records a dependency on another module. The Optional flag distinguishes required (install-time) from optional (runtime Resolve) deps. Version carries a SemVer constraint string (already validated by the caller); empty means any version is acceptable.
Dedup: if the same ID is declared both ways across the codebase, required wins (stricter beats looser). When a later required declaration upgrades a prior optional entry, the later declaration's Version replaces the earlier one (the required caller's constraint is authoritative). For same-type redeclarations (both required or both optional), first-wins — including for the Version field.
Returns true if the dependency was newly added or upgraded from optional to required, false if the call was a no-op (in either case Tables and Events from dep merge into the existing entry as a set union — repeated declarations only add, never remove).
func (*Registry) AddEmit ¶
AddEmit declares that the module emits an event of the given name. Returns true if added, false if a declaration for that name already exists (first-wins). Panics on an invalid name (see ValidateName).
func (*Registry) AddExposedTable ¶
AddExposedTable records a table NAME as eligible for SELECT by a depending module. v1 is TABLES ONLY, read-only — the producer marks a relation readable; it does NOT name WHO reads it. The app owner (not the producer) decides which installed modules may read by approving a dependency. There is intentionally no per-consumer allowlist on the exposure itself.
Name must match exposedTableNamePattern; validation panics, like the rest of the registry — an invalid declaration is a programmer error caught at module init, not a runtime input.
Dedup: declaring the same name twice is a no-op (set union). ExposedTables returns the de-duplicated set sorted, so repeated/feature-flagged declarations compose safely and the manifest output is deterministic.
func (*Registry) AddMCPResource ¶
func (r *Registry) AddMCPResource(rc MCPResourceDecl) bool
AddMCPResource registers an MCP resource. Same first-wins rule as AddMCPTool.
func (*Registry) AddMCPTool ¶
func (r *Registry) AddMCPTool(tool MCPToolDecl) bool
AddMCPTool registers an MCP tool. First-wins: if a tool with the same name already exists, the call is a no-op and returns false. Panics on an invalid name (see ValidateName).
func (*Registry) AddMetric ¶
func (r *Registry) AddMetric(d MetricDecl) bool
AddMetric records a declared usage metric. Returns true if added, false if a metric with the same name already exists. The caller (ms.Meter via meter. Declare) owns name + kind + reserved-prefix validation; this only dedups by name. Unlike the first-wins declarations (AddEmit/AddPermission), a duplicate metric name is treated as a programmer error by the caller (ms.Meter panics on a false return) — two declarations of the same metric with different kind/price would silently disagree, so it must fail loudly.
func (*Registry) AddOutboundContribution ¶
func (r *Registry) AddOutboundContribution(c OutboundContribution)
AddOutboundContribution records (or replaces) an outbound contribution keyed by (Host, Slot). Re-declaring the same Host+Slot overwrites the payload and constraint — last declaration wins — so a module contributes at most once per host slot.
func (*Registry) AddPermission ¶
AddPermission records a declared permission. First-wins by name: a second AddPermission for the same name is dropped (matches AddRoute / AddEmit / AddSchedule semantics). The roles slice is cloned so caller mutations after the call cannot leak into the stored copy. Panics on an invalid name (see ValidateName) — permissions don't end up in URL paths, so the path-separator check is purely cosmetic for permissions, but the consistency with AddSubscribe/AddEmit/AddSchedule prevents downstream consumers (DB columns, log parsers) from receiving malformed strings via the manifest.
func (*Registry) AddPermissionWithMeta ¶
func (r *Registry) AddPermissionWithMeta(p Permission)
AddPermissionWithMeta records a declared permission carrying optional metadata (DefaultRole, Labels, Descriptions). Same first-wins-by-name dedup, validation, and clone-on-store semantics as AddPermission — the simple AddPermission is just this with empty metadata. The Roles, Labels, and Descriptions are cloned so caller mutations after the call cannot leak into the stored copy.
func (*Registry) AddRoute ¶
AddRoute records a route under the given scope. First-wins: duplicate (scope, method, path) triples are dropped. Panics on an unknown scope — only SDK-internal code calls this, so an unknown value is a programmer error.
func (*Registry) AddSchedule ¶
AddSchedule registers a cron job. Returns true if added, false if a job with the same name already exists (first-wins). Panics on an invalid name (see ValidateName).
func (*Registry) AddSubscribe ¶
AddSubscribe declares that the module subscribes to an event from another module. The handler is mounted at path on the Internal scope. Returns true if added, false if a subscription for that event name already exists (first-wins). Panics on an invalid name (see ValidateName).
func (*Registry) AddTask ¶
AddTask declares a background task. Returns true if added, false if a task with the same name already exists (first-wins). Panics on an invalid name (see ValidateName).
func (*Registry) Dependencies ¶
func (r *Registry) Dependencies() []Dependency
Dependencies returns a non-nil deep copy of all declared dependencies in registration order. Tables and Events on each entry are cloned — slices.Clone is shallow, and a shared slice would let callers mutate the registry through the returned clone.
func (*Registry) Description ¶
Description returns the module description, or empty string if unset.
func (*Registry) DescriptionLabels ¶
DescriptionLabels resolves the module's per-locale description display strings (locale → text) against the catalogs loaded via RegisterMessages, or nil if no DescriptionLabel was declared. nil so the manifest omits the key (omitempty) and the platform falls back to Description. Mirrors Permission.Descriptions / MetricDecl.Labels — a resolved map keyed per locale. The Label is copied out under the lock and resolved AFTER releasing it, so Resolve's own i18n lock is never taken while holding the registry lock.
func (*Registry) ExposedTables ¶
ExposedTables returns a non-nil, SORTED, de-duplicated copy of all exposed table names. Sorting makes the manifest output deterministic (stable for prompt-cache and manifest-diffing) regardless of declaration order.
func (*Registry) MCPResource ¶
func (r *Registry) MCPResource(name string) (MCPResourceDecl, bool)
MCPResource returns the registered resource with the given name, or the zero value and false if no such resource is registered.
func (*Registry) MCPResources ¶
func (r *Registry) MCPResources() []MCPResourceDecl
MCPResources returns a non-nil copy of all registered resources in registration order.
func (*Registry) MCPTool ¶
func (r *Registry) MCPTool(name string) (MCPToolDecl, bool)
MCPTool returns the registered tool with the given name, or the zero value and false if no such tool is registered.
func (*Registry) MCPTools ¶
func (r *Registry) MCPTools() []MCPToolDecl
MCPTools returns a non-nil copy of all registered tools in registration order. Handlers are preserved in the copy; the slice header is independent.
func (*Registry) Metrics ¶
func (r *Registry) Metrics() []MetricDecl
Metrics returns a non-nil deep copy of all declared metrics in registration order. The Price pointer on each entry is cloned so caller mutations cannot leak back into the registry.
func (*Registry) OutboundContributions ¶
func (r *Registry) OutboundContributions() []OutboundContribution
OutboundContributions returns a non-nil deep copy of all declared outbound contributions in registration order. Payloads are cloned so callers can't mutate the registry through the returned slice.
func (*Registry) PermissionRoles ¶
PermissionRoles returns the enforced role set for a registered permission by its (already-qualified) name, and whether such a permission was registered. The returned slice is a clone so callers can't mutate the stored copy. Used by Module.RequirePermission to look up the roles declared via RegisterPermission.
func (*Registry) Permissions ¶
func (r *Registry) Permissions() []Permission
Permissions returns a non-nil deep copy of all declared permissions. The roles slice on each entry is cloned so caller mutations cannot leak back.
The hand-rolled loop is required: slices.Clone is shallow, and a shallow clone of []Permission would share each entry's Roles slice with the registry. TestPermissions_RolesAreCloned is the regression guard.
func (*Registry) Readme ¶
Readme returns the module's README locale map, or nil when it declared none — nil so the manifest omits the key rather than shipping an empty object.
func (*Registry) RequireStorage ¶
func (r *Registry) RequireStorage()
RequireStorage declares that this module needs its per-app object-storage namespace. The platform uses this manifest bit as the authorization gate for vending a temporary S3 credential; modules that do not opt in receive no S3 access at all.
func (*Registry) Routes ¶
Routes returns a copy of all routes grouped by scope. Every scope in AllScopes() is present in the result with at least an empty slice, so callers can rely on the field shape without nil checks.
func (*Registry) SetDescription ¶
SetDescription sets the module's human-readable description. Last-write-wins; typically set once via Description on ms.Config passed to ms.Init.
func (*Registry) SetDescriptionLabel ¶
SetDescriptionLabel records the module's per-locale description Label (ms.Text / ms.T), typically set once via DescriptionLabel on ms.Config. The Label is stored OPAQUE and resolved LATER (at manifest build, via DescriptionLabels) — the same lazy timing as ManifestDefaults.NameLabels — so it does not matter whether RegisterMessages ran before or after the label was set, only that both ran before the manifest is served. The plain Description string stays the default/fallback; this rides ALONGSIDE it per locale. Last-write-wins.
func (*Registry) SetReadme ¶
SetReadme records the module's long-form README as a locale map ("default" plus any README.<tag>.md). Already resolved — unlike DescriptionLabel these come from FILES rather than the i18n catalog, so there is nothing to defer. Last-write-wins.
func (*Registry) SetUI ¶
SetUI stores the module's declared UI manifest. Validates the input and panics on programmer errors (duplicate names, invalid route, unknown prop type). Last-write-wins; a second call replaces the first. The stored value is a deep copy so callers can mutate their input afterwards without aliasing into the registry.
func (*Registry) StorageRequired ¶
StorageRequired reports whether RequireStorage was declared.
func (*Registry) Subscribes ¶
Subscribes returns a non-nil copy of all event subscriptions.
type Schedule ¶
type Schedule struct {
Name string `json:"name"`
Cron string `json:"cron"`
Path string `json:"path"`
}
Schedule is a cron job declaration. Path is the URL the platform's scheduler invokes (POSTs to) when the cron fires; the SDK auto-derives it as /__mirrorstack/crons/{name} on the module's Internal scope.
type Scope ¶
type Scope string
Scope identifies which auth boundary a route belongs to. The three values match the three Module.Platform/Public/Internal entry points.
func AllScopes ¶
func AllScopes() []Scope
AllScopes returns the canonical ordering of scopes. The manifest endpoint iterates this so every scope appears in the payload even when no routes are registered for it.
func (Scope) IsValid ¶
IsValid reports whether s is one of the three known scopes. AddRoute panics on unknown scopes — the type accepts arbitrary strings, but only ScopePlatform/ScopePublic/ScopeInternal are valid scope keys in the manifest payload, and only SDK-internal code should be calling AddRoute.
type Task ¶
type Task struct {
Name string `json:"name"`
MaxDuration string `json:"maxDuration,omitempty"`
MaxRetries int `json:"maxRetries,omitempty"`
// Compute is a POINTER so an undeclared class omits the key entirely:
// encoding/json does not omit a zero struct, and a task that predates
// WithCompute must keep a byte-identical manifest entry.
Compute *TaskCompute `json:"compute,omitempty"`
// EphemeralStorageMB is omitted for the Fargate default (20 GiB) and for
// tasks that predate configurable ephemeral storage.
EphemeralStorageMB int `json:"ephemeralStorageMb,omitempty"`
}
Task is a declared background task. Exposed in the manifest so the platform can provision managed execution runners on deploy.
type TaskCompute ¶
type TaskCompute struct {
Class string `json:"class"` // "standard" (Lambda) | "heavy" (Fargate) | "gpu" (ECS on EC2)
VCPU float64 `json:"vcpu,omitempty"` // heavy: requested; gpu: resolved from Instance
MemoryMB int `json:"memoryMb,omitempty"` // standard/heavy: requested; gpu: resolved from Instance
Instance string `json:"instance,omitempty"` // gpu only
}
TaskCompute is a task's declared execution class and size. It exists so the platform can provision the right runner at deploy from the declaration alone — the same manifest-driven mechanism used by other platform resources.
It is equally a GUARDRAIL: "this module wants 4 vCPU / 8 GB" is visible in the manifest, therefore visible at install, therefore approved by the app owner before the module can spend anything — the same trust root that already governs dependencies and exposed tables.
The dimensions are RESOLVED, not requested: on the GPU class the instance type fixes vCPU and memory, and these fields carry what was resolved from it.
type UIComponent ¶
type UIComponent struct {
Name string `json:"name"`
Export string `json:"export"`
Props []UIProp `json:"props,omitempty"`
}
UIComponent declares one independently mountable component shipped by the module's web bundle. Name is its stable manifest identity; Export is the separate bundle export the host invokes. Props is the schema hosts validate before mounting and composition tooling uses when constructing a request.
type UIPage ¶
type UIPage struct {
Route string `json:"route"`
Surface string `json:"surface,omitempty"`
Title string `json:"title"`
TitleLabels map[string]string `json:"titleLabels,omitempty"`
Description string `json:"description,omitempty"`
DescriptionLabels map[string]string `json:"descriptionLabels,omitempty"`
Icon string `json:"icon,omitempty"`
Export string `json:"export"`
}
UIPage is one entry in DefaultPages — the module's own React page mounted at /apps/<app-slug>/<module-slug><Route>. "/" is the index page; "/users" is a sub-page. Export names the bundle's React export to mount; the platform fetches the module's web bundle and renders the matching named export for the requested route.
Surface picks which platform-rendered shell the page mounts into:
- "" (default, UISurfaceMain) — the primary module surface at /apps/<app>/<module-slug>/<route>. Used for the module's day-to-day pages.
- "settings" (UISurfaceSettings) — the per-module configuration surface at /apps/<app>/settings/module/<module-slug>/<route>. Used for install settings, secret entry, provider registries, and other configuration UIs that belong next to "manage this module" instead of next to "use this module".
More surfaces ("admin", "dev", …) can be added without changing the shape; the string value doubles as the URL segment the platform mounts under, so a single field describes both the JSON manifest and the routing convention.
The page's nav-rail icon is the module's icon (Config.Icon). Pages don't carry their own icon today — when distinct icons per page are needed, an Icon field will be added back as optional.
Title and Description remain the non-localized fallbacks, just as Defaults.Name does for NameLabels. Per-locale values come from catalog keys "ui.pages.<surface>.<route>.title" and ".description"; the main surface is written as "main", and the route is used verbatim. Older modules omit the label maps via omitempty and keep serving the plain strings unchanged.
An empty per-locale value means "translation pending, not chosen". Consumers must therefore fall back with logical-OR semantics (value[locale] || value["en-US"] || ...), never nullish-coalescing semantics.
type UIProp ¶
type UIProp struct {
Key string `json:"key"`
Type string `json:"type"`
Required bool `json:"required,omitempty"`
Default any `json:"default,omitempty"`
Hint string `json:"hint,omitempty"`
}
UIProp is one prop declared on a UIComponent. Type is one of the v0 set: "text", "secret", "textarea", "bool", "number", "text-list". Required defaults to false; Default carries the literal default value (any JSON); Hint is freeform help text shown to the agent / in dev tooling.