Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,148 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user