One file per plugin, three trust states, and permissions that are declared up front, shown before install and enforced on the plugin's process.
This is the engine's own example: the shape its loader documents and reads.
{
"id": "dev.example.jira-sync",
"name": "Jira sync",
"version": "0.4.0",
"apiVersion": ">=0.1",
"publisher": "example bv",
"description": "Blocks Deliver until the card names a ticket.",
"permissions": {
"network": ["your-org.atlassian.net"],
"cardWrite": true,
"filesystem": [],
"shell": false
},
"hooks": ["before:deliver"],
"points": ["stage.transition"],
"commands": ["dev.example.jira-sync:open-ticket"],
"stability": { "level": "unstable", "note": "Tested against Jira Cloud only." },
"activation": ["stage:deliver"],
"configuration": {
"site": { "type": "string", "title": "Jira site", "default": "your-org.atlassian.net" }
},
"main": "index.js"
}
| FIELD | RULES |
|---|---|
id |
Required. Reverse-domain or plain kebab-case: dot-separated segments of lowercase letters, digits and hyphens. grid-themes and dev.example.jira-sync both validate. It keys the registry and names a directory. |
name |
The display name. Defaults to the id. |
version |
Required. Shown, pinned into a bundle and signed. Not compared. |
apiVersion |
The plugin API range, checked against 0.2.0. Only >=x.y[.z] is parsed; any other form is ignored rather than refused. A plugin that needs a newer API is listed with the reason and not loaded. permissions.spawn (see below) is only read for a manifest that declares apiVersion: ">=0.2"; a manifest that names it under ">=0.1" gets no spawn contract at all. |
publisher |
Free text, self-declared. It earns a plugin no trust of its own; the signature is what decides that. |
description |
One line, shown on the row and in the catalogue. |
permissions |
An object with exactly the four keys below, plus a fifth, spawn, gated on apiVersion. An unknown key is a load error. Absent means nothing is granted. |
hooks |
Every name passed to hooks.on: before:<stage>, after:<stage> or on:attention. Anything else is a load error. |
points |
Every point passed to contribute, from the sixteen in plugins. Anything else is a load error. |
commands |
Every name passed to commands.register, written <id>:<name>. A command not namespaced to this plugin's id is a load error. |
stability |
{ "level", "note" }. stable, the default, or unstable, which requires a note. An unknown level is a load error. |
activation |
stage:<name>, view:<name> or command:<name>. Parsed and shown. Every enabled plugin activates at boot. |
configuration |
The settings block. Types and rules are in plugins. |
capabilities |
Optional, beside permissions. { isolation, errors }: a provider's own claim about whether two of its accounts can run on one machine, and a table mapping its own failure text onto the four words core acts on. Field by field, this is Adding a provider. |
main |
The entry point, relative to the plugin directory. Defaults to index.js. |
capabilities is the one place where an unknown key is not a load error. An isolation word or an errors class outside its vocabulary still refuses the manifest, because both are read and acting on a misreading is worse than refusing to load. A key this build does not know is logged once and ignored instead, so a manifest that declares a capability for a newer Grid keeps loading here, and a workspace whose only agent provider carries one keeps starting sessions.
The older shape still loads, with a deprecation line in the log: plugin.json as the file name, a grid app-version range instead of apiVersion, settings as [key, type, value] rows, and permissions as a string array. The array grants nothing, because those strings never had a vocabulary behind them.
Arrives with the public beta. Lazy activation on activation events. The field validates today so a manifest written now keeps working.
| KEY | GRANTS | ENFORCED BY |
|---|---|---|
network |
The hostnames the plugin may reach. A domain covers its subdomains. Empty means no network. | Wrappers around http, https, net, tls and fetch inside the plugin process. |
filesystem |
Directories it may read and write. Its own directory is always readable. | Node's permission flags on the process. |
cardWrite |
Whether ctx.amend may write sections into a card. |
The host, on its side of the boundary. |
shell |
Whether it may spawn processes for the plugin's own worker (--allow-child-process on the sandboxed process, present or absent). |
Node's process flags. |
spawn |
Behind apiVersion: ">=0.2" only. A bin, argv and, optionally, resumeArgv, forkArgv, env, sessionId, a transcript adapter, a hooks table and a trust word. Together they are the whole description of a program Grid runs OUTSIDE the plugin sandbox, as the operator, with every right the operator has. A different question from shell above, and not overlapped by it: shell fences the plugin's own worker process; spawn describes a program that never runs inside that fence at all. Field by field, this is Adding a provider. |
The spawn engine, the transcript adapter and the hook installer. Before the binary's first run, a person as well, at a resolved-path confirmation separate from install. |
The rights are printed before anything is installed: grid plugin install lists them line by line, and the catalogue row shows them before a byte is fetched. Re-adding a directory over an id that is already recorded, or installing a .gridplugin update over one, is an update, and the host compares the new manifest's grants against the old ones. Installing an updated .gridplugin that widens any of the five keys is refused until the install is retried with the widening acknowledged. spawn is included, where ANY change to the block counts as a widening, let alone a bigger one. grid plugin add on a plain directory only reports that it widened, for the CLI to act on. No version bump is required for a permission change either way. Read the line.
Trust is provenance: where the loader believes the code came from, decided by signature and re-derived on every boot from the bytes on disk, never read back from a record.
| STATE | MEANING |
|---|---|
| Official | Shipped with the app, or signed by the Grid Console publisher key that ships inside it. Silent on the row. |
| Verified | Signed by a key this workspace has decided to believe, with grid plugin trust <fingerprint> or the Trust publisher button. |
| Unverified | No valid signature, or a valid signature from a key nobody here has decided about. Blocked until you turn on Allow unverified plugins in Settings › Plugins, which keeps a banner visible while it is on. |
A row also shows the signature's own state when it is not the ordinary one: BAD SIGNATURE when the recorded signature no longer covers the files on disk, NO SIGNATURE when the local registry names a signer with nothing to back it. The catalogue's trust pill is a claim: it matches the index's signedBy against your key lists before any artefact is fetched, so it renders as claimed, and the signature is checked when you install. The word community, a registry verdict meaning checked by automation and not read by a person, is reserved. Nothing produces it, because there is no reviewer behind it.
Stability is not trust. A plugin can be official, signed and in the box, and still be the least proven row on the page. codex-provider ships that way: unstable, with the manifest's note under an UNSTABLE chip in the pane and in the catalogue.
A plugin arrives one of two ways: from the registry, or from a file you were handed. Both go through the same check.
| WAY IN | WHAT IT IS |
|---|---|
| The registry | gridconsole.dev/registry/index.json, a static index of the nine first-party bundles, with one signed .gridplugin per row beside it. Settings › Plugins, Browse registry, fetches it when you press the button, from the app's renderer rather than the daemon, so the engine makes no registry call. The NOT INSTALLED column then lists what you do not have yet, with search, category chips and per row the trust pill, the UNSTABLE chip, the permission line and Install or Update. Nothing in it is third-party. A self-hosted Grid serves its own index from the same origin as its licence server. |
| A file | + Install from file takes a .gridplugin. grid plugin install <file.gridplugin> does the same from the command line. |
The check happens at install, before a byte is written:
| STEP | REFUSES WHEN |
|---|---|
| Format | The file is not a format 2 bundle. An older format is named, with grid plugin pack as the fix, rather than half-read. |
| Every file | A file's bytes do not match its own digest, a path appears twice, or a path would land outside the plugin directory. |
| Content hash | The file set does not hash to the bundle's hash. |
| Manifest digest | The envelope manifest, the one you read with cat, does not match manifestHash. |
| Signature | A signature is present and does not verify over the format, id, version, content hash and manifest digest together. |
A bundle that passes is unpacked under Grid's own plugin directory, in a folder named by its content hash. Its manifest is re-read from disk, and the record keeps the signature and public key so trust is re-derived on every boot. The install goes through the daemon, so the plugin is loaded by the time the button or the command returns.
A valid signature from a key you have not decided about reads UNKNOWN on the row, with Trust publisher beside it. grid plugin trust <fingerprint> is the same decision from the command line, --revoke reverses it, and both reload every plugin so the change applies now.
There is no update mechanism of its own. When the index carries a newer version than yours, the row offers Update, and that is an install: the new artefact is verified and unpacked beside the old one, and the record moves to it. Nothing checks for updates by itself, nothing can disable an installed copy from outside your machine, and there is no grid:// link, so a web page cannot open the app to install anything.
Arrives with the public beta. An entitlements API, so a paid plugin can check what the user bought, workspace-wide on Crew.
Arrives with Fleet, November 2026. Administrator policy: verified-only, allow and block lists, forced plugins, pinned versions, a mirrored private registry, and blocking unsigned plugins entirely. The only policy in this build is the Allow unverified plugins switch, per workspace.