> ## Documentation Index
> Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manifest reference

> Every field of plugin manifest.yaml, in dependency order.

The manifest is the entire contract between a plugin and the host.
Get it right and everything else (install, permission grants, slot
mounting, capability checks) falls into place automatically.

## File location

`manifest.yaml` lives at the root of the `.adosplug` archive. The host
opens the archive, reads the manifest first, and refuses to unpack
anything else if the manifest fails validation.

## Top-level shape

Identity and configuration live as flat top-level keys. There is no
`plugin:` wrapper.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
schema_version: 1     # 1 or 2
id: com.example.thermal
version: "1.0.0"
name: "Example Thermal"
description: "One sentence."
author: "Example Author"
homepage: "https://example.com/thermal"
license: "GPL-3.0-or-later"
risk: medium          # low | medium | high | critical

compatibility:        # required
agent:                # optional, agent half
gcs:                  # optional, GCS half
```

At least one of `agent` or `gcs` must be present. The host accepts
plugins that ship only one half; the first-party Battery Health Panel,
for example, is GCS-only.

| Field            | Type   | Notes                                                                                                         |
| ---------------- | ------ | ------------------------------------------------------------------------------------------------------------- |
| `schema_version` | int    | `1` baseline, `2` unlocks the v2 agent fields (`vendor_attribution`, `subprocess_spawn`, `per_drone_config`). |
| `id`             | string | Reverse-DNS, lowercase, dotted. Validated against the plugin-id pattern.                                      |
| `version`        | string | Semver.                                                                                                       |
| `name`           | string | Operator-facing display name.                                                                                 |
| `description`    | string | One short sentence.                                                                                           |
| `author`         | string | Free-form.                                                                                                    |
| `homepage`       | url    | Optional.                                                                                                     |
| `license`        | string | SPDX expression. `GPL-3.0-or-later` recommended.                                                              |
| `risk`           | enum   | `low`, `medium`, `high`, `critical`. Defaults to `medium`.                                                    |

The manifest also accepts optional install-dialog content fields the GCS
renders for a richer pre-install summary (`description_long`, `features`,
`hardware_requirements`, `resource_impact`, `required_fc_parameters`,
`telemetry_fields`, `documentation_url`, `screenshots`). The agent does
not interpret any of them.

## `compatibility`

| Field              | Notes                                                                                                                                                                                                            |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ados_version`     | Required. Semver range. The agent half compares against setup status or `/api/version`.                                                                                                                          |
| `gcs_version`      | Optional. Semver range matched against the running Mission Control build.                                                                                                                                        |
| `supported_boards` | Array of board ids, exact match. An empty list (the default) means any board passes; a non-empty list is checked by exact membership, so the supervisor refuses install or enable on a board id not in the list. |
| `min_tier`         | Optional integer 1 to 4. Minimum compute-class tier the plugin needs. The supervisor refuses install or enable on a board whose detected tier is below this; a board with an unknown tier is never blocked.      |

Node profiles are declared on the agent half (`agent.target_profiles`),
not here.

## `agent`

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
agent:
  entrypoint: "agent/plugin.py"
  isolation: "subprocess"        # subprocess (default) or inprocess (first-party)
  runtime: "python"              # python (default) or rust
  permissions:
    - id: event.publish
    - id: mavlink.read
  resources:
    max_ram_mb: 96               # default 96, range 8 to 4096
    max_cpu_percent: 25          # default 25, range 1 to 100
    max_pids: 12                 # default 12, range 1 to 256
  target_profiles: ["drone"]     # subset of drone, ground-station
  contributes:
    services: []                 # optional, plugin-declared extra systemd services
```

`entrypoint` is a relative path inside the archive (no absolute paths,
no `..`). For a file-path entrypoint like `agent/plugin.py` the runner
loads a class named `Plugin`. A `module:Class` entrypoint is also
accepted and is how built-in plugins declare themselves.

`isolation: subprocess` is the default and the only mode third-party
plugins may declare. `inprocess` is reserved for first-party plugins
that import into the supervisor. `runtime: rust` runs the entrypoint as
its own binary (rust always runs subprocess).

### Agent field reference

| Field                    | Type   | Default      | Notes                                                                                                                                                                                           |
| ------------------------ | ------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `entrypoint`             | string | required     | A `module:Class` id (built-in plugins) or a relative path to the half's entrypoint inside the archive. No `..`, no absolute path. For a Rust plugin this is the binary path, e.g. `bin/<name>`. |
| `isolation`              | enum   | `subprocess` | `subprocess` is the only mode third-party plugins may declare. `inprocess` is first-party-only and rejected for `runtime: rust`.                                                                |
| `runtime`                | enum   | `python`     | `python` runs the entrypoint under the shared Python runner; `rust` execs the plugin's own cross-compiled binary directly under systemd. Older manifests without the field load as `python`.    |
| `permissions`            | list   | `[]`         | Capability requests. See [permission shape](#permission-shape).                                                                                                                                 |
| `resources`              | object | see below    | Hard limits the supervisor enforces.                                                                                                                                                            |
| `contributes`            | object | `{}`         | Agent-side contributions: `services`, `drivers`, `vision`.                                                                                                                                      |
| `mavlink_components`     | list   | `[]`         | MAVLink components the plugin registers.                                                                                                                                                        |
| `target_profiles`        | list   | `["drone"]`  | Subset of `drone`, `ground-station`. Must list at least one.                                                                                                                                    |
| `contains_vendor_binary` | bool   | `false`      | Set true when the archive ships a pre-compiled vendor binary; requires `vendor_attribution`. Schema v2.                                                                                         |
| `vendor_attribution`     | list   | `[]`         | Source-offer records. Required (non-empty) when `contains_vendor_binary` is true. Schema v2.                                                                                                    |
| `subprocess_spawn`       | list   | absent       | Allowlist of binary basenames the plugin may exec via `ctx.process.spawn`. Requires the `process.spawn` permission. Schema v2.                                                                  |
| `per_drone_config`       | bool   | `false`      | Run one process instance per connected drone with a distinct `ctx.agent_id` and a per-drone config file. Schema v2.                                                                             |
| `test_fixtures`          | map    | `{}`         | Map of friendly name to fixture YAML path, replayed by the SDK test harness.                                                                                                                    |

#### `agent.resources`

| Field             | Default | Range     |
| ----------------- | ------- | --------- |
| `max_ram_mb`      | `96`    | 8 to 4096 |
| `max_cpu_percent` | `25`    | 1 to 100  |
| `max_pids`        | `12`    | 1 to 256  |

#### `agent.mavlink_components`

Each entry registers a MAVLink component so the router claims its id and
answers component-info on the plugin's behalf.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
mavlink_components:
  - component_id: 100        # 0 to 255
    component_kind: camera   # camera | gimbal | payload | peripheral | generic | vio
    sub_id: 1                # optional, 0 to 255
```

#### Permission shape

A permission entry is either a bare string (which means required) or an
object with `id`, `required`, and an optional `degraded_behavior`:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
permissions:
  - id: mavlink.read                 # required (bare string also works: "mavlink.read")
  - id: hardware.i2c
    required: false                  # optional; the plugin degrades gracefully without it
    degraded_behavior: "Falls back to MAVLink-only telemetry"
```

Unknown permission ids load with a warning rather than a hard failure,
so an experimental manifest still parses. See the
[Permissions reference](/developers/permissions).

#### Schema-2 agent fields

`contains_vendor_binary`, `vendor_attribution`, `subprocess_spawn`, and
`per_drone_config` are schema-2 fields. A v1 manifest gets the defaults.

* **`per_drone_config: true`** runs one process per connected drone, each
  with its own `ctx.agent_id` and config at
  `/var/lib/ados/plugins/<plugin_id>/config/<agent_id>.yaml`.
* **`subprocess_spawn`** is an allowlist of binary basenames the plugin
  may exec. The plugin host rejects any spawn whose basename is not on
  the list, and the manifest validator requires the `process.spawn`
  permission whenever the list is non-empty.
* **`contains_vendor_binary` plus `vendor_attribution`** travel together:
  the flag must be set when (and only when) at least one attribution
  record is present. Each `vendor_attribution` entry takes `license`
  (required) plus optional `name`, `source_url`, `source_offer_url`,
  `upstream_repo`, `upstream_version`, `commit_sha`, and `notice`. See
  [Vendor binaries](/developers/vendor-binaries).

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
agent:
  runtime: rust
  entrypoint: "bin/vision-nav"
  isolation: subprocess
  per_drone_config: true
  contains_vendor_binary: true
  vendor_attribution:
    - name: "OpenVINS"
      license: "GPL-3.0-only"
      source_url: "https://github.com/rpng/open_vins"
      upstream_version: "v2.7"
      notice: "Used by the VIO mode."
  permissions:
    - id: vision.frame.read
    - id: process.spawn
  subprocess_spawn:
    - ados_openvins_shim
```

## `gcs`

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
gcs:
  entrypoint: "gcs/plugin.bundle.js"
  isolation: "iframe"            # iframe (default), worker, or inline
  permissions:
    - id: ui.slot.fc-tab
    - id: telemetry.subscribe.battery
  contributes:
    panels:
      - id: battery-health-tab
        slot: fc.tab
        title: "Battery Health"
        icon: "battery"
        order: 30
    notifications:
      - id: battery-anomaly
        title: "Battery anomaly"
        severity: warning
  locales:
    - "en"
```

`contributes` carries `skills`, `tabs`, `parameters`, `settings`,
`panels`, `overlays`, `notifications`, `missionTemplates`, `mapOverlays`,
and `models`. The `slot` value in `contributes.panels[].slot` comes from
the slot taxonomy (`fc.tab`, `hardware.tab`, `settings.section`, and the
rest; see the [contribution points](/developers/contribution-points)
reference). Mounting in a slot also requires the matching `ui.slot.*`
permission listed under `gcs.permissions`. Locales are listed under
`gcs.locales`, a sibling of `contributes`.

`skills` declares flight Skills that surface in the cockpit Skill Bar
(toggle, hotkey or gamepad binding, activation through per-drone config,
read-back over an event topic). Each skill entry needs the
`ui.slot.flight-skill` permission. The agent does not interpret skill
entries; the GCS skill registry reads them.

| `contributes` key  | Mounts                                                                      |
| ------------------ | --------------------------------------------------------------------------- |
| `skills`           | A flight Skill in the cockpit Skill Bar (`flight.skill`).                   |
| `tabs`             | A node-detail tab on any node profile (`node.detail.tab`).                  |
| `parameters`       | Native schema-driven form fields (no UI slot of their own).                 |
| `settings`         | A native settings section (`settings.section`).                             |
| `panels`           | A tab or panel in a UI slot (`fc.tab`, `hardware.tab`, `settings.section`). |
| `overlays`         | A video overlay (`video.overlay`).                                          |
| `notifications`    | A notification channel (`notification.channel`).                            |
| `missionTemplates` | A mission template entry (`mission.template`).                              |
| `mapOverlays`      | A map overlay (`map.overlay`).                                              |
| `models`           | A vision model registration (no UI slot).                                   |

The full contribution reference, with the manifest shape of each kind and
where it renders, is on the
[contribution points](/developers/contribution-points) page.

## Signing and the archive

The `.adosplug` archive is a zip. There is no per-asset hash list in the
manifest and no `signing:` block. Instead the archive carries a separate
top-level `SIGNATURE` file with two lines: the signer key id, then the
base64 Ed25519 signature over a canonical payload hash (the SHA-256 of
the sorted `<path>\n<sha256-hex>\n` lines for every entry except
`SIGNATURE`). Because the hash covers every entry, any tampering after
signing fails verification.

Archives are size-bounded (50 MB per archive, 25 MB per entry) and
reject path-traversal entries (`..`, absolute paths, symlinks). A
manifest that fails validation raises a manifest error; a signature from
an unknown or revoked signer is rejected (see
[Signing keys](/developers/signing-keys)).

## See also

* [Permissions reference](/developers/permissions)
* [Lifecycle](/developers/lifecycle)
* [Signing keys](/developers/signing-keys)
