registry

package
v0.4.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

View Source
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 New

func New() *Registry

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

func (r *Registry) AbsorbsInfra() bool

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

func (r *Registry) AddEmit(name string) bool

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

func (r *Registry) AddExposedTable(name string)

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

func (r *Registry) AddPermission(name string, roles []string)

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

func (r *Registry) AddRoute(scope Scope, method, path string)

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

func (r *Registry) AddSchedule(name, cron, path string) bool

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

func (r *Registry) AddSubscribe(name, path string) bool

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

func (r *Registry) AddTask(task Task) bool

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

func (r *Registry) Description() string

Description returns the module description, or empty string if unset.

func (*Registry) DescriptionLabels

func (r *Registry) DescriptionLabels() map[string]string

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) Emits

func (r *Registry) Emits() []string

Emits returns a non-nil copy of all declared emit events.

func (*Registry) ExposedTables

func (r *Registry) ExposedTables() []string

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

func (r *Registry) PermissionRoles(name string) ([]string, bool)

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

func (r *Registry) Readme() map[string]string

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

func (r *Registry) Routes() map[Scope][]Route

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) Schedules

func (r *Registry) Schedules() []Schedule

Schedules returns a non-nil copy of all scheduled jobs.

func (*Registry) SetDescription

func (r *Registry) SetDescription(s string)

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

func (r *Registry) SetDescriptionLabel(l i18n.Label)

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

func (r *Registry) SetReadme(m map[string]string)

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

func (r *Registry) SetUI(ui ModuleUI)

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

func (r *Registry) StorageRequired() bool

StorageRequired reports whether RequireStorage was declared.

func (*Registry) Subscribes

func (r *Registry) Subscribes() map[string]string

Subscribes returns a non-nil copy of all event subscriptions.

func (*Registry) Tasks

func (r *Registry) Tasks() []Task

Tasks returns a non-nil copy of all declared tasks.

func (*Registry) UI

func (r *Registry) UI() *ModuleUI

UI returns a deep copy of the stored manifest, or nil if SetUI was never called. Nil rather than an empty zero-value so the manifest endpoint can distinguish "no UI" (omit the field) from "UI with empty lists".

type Route

type Route struct {
	Method string `json:"method"`
	Path   string `json:"path"`
}

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.

const (
	ScopePlatform Scope = "platform"
	ScopePublic   Scope = "public"
	ScopeInternal Scope = "internal"
)

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

func (s Scope) IsValid() bool

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL