system

package
v0.4.20 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

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

type ClientSpec struct {
	Dir       string `json:"dir"`
	OutputDir string `json:"outputDir"`
}

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

type InstallCredential struct {
	Username string `json:"username"`
	Token    string `json:"token"`
}

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

type MCPFailureLogger func(ctx context.Context, kind, name string, err error)

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

type ManifestDocument struct {
	Body   []byte
	SHA256 string
}

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

type MigrationVersions struct {
	App    string `json:"app"`
	Module string `json:"module,omitempty"`
}

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

type Provisioner func(ctx context.Context) error

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

type SeedConn interface {
	db.Querier
	Conn() *pgx.Conn
}

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

type SeedConnAcquirer func(ctx context.Context) (SeedConn, func(), error)

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

type VersionRequest struct {
	From string `json:"from"`
	To   string `json:"to"`
}

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.

Jump to

Keyboard shortcuts

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