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

# Vendor binaries

> Ship a closed-source .so or vendor SDK alongside an open plugin.

Hardware drivers sometimes need a closed-source shared library to talk to
the device: a camera SDK, a LiDAR decompressor, a mount-control library.
ADOS plugins can include these binaries, but the manifest must declare
the inclusion so operators see the trust signal in the install dialog.

## Declare the vendor binary

The agent half sets `contains_vendor_binary: true` and provides
`vendor_attribution`. The two go together: the schema rejects one without
the other (set the flag with no attribution and the manifest fails to
load; set attribution with the flag false and it also fails). Both fields
are schema version 2, so the manifest must set `schema_version: 2`.

`vendor_attribution` is a list, so a plugin can attribute more than one
upstream. The `vision-nav` extension ships two VIO backends and declares
both:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
schema_version: 2
agent:
  runtime: rust
  entrypoint: "bin/vision-nav"
  isolation: subprocess
  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: "VIO mode vio_openvins uses the OpenVINS estimator."
    - name: "VINS-Fusion"
      license: "GPL-3.0-only"
      source_url: "https://github.com/HKUST-Aerial-Robotics/VINS-Fusion"
      upstream_version: "be55a937a57436548ddfb1bd324bc1e9a9e828e0"
      notice: "VIO mode vio_vins_fusion uses the VINS-Fusion estimator."
```

There is no per-binary `path` or `sha256` field in the manifest. The
archive's single `SIGNATURE` file covers every entry's hash, so the
vendor binary is protected by the same signature as the rest of the
archive. `license` is the only required attribution field (it must be a
non-empty string); the rest are optional provenance (`name`,
`source_url`, `source_offer_url`, `upstream_repo`, `upstream_version`,
`commit_sha`, `notice`). For a GPL-compatible upstream, set at least one
of `source_url` or `source_offer_url` plus a version pin
(`upstream_version` or `commit_sha`) so the install dialog can render a
real source offer.

## Trust signal in the install dialog

When `contains_vendor_binary` is true the install dialog shows the vendor
attribution and labels the plugin as carrying a vendor binary rather than
pure open source. The operator can still install, but the signal is loud.

## Loading the binary at runtime

The agent unpacks the archive under the plugin's install directory at
`/var/ados/plugins/<plugin-id>/`. Resolve the binary relative to your
plugin module and load it with `ctypes`:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import ctypes
from pathlib import Path

class FooDriver:
    def __init__(self) -> None:
        sdk_path = Path(__file__).parent / "vendor" / "libfoo.so"
        self._lib = ctypes.CDLL(str(sdk_path))
```

The plugin runs inside its sandbox, so the binary cannot escape the
plugin's permission set. A vendor `.so` that opens a USB camera still
requires the plugin to declare the matching capability (here
`hardware.usb.uvc`).

### Running the vendor code as a separate process

If the vendor code must run as its own process rather than load
in-process, the plugin needs the `process.spawn` capability plus a
`subprocess_spawn` allowlist. Entries are binary basenames, not paths.
The plugin host rejects any `ctx.process.spawn(...)` whose basename is
not on the list, and the manifest validator refuses to load if
`subprocess_spawn` is set but `process.spawn` is missing from
`permissions`.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
agent:
  permissions:
    - id: process.spawn
  subprocess_spawn:
    - ados_openvins_shim
    - ados_vins_fusion_shim
```

Each basename resolves to `<install_dir>/vendor/<basename>`, so ship the
binaries under `agent/vendor/` in your archive and the extractor lays
them out at `/var/ados/plugins/<plugin-id>/vendor/<basename>` after
install. The host rejects shell metacharacters and any path-traversal
attempt in a basename.

## License note

Altnautica's own open hardware files and developer docs are dedicated to
the public domain under CC0 1.0; the agent and the GCS are GPL-3.0. A
plugin that wraps a closed-source vendor `.so` is still under its own
declared license in its own source files; the install dialog flags the
vendor-binary inclusion separately so operators see it.

## Architecture support

Nothing validates that the `.so` matches the target SBC architecture.
Ship architecture-specific subfolders if your plugin runs on more than
one:

```
agent/vendor/
├── aarch64/
│   └── libfoo.so
└── armv7l/
    └── libfoo.so
```

And resolve at runtime:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import platform
from pathlib import Path

arch = platform.machine()
sdk_path = Path(__file__).parent / "vendor" / arch / "libfoo.so"
```

<Note>
  Arch subfolders work for an in-process `.so` you load yourself with
  `ctypes`. A `subprocess_spawn` binary is different: the host resolves it
  at the flat path `<install_dir>/vendor/<basename>`, so per-arch spawned
  binaries need a wrapper script on the allowlist that picks the right
  arch, not a subfolder.
</Note>

## See also

* [Manifest reference](/developers/manifest) for the full `agent` block.
* [Distribution and local install](/developers/distribution-local-install)
  for the pack and sign flow.
