149 lines
8.0 KiB
Markdown
149 lines
8.0 KiB
Markdown
# Plugin Manifest Reference
|
|
|
|
> The plugin manifest is the contract between an installable WASM
|
|
> binary and the host runtime. The host parses the manifest with the
|
|
> Zod schema in `packages/plugin-runtime/src/manifest.ts` and rejects
|
|
> anything that does not validate — adding a field here without
|
|
> updating the schema does nothing.
|
|
|
|
The manifest may be TOML or JSON (whichever your toolchain emits). The
|
|
runtime works against the parsed object, not the source bytes — except
|
|
during signing, where the canonical form is the sorted-keys JSON
|
|
projection (see `cli-reference.md#sign`).
|
|
|
|
## Top-level fields
|
|
|
|
| Field | Type | Required | Notes |
|
|
| --------------- | --------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `schemaVersion` | `1` | yes | Bumped on breaking schema changes. Host rejects unknown versions. |
|
|
| `id` | reverse-DNS string | yes | Pattern `^[a-z]+(\.[a-z0-9-]+){1,}$`. Globally unique across hosts. |
|
|
| `version` | semver | yes | `MAJOR.MINOR.PATCH[-prerelease]`. Host enforces SemVer for upgrades. |
|
|
| `name` | string ≤ 256 chars | yes | Display name. |
|
|
| `description` | string ≤ 2048 chars | no | Free-form for the marketplace. |
|
|
| `entry` | string ≤ 256 chars | yes | Path of the wasm relative to the manifest, e.g. `plugin.wasm`. |
|
|
| `wasmSha256` | 64-char hex | yes | SHA-256 of the binary at signing time. Re-checked on every load. |
|
|
| `signature` | 128-char hex | yes | Ed25519 signature over `canonicalManifest:wasmSha256`. |
|
|
| `signedBy` | 64-char hex | yes | Raw 32-byte ed25519 public key (hex). Registered with operator. |
|
|
| `allowedHosts` | array of public hostnames | no | Outbound HTTP allowlist for `http.outbound`. Empty disables HTTP. No scheme, port, localhost, IP literal, `.local`, or `.internal`. |
|
|
| `capabilities` | array of capability entries | no | Capability declarations. See below. |
|
|
| `hooks` | array of hook entries (≥1) | yes | At least one hook must be declared; a zero-hook plugin never runs. |
|
|
| `ui.settings.entry` | relative bundle path | no | Optional settings HTML entry rendered by the host in a sandboxed `PluginSettingsFrame`. Absolute paths, URL schemes, and `..` segments are rejected. When present, the settings entry must also declare `ui.settings.sha256` or `ui.settings.integrity` so the host can verify the bundled HTML before rendering it. |
|
|
|
|
Settings HTML communicates with the host through the versioned
|
|
`plugin.settings.*` `postMessage` protocol documented in
|
|
[`sdk-reference.md#settings-iframe-protocol`](sdk-reference.md#settings-iframe-protocol).
|
|
|
|
## Capability entries
|
|
|
|
```toml
|
|
[[capabilities]]
|
|
name = "http.outbound"
|
|
scope = { allowedHosts = ["hooks.slack.com"] }
|
|
|
|
[[capabilities]]
|
|
name = "secret.read"
|
|
scope = { keyPattern = "slack-webhook-.*" }
|
|
```
|
|
|
|
Each entry has:
|
|
|
|
- `name` — capability name from the closed enum (see
|
|
`capability-matrix.md`).
|
|
- `scope` — optional, capability-specific narrowing. Permitted shape:
|
|
|
|
| Field | Allowed for | Meaning |
|
|
| -------------- | --------------------- | --------------------------------------------- |
|
|
| `allowedHosts` | `http.outbound` | Hostname allowlist (no scheme, no port) |
|
|
| `keyPattern` | `secret.read` | Regex (1-128 chars) the key must match |
|
|
| `tablePrefix` | `db.read`, `db.write` | Required prefix; pattern `^[a-z][a-z0-9_]*_$` |
|
|
|
|
Other keys are rejected by `.strict()`.
|
|
|
|
## Hook entries
|
|
|
|
```toml
|
|
[[hooks]]
|
|
name = "entity.created"
|
|
export = "handleEntityCreated"
|
|
```
|
|
|
|
- `name` — host hook event name; must match an entry the host emits.
|
|
- `export` — function name exported from the wasm binary that handles
|
|
the hook.
|
|
|
|
Both fields cap at 128 characters.
|
|
|
|
### Foundation Hook Payloads
|
|
|
|
The SDK exports matching payload types from `@toolctrl/plugin-sdk`
|
|
(`TypedHookHandler<"entity.updated">`, `PluginHookWorkflowExecutedPayload`,
|
|
and friends). Runtime validation is strict, so handlers should rely on
|
|
these field names instead of ad-hoc shapes.
|
|
|
|
| Hook | Payload summary |
|
|
| --- | --- |
|
|
| `tenant.created` | `tenantId`, `slug`, optional `ownerEmail`, optional `plan`, `createdAt` |
|
|
| `user.created` | `userId`, optional nullable `tenantId`, `email`, optional `locale`, `createdAt` |
|
|
| `entity.created` / `entity.updated` / `entity.deleted` | `entityType`, `entityId`, nullable `tenantId`, optional `userId`, optional `changedFields`, `occurredAt` |
|
|
| `workflow.created` / `workflow.updated` / `workflow.deleted` | nullable `tenantId`, `workflowId`, nullable `userId`, `status`, `appliesToAllTenants`, optional `changedFields`, `occurredAt`; workflow blocks, credentials, and raw input are intentionally not included. |
|
|
| `workflow.executed` | `tenantId`, `workflowId`, `workflowExecutionId`, optional nullable `userId`, `type`, `status`, `durationMs`, `errorCode` (`null` for `success`, required for `error`), `appliesToAllTenants`, `occurredAt`; raw execution input, output, and error bodies are intentionally not included. |
|
|
| `workflow.failed` | `tenantId`, `workflowId`, `workflowExecutionId`, optional nullable `userId`, `type`, fixed `status: "error"`, required `errorCode`, `durationMs`, `appliesToAllTenants`, `occurredAt`; raw execution input, output, and error bodies are intentionally not included. |
|
|
|
|
## Strictness
|
|
|
|
The schema uses Zod's `.strict()` everywhere — unknown fields cause
|
|
parse failure. This is intentional: extending the manifest is a
|
|
deliberate, host-side operation, never a plugin-author hack.
|
|
|
|
## Canonical bytes (for signing)
|
|
|
|
The CLI and runtime verifier both sign and verify
|
|
`<canonicalManifestJSON>:<wasmSha256>` where the canonical form is the manifest
|
|
minus `signature`, `signedBy`, and `wasmSha256`, projected to JSON with deeply
|
|
sorted keys (see `canonicalPluginSignaturePayload` in
|
|
`packages/plugin-runtime/src/signature.ts`).
|
|
|
|
This means adding / removing capabilities, hooks, or any other field
|
|
invalidates the signature — re-signing is required for every authoring
|
|
change.
|
|
|
|
## Example (TOML)
|
|
|
|
```toml
|
|
schemaVersion = 1
|
|
id = "de.acme.slack-notifier"
|
|
version = "1.2.0"
|
|
name = "Slack Notifier"
|
|
description = "Sends tenant events to a Slack channel."
|
|
entry = "plugin.wasm"
|
|
wasmSha256 = "a1b2..."
|
|
signature = "c3d4..."
|
|
signedBy = "e5f6..."
|
|
allowedHosts = ["hooks.slack.com"]
|
|
|
|
[[capabilities]]
|
|
name = "log"
|
|
|
|
[[capabilities]]
|
|
name = "secret.read"
|
|
scope = { keyPattern = "slack-webhook-.*" }
|
|
|
|
[[capabilities]]
|
|
name = "http.outbound"
|
|
scope = { allowedHosts = ["hooks.slack.com"] }
|
|
|
|
[[hooks]]
|
|
name = "entity.created"
|
|
export = "handleEntityCreated"
|
|
|
|
[[hooks]]
|
|
name = "entity.updated"
|
|
export = "handleEntityUpdated"
|
|
```
|
|
|
|
## Versioning + compatibility
|
|
|
|
Plugins may carry a `minHostVersion` / `maxHostVersion` constraint
|
|
(see `versioning.md`). Install rejects manifests whose declared host-version
|
|
range does not include the current host version.
|