> ## 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.

# Versioning and updates

> Semver discipline, compatibility ranges, additive vs breaking permission changes.

A plugin lives across many host versions and many of its own versions.
Semver gives operators a way to predict what an upgrade will and will not
change.

## Semver for plugins

Use `MAJOR.MINOR.PATCH`:

| Bump  | When                                                                                                                                       |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| PATCH | Bug fixes. No new features, no behavior changes operators would notice.                                                                    |
| MINOR | Additive features. New optional permissions are fine; new required permissions are not. New slot contributions, new patterns, new drivers. |
| MAJOR | Breaking changes. Removed features, renamed config fields, new required permissions, changed default behavior.                             |

The rule of thumb: an operator running an old config on a new MINOR
version should see a working plugin. A new MAJOR version warrants reading
the changelog.

## Compatibility ranges

The manifest declares which host versions it supports:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
compatibility:
  ados_version: ">=0.10.0,<1.0.0"
  gcs_version: ">=0.5.0,<1.0.0"
  supported_boards: []
```

The host parses each range as comma-separated atoms, where every atom is
an operator followed by a semver. The supported operators:

| Operator              | Meaning               |
| --------------------- | --------------------- |
| `=1.2.3` or `==1.2.3` | Exact match.          |
| `>=1.2.3`             | At least 1.2.3.       |
| `<=1.2.3`             | At most 1.2.3.        |
| `>1.2.3`              | Strictly above 1.2.3. |
| `<2.0.0`              | Strictly below 2.0.0. |

Combine atoms with a comma (logical AND): `">=0.10.0,<1.0.0"`. A bare
semver with no operator means exact match. The pre-release and build
suffixes are stripped before comparison, so `1.2.3-rc.1` compares as
`1.2.3`. Caret (`^`) and tilde (`~`) shorthand are not supported; write
the bounds out explicitly.

`supported_boards` is matched as an exact board-id list. The default is
an empty list, which means any board. To restrict the plugin, list the
exact board ids it runs on; there is no wildcard, so an empty list (or
omitting the field) is the way to mean "any board".

The compatibility check runs at install time. If the running agent
version is outside `ados_version`, the install (or the install of a newer
archive over an existing plugin) is rejected. A `gcs_version` range is
checked on the Mission Control side against the running build.

## `ados_version` vs `gcs_version`

`ados_version` matches the running ADOS Drone Agent version (reported by
setup status or `/api/version`). `gcs_version` matches the running
Mission Control build. They move independently, so a plugin can support a
wide agent range and a narrow GCS range or vice versa.

The `compatibility` block and its `ados_version` field are both required
on every manifest, even for a GCS-only plugin. Set `ados_version` to the
agent range the plugin needs (or a wide range if it does not depend on a
specific agent). `gcs_version` is optional; omit it for an agent-only
plugin. The agent host enforces `ados_version` at install; Mission
Control enforces `gcs_version` against its own build.

## Update flow

1. Operator drops a newer `.adosplug` into the install dialog for an
   already-installed plugin.
2. The host parses the archive. If the new version's compatibility range
   does not match the running host, the update is rejected.
3. The host diffs the new manifest's permissions against the existing
   grants:
   * **Removed permissions**: silently dropped.
   * **Added optional permissions**: shown with an off-by-default toggle.
   * **Added required permissions**: shown with emphasis; the operator
     must approve.
4. The operator clicks Install. The host stops the running plugin,
   removes the old install directory, unpacks the new archive,
   regenerates the systemd unit, and re-runs the permission grants.
5. On the next start the plugin's lifecycle hooks run in order
   (`on_install`, `on_enable`, `on_configure`, `on_start`). Read your
   live config through `ctx.config_kv` and migrate your data directory if
   needed.

The whole flow is one click for additive updates, plus a confirmation
when a new required permission is involved.

## Additive vs breaking permission changes

Additive (MINOR safe):

* Adding an optional permission.
* Adding a slot contribution.
* Adding a config field with a default value.

Breaking (MAJOR required):

* Adding a required permission.
* Removing a slot the operator depends on.
* Renaming a config field without a migration.
* Changing the wire format of a `plugin.<id>.*` event topic.
* Changing default behavior in a way the operator would notice.

## Config migration

Carry a `version` field in your config and migrate forward inside
`on_configure`:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
async def on_configure(self, ctx, new_config):
    if new_config.get("version", 1) < 2:
        new_config["new_field"] = derive_from_old(new_config["old_field"])
        new_config.pop("old_field", None)
        new_config["version"] = 2
        await ctx.config_kv.set("config", new_config, scope="drone")
```

## Data migration

There is no SDK migration helper. Carry a version marker in your data
directory and migrate forward on start, handling skipped versions:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
async def on_start(self, ctx):
    marker = ctx.data_dir / ".data_version"
    v = int(marker.read_text()) if marker.exists() else 1
    if v < 3:
        if v < 2:
            await self.migrate_v1_to_v2(ctx)
        await self.migrate_v2_to_v3(ctx)
        marker.write_text("3")
```

Forward migration is the contract; reading new data with an older plugin
is best-effort.

## Downgrades

Reinstalling an older version on top of a newer one is not the supported
path. To roll back, remove the plugin and install the older version. The
data directory persists across remove, so your data survives the rollback
(your migration code still has to tolerate it).

## Pre-release versions

Pre-releases use semver pre-release identifiers (`1.0.0-rc.1`). Treat a
pre-release as older than its equivalent release, so `1.0.0` supersedes
`1.0.0-rc.1`. Do not ship `-rc` builds to operators who did not ask for
them; the local-install path will happily take one.

## Communicating breaking changes

A MAJOR version is contract-breaking. Update the README with a migration
section, optionally ship a one-shot migration path that imports old state
into the new format, and bump `ados_version` if the change required a new
host feature. The host does not detect "user data lost" on upgrade; that
responsibility is the plugin author's.

## See also

* [Manifest reference](/developers/manifest)
* [Permissions](/developers/permissions)
* [Lifecycle](/developers/lifecycle)
