# WIT world and ABI

Current addon ABI: `0.3.0`. Addons are WASI preview 2 components for the world below.

## Functions

| Interface | Name | Signature | Description |
|---|---|---|---|
| `host` | `log` | `func(level: level, message: string)` |  |
| `host` | `http-fetch` | `func(req: http-request) -> result<http-response, string>` | Outbound HTTP. Only hosts in `capabilities.http` (plus `$server` / `$setting:<key>` placeholders) are reachable; link-local and cloud metadata addresses are always refused; bodies and time are capped. |
| `host` | `setting` | `func(key: string) -> option<string>` | Admin setting value (JSON-encoded), if set. |
| `host` | `secret` | `func(key: string) -> option<string>` | Decrypted secret declared in `capabilities.secrets`. |
| `host` | `kv-get` | `func(key: string) -> option<string>` | Addon-private key/value store (needs `capabilities.kv`). |
| `host` | `kv-set` | `func(key: string, value: string) -> result<_, string>` |  |
| `host` | `kv-delete` | `func(key: string)` |  |
| `host` | `now-unix` | `func() -> s64` |  |
| `host` | `random-bytes` | `func(len: u32) -> list<u8>` |  |
| `power` | `db-read` | `func(entity: string, query: string) -> result<string, string>` | Read core records (`capabilities.db.read` lists the entities). `query`: JSON `{ "id"?, "filter"?: {column: value}, "limit"?, "offset"? }`. Returns a JSON array. Password hashes, TOTP secrets, tokens and encrypted credentials are never returned. |
| `power` | `db-write` | `func(op: string, input: string) -> result<string, string>` | Typed write through host APIs (`capabilities.db.write` lists the ops, e.g. `client.note`, `ticket.reply`, `service.suspend`). Audited. |
| `power` | `sql` | `func(statement: string, params: string) -> result<string, string>` | SQL on the addon's own tables (`addon_<id>_*`, `capabilities.db.sql`). `params`: JSON array bound as $1..$n. Returns JSON rows. |
| `power` | `exec` | `func(program: string, args: list<string>, stdin: string) -> result<exec-output, string>` | Run an allowlisted program (`capabilities.exec`, absolute paths; no shell, empty environment, output and time capped). Off in Docker. |
| `power` | `job-enqueue` | `func(op: string, input: string, delay-secs: u32) -> result<_, string>` | Queue `call(op, input)` to run in the background job worker after `delay-secs` (`capabilities.jobs` must list `op`). |
| `events` | `emit` | `func(name: string, payload: string) -> result<string, string>` |  |
| `addon` | `describe` | `func() -> string` | JSON descriptor: kind-specific metadata (capabilities, settings, fields). |
| `addon` | `call` | `func(op: string, input: string) -> result<string, string>` | Run one operation with a JSON input; returns JSON output. |
| `addon` | `on-event` | `func(name: string, payload: string) -> result<_, string>` | Subscribed event (`invoice.paid`, `service.created`, …). |

## pelty.wit

```wit
package pelty:addon@0.2.0;

/// Host services every addon may import. Each call is checked against the
/// capabilities the admin approved at install (addon.json `capabilities`).
interface host {
    enum level { debug, info, warn, error }
    log: func(level: level, message: string);

    record http-request {
        method: string,
        url: string,
        headers: list<tuple<string, string>>,
        body: string,
    }
    record http-response {
        status: u16,
        body: string,
    }
    /// Outbound HTTP. Only hosts in `capabilities.http` (plus `$server` /
    /// `$setting:<key>` placeholders) are reachable; link-local and cloud
    /// metadata addresses are always refused; bodies and time are capped.
    http-fetch: func(req: http-request) -> result<http-response, string>;

    /// Admin setting value (JSON-encoded), if set.
    setting: func(key: string) -> option<string>;
    /// Decrypted secret declared in `capabilities.secrets`.
    secret: func(key: string) -> option<string>;

    /// Addon-private key/value store (needs `capabilities.kv`).
    kv-get: func(key: string) -> option<string>;
    kv-set: func(key: string, value: string) -> result<_, string>;
    kv-delete: func(key: string);

    now-unix: func() -> s64;
    random-bytes: func(len: u32) -> list<u8>;
}

/// Opt-in powers. Every call is refused unless the matching capability was
/// approved by the admin at install (see the generated docs, "Capabilities" page).
interface power {
    /// Read core records (`capabilities.db.read` lists the entities).
    /// `query`: JSON `{ "id"?, "filter"?: {column: value}, "limit"?, "offset"? }`.
    /// Returns a JSON array. Password hashes, TOTP secrets, tokens and
    /// encrypted credentials are never returned.
    db-read: func(entity: string, query: string) -> result<string, string>;
    /// Typed write through host APIs (`capabilities.db.write` lists the ops,
    /// e.g. `client.note`, `ticket.reply`, `service.suspend`). Audited.
    db-write: func(op: string, input: string) -> result<string, string>;
    /// SQL on the addon's own tables (`addon_<id>_*`, `capabilities.db.sql`).
    /// `params`: JSON array bound as $1..$n. Returns JSON rows.
    sql: func(statement: string, params: string) -> result<string, string>;

    record exec-output {
        status: s32,
        stdout: string,
        stderr: string,
    }
    /// Run an allowlisted program (`capabilities.exec`, absolute paths; no
    /// shell, empty environment, output and time capped). Off in Docker.
    exec: func(program: string, args: list<string>, stdin: string) -> result<exec-output, string>;

    /// Queue `call(op, input)` to run in the background job worker after
    /// `delay-secs` (`capabilities.jobs` must list `op`).
    job-enqueue: func(op: string, input: string, delay-secs: u32) -> result<_, string>;
}

/// Addon-defined events (ABI 0.3 features; the package stays @0.2.0 so
/// 0.2 components keep linking). `name` must be `<own-addon-id>:<event>`
/// and be declared in `capabilities.emits`. The `before` phase runs
/// synchronously through every listener (priority order, cancel/modify);
/// the result is JSON `{ "cancelled": bool, "reason"?: string, "payload": {...} }`.
/// `after` and `finished` phases are delivered by the host afterwards.
interface events {
    emit: func(name: string, payload: string) -> result<string, string>;
}

/// The addon contract. Every kind (gateway, provisioning, registrar,
/// captcha, sso, integration) implements the same three exports; `op` names
/// and JSON shapes per kind are documented in the generated docs (`make docs`).
interface addon {
    /// JSON descriptor: kind-specific metadata (capabilities, settings, fields).
    describe: func() -> string;
    /// Run one operation with a JSON input; returns JSON output.
    call: func(op: string, input: string) -> result<string, string>;
    /// Subscribed event (`invoice.paid`, `service.created`, …).
    on-event: func(name: string, payload: string) -> result<_, string>;
}

world pelty-addon {
    import host;
    import power;
    import events;
    export addon;
}
```

## captcha.wit

```wit
package pelty:addon@0.1.0;

/// Host services every addon may import. Capabilities are still gated by the manifest.
interface host {
    enum level { debug, info, warn, error }
    log: func(level: level, message: string);
}

/// Captcha verification contract (ABI 0.1).
interface captcha {
    record verify-request {
        token: string,
        remote-ip: option<string>,
        /// Admin-configured secret from addon settings.
        secret: string,
    }
    record verify-result {
        success: bool,
        error-codes: list<string>,
    }
    verify: func(req: verify-request) -> result<verify-result, string>;
}

world captcha-addon {
    import host;
    export captcha;
}
```

## Rust SDK

`extensions/addons/sdk` (`pelty-addon-sdk`) wraps the bindings: `host`, `power` and `events` modules, `emit`, `fetch`, `setting`, `reply::{proceed, modify, cancel, uncancel}`, and the `export!`, `gateway_addon!`, `provisioning_addon!` and `registrar_addon!` macros.

