# 🤖 The integrations registry

One JSON manifest per third-party service. A manifest is the WHOLE of what an
integration can do on an Eleven Messenger instance: how a member authenticates
to the service, the one API host its operations may touch, and the closed list
of operations — each marked `read` or `write`, writes each carrying the
confirmation line a person taps before it runs. An operation that isn't in the
manifest cannot be expressed, the same fail-closed palette rule as bot recipes
(`docs/bot-recipes.md`).

This directory is destined to become its own public repository
(`elevenmessenger/integrations`) so integrations can be added — and audited —
without touching the messenger codebase: adding one is a PR with a manifest
(plus, for OAuth services, registering the OAuth app on the connect broker,
which is where client secrets live — `docs/connected-services.md`). Until that
split, it lives here and is embedded into the binaries (`registry.go`), with
the broker serving it at `GET /registry` so web clients read the same catalog.

## Manifest schema (v1)

```jsonc
{
  "schema": 1,
  "id": "github",                    // lowercase slug, unique
  "name": "GitHub",
  "category": "development",         // catalog grouping
  "auth": {
    "kind": "pat",                   // "pat" | "oauth" | "both"
    "base": "https://api.github.com",// the ONLY host ops may reach (executor-pinned)
    "authorization": "Bearer",       // Authorization: <scheme> <token>; "none" sends the bare token (Linear)
    "headers": { "Accept": "…" },    // extra headers on every call
    "pat_help": "…",                 // where a member mints a scoped token
    "oauth": { "authorize_url": "…", "scopes": ["…"] }  // when the broker has an app
  },
  "ops": [
    {
      "id": "repos.list",            // namespace.verb, unique within the manifest
      "label": "List your repositories",
      "access": "read",              // "read" | "write" — grants are scoped to this
      "method": "GET",
      "path": "/user/repos?per_page=20",   // {arg} placeholders allowed
      "args": { "owner": { "required": true } },  // string args only; validated
      "body_json": { "title": "{title}" },        // JSON body template ({arg} in leaves)
      "encoding": "json",            // "json" (default) | "form" (Stripe-style)
      "confirm": "Open “{title}”?",  // REQUIRED on writes: the consent chip's line
      "pick": {                      // response projection — what may flow BACK
        "root": "data",              // descend into an envelope first (optional)
        "fields": ["full_name", "user.login"]  // keep only these dot-paths per object
      }
    }
  ]
}
```

`pick` makes the response as reviewable as the request: provider APIs answer
with far more than an app needs (a GitHub repo object is ~100 fields, several
KB), and an op result travels inside one end-to-end-encrypted message with a
hard size cap — so the manifest declares the fields that may flow back, and the
executor drops everything else before the result ever leaves the sidecar.
Applied to the object at `root` (or to each object of the array there); nested
paths come back flattened (`"user.login": "octocat"`). An op without `pick`
returns the raw body, still subject to the size cap.

Rules the loader enforces (fail closed — a bad manifest never half-loads):

- `id`/op ids well-formed; op ids unique; `access` ∈ read/write; every write
  has a `confirm` template; `base` is https; methods ∈ GET/POST/PUT/PATCH/DELETE.
- `{arg}` placeholders in `path`/`body_json`/`confirm` must name declared args.
- Arg values are strings, bounded, and escaped into the URL — a value can
  never break out of its path segment or query slot.
- `pick` (when present) names at least a root or one field, all well-formed
  dot-paths.

## Trust model

The manifest is reviewed CODE (this registry), never user content. The
executor pins every request to `auth.base`'s host, so a manifest op can't be
steered elsewhere. Member credentials stay client-sealed exactly as before
(`docs/connected-services.md`): a bot receives a short-lived grant sealed to
its own key, now carrying `access: "read" | "write"` — a read grant cannot run
a write op, and grants are per-bot by construction (sealed to one keypair).
Writes additionally require the member's tap on a confirmation chip naming the
exact operation ("Register blip.com — this costs real money").
