Documentation
¶
Index ¶
- Variables
- func DowngradeHandler(sqlFS fs.FS, scope migration.Scope, runTx migration.TxRunner) http.HandlerFunc
- func Health(w http.ResponseWriter, r *http.Request)
- func InstallHandler(sqlFS fs.FS, scope migration.Scope, runTx migration.TxRunner, ...) http.HandlerFunc
- func MCPResourcesListHandler(reg *registry.Registry) http.HandlerFunc
- func MCPResourcesReadHandler(reg *registry.Registry) http.HandlerFunc
- func MCPResourcesReadHandlerWithFailureLogger(reg *registry.Registry, failureLogger MCPFailureLogger) http.HandlerFunc
- func MCPToolsCallHandler(reg *registry.Registry) http.HandlerFunc
- func MCPToolsCallHandlerWithFailureLogger(reg *registry.Registry, failureLogger MCPFailureLogger) http.HandlerFunc
- func MCPToolsListHandler(reg *registry.Registry) http.HandlerFunc
- func ManifestHandler(id, slug, name, icon string, tags []string, sqlFS fs.FS, ...) http.HandlerFunc
- func ManifestHandlerWithClient(id, slug, name, icon string, tags []string, sqlFS fs.FS, ...) http.HandlerFunc
- func SeedHandler(acquire SeedConnAcquirer) http.HandlerFunc
- func UninstallHandler() http.HandlerFunc
- func UpgradeHandler(sqlFS fs.FS, scope migration.Scope, runTx migration.TxRunner, ...) http.HandlerFunc
- func WebHandler(rootDir string) http.HandlerFunc
- type ClientSpec
- type InstallCredential
- type InstallRequest
- type LifecycleError
- type LifecycleResult
- type MCPFailureLogger
- type MCPResourceEntry
- type MCPToolEntry
- type ManifestDefaults
- type ManifestDocument
- type ManifestEvents
- type ManifestExposes
- type ManifestMCP
- type ManifestPayload
- type ManifestResources
- type MigrationVersions
- type Provisioner
- type SeedConn
- type SeedConnAcquirer
- type SeedRequest
- type SeedResponse
- type UninstallResult
- type UpgradeRequest
- type VersionRequest
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidArgs = errors.New("mcp: invalid args")
ErrInvalidArgs signals a 400-class error from an MCP tool handler (bad input shape). Other errors are treated as 500.
Functions ¶
func DowngradeHandler ¶
func DowngradeHandler(sqlFS fs.FS, scope migration.Scope, runTx migration.TxRunner) http.HandlerFunc
DowngradeHandler reverts migrations between (req.To, req.From] in newest-first order. Each migration must have a .down.sql or the request fails before any SQL runs. Both fields must be migration numbers (see UpgradeHandler). Schema + Credential in the body behave exactly as in UpgradeHandler.
func Health ¶
func Health(w http.ResponseWriter, r *http.Request)
Health handles GET /__mirrorstack/health.
func InstallHandler ¶
func InstallHandler(sqlFS fs.FS, scope migration.Scope, runTx migration.TxRunner, provision Provisioner) http.HandlerFunc
InstallHandler returns an http.HandlerFunc that runs provision (when non-nil), then applies all migrations from sqlFS in numeric ascending order.
The SDK does NOT track which migrations were previously applied — it is a stateless executor. The platform decides when install is appropriate (a fresh app, or first deploy of a module) based on its own state store and calls install at most once per (scope, target). Calling install twice will re-run every migration and likely fail with "relation already exists" — platform-side saga logic is responsible for preventing that.
func MCPResourcesListHandler ¶
func MCPResourcesListHandler(reg *registry.Registry) http.HandlerFunc
MCPResourcesListHandler returns the handler for GET /__mirrorstack/mcp/resources/list.
func MCPResourcesReadHandler ¶
func MCPResourcesReadHandler(reg *registry.Registry) http.HandlerFunc
MCPResourcesReadHandler returns the handler for GET /__mirrorstack/mcp/resources/read?name=..., logging unexpected handler failures through the process default logger.
func MCPResourcesReadHandlerWithFailureLogger ¶
func MCPResourcesReadHandlerWithFailureLogger(reg *registry.Registry, failureLogger MCPFailureLogger) http.HandlerFunc
MCPResourcesReadHandlerWithFailureLogger is MCPResourcesReadHandler with a callback for unexpected handler failures. The callback receives the request context so the SDK runtime can attach trusted correlation fields to its log.
func MCPToolsCallHandler ¶
func MCPToolsCallHandler(reg *registry.Registry) http.HandlerFunc
MCPToolsCallHandler invokes a registered tool by name with the given args, logging unexpected handler failures through the process default logger. Body: {"name": "...", "args": {...}}. Returns {"result": ...} on success. 404 for unknown name, 400 for invalid body or args that fail the tool's handler, 500 for handler errors.
func MCPToolsCallHandlerWithFailureLogger ¶
func MCPToolsCallHandlerWithFailureLogger(reg *registry.Registry, failureLogger MCPFailureLogger) http.HandlerFunc
MCPToolsCallHandlerWithFailureLogger is MCPToolsCallHandler with a callback for unexpected handler failures. The callback receives the request context so the SDK runtime can attach trusted correlation fields to its log.
func MCPToolsListHandler ¶
func MCPToolsListHandler(reg *registry.Registry) http.HandlerFunc
MCPToolsListHandler returns the handler for GET /__mirrorstack/mcp/tools/list. Emits the full tool surface (name, description, input/output schemas) so the platform's aggregated MCP server can route agent calls without live-listing per module. The list mirrors ManifestPayload.MCP.Tools — either suffices.
func ManifestHandler ¶
func ManifestHandler(id, slug, name, icon string, tags []string, sqlFS fs.FS, versions map[string]MigrationVersions, reg *registry.Registry, contribReg *contributions.Registry) http.HandlerFunc
ManifestHandler returns an http.HandlerFunc that serves the module manifest. The migration versions are read from sqlFS at request time so a hot-reloaded build picks up new migrations without a restart. sqlFS may be nil — both migration fields will be empty.
versions is the module's declared semver→migration map. A nil map is normalized to an empty object so the JSON output is always `"versions":{}` instead of `"versions":null` — the handler owns the output contract and normalizes here the same way Registry normalizes Routes/Emits/Subscribes/ Schedules at their getters.
contribReg is the module's contribution-slot registry. Pass nil to omit declared contributions from the manifest entirely (e.g. tests).
func ManifestHandlerWithClient ¶
func ManifestHandlerWithClient(id, slug, name, icon string, tags []string, sqlFS fs.FS, versions map[string]MigrationVersions, reg *registry.Registry, contribReg *contributions.Registry, client *ClientSpec) http.HandlerFunc
ManifestHandlerWithClient is the client-aware manifest entry used by the SDK's module wiring. ManifestHandler remains unchanged for source compatibility with callers that construct a manifest handler directly.
func SeedHandler ¶
func SeedHandler(acquire SeedConnAcquirer) http.HandlerFunc
SeedHandler returns an http.HandlerFunc for the dev-mount seed endpoint: api-platform's devseed.Seeder POSTs one Postgres COPY-text chunk per call, targeting either the module's own table or an exposure-anchored producer dependency table (see package devseed on the platform side for the full design). This is the only lifecycle verb scoped to "app" alone — there is no module-scope seed, so the caller mounts it only under that scope's route (see internal/core/module.go's mountSystemRoutes).
acquire resolves the connection for each request; it is responsible for putting ctx's app schema on search_path (via db.WithSchema + a scoped acquire, e.g. Module.seedConn) — SeedHandler itself only derives the schema NAME from RequestBody.AppID and injects it into ctx.
First=true chunks run the create-then-if-empty sequence: CreateSQL (when non-empty) materializes a table the dev DB never migrated — a dependency table belonging to a producer module — then an if-empty check on Table decides whether to proceed. A non-empty table means the developer already has local data for it: the SDK answers {skipped:true} and does NOT touch the table, so a developer's local writes are never clobbered by a re-seed. First=false chunks (a continuation for a table already accepted on its first chunk) skip both steps and append unconditionally — the seeder stops sending further chunks the moment it sees {skipped:true} on the first one.
Table and every entry of Columns are quoted via pgx.Identifier.Sanitize before use in any SQL. The platform derives them from its own pg_catalog read (see devseed.Seeder.columns), but the SDK quotes defensively rather than trust the wire.
func UninstallHandler ¶
func UninstallHandler() http.HandlerFunc
UninstallHandler is a no-op that returns success. The platform handles soft-delete and retention separately — modules are not asked to drop their own data, because that would prevent restore.
func UpgradeHandler ¶
func UpgradeHandler(sqlFS fs.FS, scope migration.Scope, runTx migration.TxRunner, provision Provisioner) http.HandlerFunc
UpgradeHandler applies the migrations strictly between (req.From, req.To]. Both fields must be migration numbers; semver must be translated by the platform before calling this endpoint (the platform reads the Versions map from the manifest and uses the scope-matching field of MigrationVersions).
When the body also carries Schema + Credential (the platform's non-tunnel path), the migrations run under that per-(app, module) credential with search_path = schema — identical to InstallHandler. A bare {from, to} body keeps the dev-tunnel env-pool behavior.
provision runs here as well as on install, and BEFORE the (From, To] window is even resolved: upgrade is the only lifecycle call an already-installed app ever receives, so it is the only place SDK-owned per-app state can converge for an install that predates it. An empty window must still provision.
func WebHandler ¶
func WebHandler(rootDir string) http.HandlerFunc
WebHandler serves the module's React bundle output (the directory the module author named in Config.WebDir, typically "web/dist") under /__mirrorstack/web/*. The platform's catch-all module page fetches the bundle from here and dynamically imports the named exports declared in ms.RegisterUI(DefaultPages).
CORS is intentionally permissive: bundles are public static assets, browser-fetched from a different origin (web-applications on :3001 vs. the module on :8080 in local dev), and carry no credentials. The /__mirrorstack/web/* namespace is read-only — no cookie/auth flows.
If rootDir is empty, every request 404s — the module declared no web surface.
Path traversal is blocked by os.Root, which rejects paths and symlinks that escape rootDir while opening the file.
Types ¶
type ClientSpec ¶
ClientSpec identifies the module-local client project and its compiled output. Both paths are canonical, slash-separated paths relative to their containing project: Dir is relative to the module root and OutputDir is relative to Dir. Module construction validates this contract.
type InstallCredential ¶
InstallCredential is the per-install Postgres credential delivered in the install request body. Only Username + Token live here — those are the per-(app, module) values only the platform knows (minted when the per-module role was created).
Host/Port/Database deliberately do NOT live here. They come from the module's own environment (db.EnvBaseCredential — backed by MS_LOCAL_DB_URL / DATABASE_URL in dev, AWS Secrets Manager in production) so the platform doesn't leak its DB location to every install payload.
type InstallRequest ¶
type InstallRequest struct {
AppID string `json:"appId,omitempty"`
Schema string `json:"schema,omitempty"`
Credential *InstallCredential `json:"credential,omitempty"`
}
InstallRequest is the optional body for the install endpoint. The platform populates Credential + Schema so the SDK runs migrations under the per-(app, module) role provisioned at install time; AppID is metadata for module-side logging/auditing.
All fields are optional — an empty body falls back to the dev path (DATABASE_URL pool, default schema), matching the pre-B2b behavior that `mirrorstack dev`'s migration auto-apply relies on.
type LifecycleError ¶
type LifecycleError struct {
LifecycleResult
Error string `json:"error"`
}
LifecycleError is the JSON shape for partial-failure responses: it carries whatever the runner managed to apply/revert before the failure plus the error message. The platform uses this to update its own state with the versions that actually ran before retrying.
type LifecycleResult ¶
type LifecycleResult struct {
Applied []string `json:"applied,omitempty"`
Reverted []string `json:"reverted,omitempty"`
}
LifecycleResult is the JSON response for install/upgrade/downgrade. Install and upgrade return Applied; downgrade returns Reverted.
type MCPFailureLogger ¶
MCPFailureLogger records an unexpected tool or resource failure. The SDK always keeps the underlying error out of the response; a caller may provide this hook to attach its request-scoped correlation fields to the log.
type MCPResourceEntry ¶
type MCPResourceEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Schema json.RawMessage `json:"schema,omitempty"`
}
MCPResourceEntry is the JSON wire shape for a resource.
type MCPToolEntry ¶
type MCPToolEntry struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
// Permission mirrors MCPToolDecl.Permission: the slug-qualified module
// permission required to see and call the tool; absent when none declared.
Permission string `json:"permission,omitempty"`
// Annotations is the MCP spec's per-tool hint object (title, readOnlyHint,
// destructiveHint, idempotentHint, openWorldHint). The platform stores it
// whole and the agent renders the actionable hints into the tool
// description the model reads.
Annotations json.RawMessage `json:"annotations,omitempty"`
}
MCPToolEntry is the JSON wire shape for a tool in list responses and the manifest. Omits the Handler field from registry.MCPToolDecl.
type ManifestDefaults ¶
type ManifestDefaults struct {
Name string `json:"name"`
Icon string `json:"icon"`
Tags []string `json:"tags,omitempty"`
NameLabels map[string]string `json:"nameLabels,omitempty"`
TagLabels map[string][]string `json:"tagLabels,omitempty"`
}
ManifestDefaults is the default display name, icon, and tags. The platform may override name/icon per-app installation; tags are module-level badges.
NameLabels carries per-locale display names (resolved from the module's i18n catalog key "module.name"); empty when the module declared none, in which case the platform falls back to Name.
TagLabels carries per-locale localized tag LISTS (locale → []tag), the list counterpart of NameLabels. It is resolved from the module's i18n catalog key "module.tags", whose per-locale value packs the tag list into a single comma-separated string (e.g. en-US "Auth, Payments", zh-TW "驗證, 付款"); the manifest splits on "," and space-trims each tag. Empty when the module declared no "module.tags" key, in which case the platform falls back to the raw Tags list. Tags stays the default/fallback exactly as Name does.
type ManifestDocument ¶
ManifestDocument is one canonical manifest build. Body is the exact byte sequence served by the manifest HTTP endpoint, including its trailing newline. SHA256 is lowercase hexadecimal over those same bytes.
Callers must treat Body as immutable. The release-manifest tool base64 encodes it so a non-Go caller can recover and verify the byte sequence without reserializing the JSON.
func BuildManifest ¶
func BuildManifest(id, slug, name, icon string, tags []string, sqlFS fs.FS, versions map[string]MigrationVersions, reg *registry.Registry, contribReg *contributions.Registry, client *ClientSpec) (ManifestDocument, error)
BuildManifest builds the exact document used by both the served manifest endpoint and the SDK's release-manifest tool mode. Keeping construction, encoding, and hashing here prevents the two release inputs from drifting.
The encoding deliberately matches httputil.JSON: encoding/json's default HTML escaping and one trailing newline. Migration discovery errors remain non-fatal, matching the long-standing HTTP behavior; diagnostics are sanitized so an os.DirFS path is never disclosed.
type ManifestEvents ¶
type ManifestEvents struct {
Emits []string `json:"emits"`
Subscribes map[string]string `json:"subscribes"`
}
ManifestEvents declares which events the module emits and which it subscribes to.
type ManifestExposes ¶
type ManifestExposes struct {
Tables []string `json:"tables"`
}
ManifestExposes declares the read-only (SELECT) surface this module opens to depending modules. Tables is a flat, sorted, de-duplicated list of table NAMES (ms.ExposeTable) in the module's `mod_<id>` schema. There is no per-consumer "readableBy" list: the producer only marks a table SELECT-eligible; the app owner decides WHO reads it by approving a dependency. v1 is TABLES ONLY.
type ManifestMCP ¶
type ManifestMCP struct {
Tools []MCPToolEntry `json:"tools"`
Resources []MCPResourceEntry `json:"resources"`
}
ManifestMCP declares the MCP tool and resource surface of the module. The platform catalog ingests this at publish time so an aggregated MCP server can route agent tool calls without live-listing per module. Handlers are stripped — only name, description, and schemas appear on the wire.
type ManifestPayload ¶
type ManifestPayload struct {
ID string `json:"id"`
// Slug is the catalog handle (e.g. "oauth"). Empty for dev/legacy
// modules that haven't been assigned a slug yet — the platform falls
// back to ID-based addressing in that case.
Slug string `json:"slug,omitempty"`
Defaults ManifestDefaults `json:"defaults"`
Description string `json:"description,omitempty"`
// DescriptionLabels carries per-locale description display strings (locale →
// text), resolved from the module's i18n catalog (ms.Config.DescriptionLabel).
// Omitted when the module declared none, in which case the platform falls
// back to Description. Mirrors Permission.Descriptions / MetricDecl.Labels.
DescriptionLabels map[string]string `json:"descriptionLabels,omitempty"`
// Readme is the module's long-form README as a locale map ("default" =
// README.md, "<tag>" = README.<tag>.md), the same shape
// `mirrorstack module deploy` records on a published version.
//
// 🔴 IT IS HERE SO A DEV-MOUNTED MODULE CAN SHOW ITS CURRENT README. The
// recorded map belongs to a VERSION, so a module served from a dev tunnel
// otherwise shows whatever its last release captured — stale text, and in
// the wrong language when the localized README arrived after that release.
// The console already prefers the live manifest for the description; this
// gives the README the same source of truth.
Readme map[string]string `json:"readme,omitempty"`
// Client declares the module-local source and build-output directories for
// the optional custom-app client. The CLI consumes these paths during local
// development; package identity and versioning remain platform-owned.
Client *ClientSpec `json:"client,omitempty"`
Dependencies []registry.Dependency `json:"dependencies"`
Migration MigrationVersions `json:"migration"`
Versions map[string]MigrationVersions `json:"versions"`
Routes map[registry.Scope][]registry.Route `json:"routes"`
Events ManifestEvents `json:"events"`
// Exposes lists the tables this module marks readable (SELECT-eligible)
// by a depending module (ms.ExposeTable). The platform catalog issues
// GRANT SELECT against the depending module's DB role after the app
// owner approves the dependency. Always present; tables is an empty
// array when nothing is exposed.
Exposes ManifestExposes `json:"exposes"`
Schedules []registry.Schedule `json:"schedules"`
Tasks []registry.Task `json:"tasks"`
Permissions []registry.Permission `json:"permissions"`
// Resources declares privileged runtime resources the module needs. The
// platform must not vend a resource merely because the SDK supports it.
Resources ManifestResources `json:"resources"`
// Metrics lists the usage metrics this module declares (ms.Meter). The
// platform populates its metric_definitions catalog (kind/unit/price) from
// this at install/publish, so the catalog is authoritative before any usage
// event arrives. Omitted when the module declares no metrics.
Metrics []registry.MetricDecl `json:"metrics,omitempty"`
// AbsorbInfra is ms.AbsorbInfra(): pass EVERY platform infra metric through
// to the app owner at 0. A BOOLEAN, deliberately — the platform expands it
// against its own catalog, so no list of infra metric names travels in a
// manifest to go stale. Omitted when the module did not declare it.
AbsorbInfra bool `json:"absorbInfra,omitempty"`
MCP ManifestMCP `json:"mcp"`
// UI is the module's declared UI surface (RegisterUI). Nil/absent when
// the module ships no UI — callers must nil-check before reading.
UI *registry.ModuleUI `json:"ui,omitempty"`
// Provides lists the extension slots this module declares for
// others to contribute to (ms.Provide). The catalog reads this to know
// what other modules can plug into. Always present; empty array
// when no slots are declared.
Provides []contributions.SlotInfo `json:"provides"`
// ContributesTo lists the host slots this module pushes INTO
// (ms.ContributesTo) — the contributor side. The catalog (CLI in dev)
// validates each against the host's provides and performs
// the registration after app-owner approval. Always present; empty
// array when the module contributes nothing.
ContributesTo []registry.OutboundContribution `json:"contributesTo"`
}
ManifestPayload is the JSON shape returned by GET /__mirrorstack/platform/manifest. The platform reads this on deploy to discover module identity, capabilities, migration versions, and the semver→migration mapping it needs to translate lifecycle calls.
type ManifestResources ¶
type ManifestResources struct {
Storage bool `json:"storage,omitempty"`
}
ManifestResources is the module's opt-in runtime-resource contract. Empty is encoded as {} so old modules remain explicitly resource-free.
type MigrationVersions ¶
MigrationVersions is the per-scope migration number set. Used both for the current bundled version (ManifestPayload.Migration) and for each entry in the semver→migration map (ManifestPayload.Versions).
Module is omitempty so modules that don't use the cross-app shared schema (the vast majority) don't see the field in the wire shape. App is always present so consumers can rely on its existence.
type Provisioner ¶
Provisioner is SDK-owned per-app setup the install and upgrade handlers run BEFORE the author's migrations, inside the already-injected lifecycle context (schema + credential). Nil means this scope has nothing to provision.
It exists because some per-app state belongs to the SDK rather than to the module author's sql/app set — today the contributions store — and this is the only window on the deployed plane where the SDK holds an app schema together with a credential allowed to create tables in it. A failure fails the lifecycle call: reporting install success over a module whose declared contribution slots have no store is the silent half-install this hook exists to remove.
type SeedConn ¶
SeedConn is the connection seam SeedHandler needs: the db.Querier surface for CreateSQL and the if-empty guard, plus the raw driver connection COPY FROM STDIN requires (db.Querier exposes neither PgConn() nor the low-level copy protocol). *pgxpool.Conn satisfies this directly — its Conn() method returns the *pgx.Conn that PgConn() rides on.
type SeedConnAcquirer ¶
SeedConnAcquirer resolves a SeedConn scoped to ctx's app schema for one request. Implementations must NOT route this through a cross-module dev guard the way Module.DB/Tx do: the seed endpoint legitimately writes into another module's exposure-anchored dependency tables, a grant the platform already enforced before it ever sent the chunk.
type SeedRequest ¶
type SeedRequest struct {
AppID string `json:"appId"`
Table string `json:"table"` // physical m<uuid-hex>_* relation name
Columns []string `json:"columns"` // explicit COPY column list, ordinal order
CreateSQL string `json:"createSql,omitempty"`
Data string `json:"data"` // Postgres COPY text rows
First bool `json:"first"` // first chunk for this table
}
SeedRequest is the platform->SDK wire shape for the dev-mount seed endpoint (devseed.Seeder on the api-platform side — a cross-repo contract; keep field tags byte-for-byte in sync with that package's seedRequest). One POST carries one Postgres COPY-text chunk for one table.
type SeedResponse ¶
type SeedResponse struct {
Skipped bool `json:"skipped,omitempty"`
}
SeedResponse is the SDK's per-chunk answer.
type UninstallResult ¶
type UninstallResult struct {
Status string `json:"status"`
}
UninstallResult is the response for uninstall.
type UpgradeRequest ¶
type UpgradeRequest struct {
VersionRequest
InstallRequest
}
UpgradeRequest is the full body shape for upgrade and downgrade: the migration window plus the same optional install context the platform sends on install (appId, schema, credential). It mirrors api-platform's upgradeRequest verbatim — the wire shape is the contract.
A bare {from, to} body (the dev-tunnel path, where the module runs against its own env DB) keeps the pre-existing behavior: no schema or credential is injected and migrations run on the SDK's DATABASE_URL pool. When the platform drives a DEPLOYED (non-tunnel) upgrade it populates Schema + Credential so the (from, to] migrations run under the per-(app, module) r_* role against the app schema — exactly like install.
type VersionRequest ¶
VersionRequest is the migration window for upgrade and downgrade. Both fields MUST be numeric migration numbers ("0008"), not semver strings ("v0.1.0"). The platform does semver→migration translation before calling the SDK, using the Versions map exposed via the manifest endpoint.