# ABI Capability Surface (WO-WZ-019) The canonical map of **every capability a plugin needs from the CMS** to its ABI mechanism. The rule is *injection, not reliance*: a plugin obtains 100% of its host interactions by (a) declaring static intent in `manifest.pb`, (b) calling a `blockninja.host_call` capability method, or (c) implementing a host-invoked hook. Nothing requires linking the Go `core` module — the Go guest SDK (`plugin/wasmguest`) is an ergonomic front-end over these primitives, not part of the model. A plugin in any language that speaks protobuf over the [wasm ABI](wasm-abi.md) reaches everything below. Legend — **Mechanism**: `manifest` (static declaration, consumed by the host loader), `host_call` (dynamic guest→host capability), `hook` (host-invoked guest logic), `artifact` (a directory in the `.bnp` the host consumes). **Codeless equivalent**: how a plugin with NO `plugin.wasm` gets the same effect (the WO-WZ-020 declarative path; rows marked *WZ-020* land there). ## Static contributions (what the plugin IS) | Capability | Mechanism | Status | Codeless equivalent | |---|---|---|---| | Register block types (key/title/category/icon/schema) | `manifest.blocks` (`BlockMeta`) + `schemas/` artifact | ABI-native | Same — pure declaration, zero code | | Block template overrides | `manifest.block_template_overrides` | ABI-native | Same | | Templates / system templates / page templates | `manifest.template_keys`, `system_templates`, `page_templates` | ABI-native | Same + `.ninjatpl` files in the artifact (WZ-020) | | Master pages | `manifest.master_pages` | ABI-native | Same | | Email wrappers | `manifest.email_wrapper_system_keys` | ABI-native | Same | | Template tags / filters (computing) | `manifest.declared_tags` / `declared_filters` + `HOOK_RENDER_TAG` / `HOOK_APPLY_FILTER` | ABI-native | N/A — a *computing* tag requires code by definition | | Template tags (pure snippet/partial, no logic) | today: same as computing (hook) | **WZ-020**: declare as a template partial, host-rendered, no hook | Partial file in `templates/` | | Powered block with a *declared data provider* (no render hook at all) | today: `HOOK_RENDER_BLOCK` returning `RenderBlockResponse.powered` | **WZ-020**: manifest provider declaration (`posts`, `pages`, `menu`, `datasource:`, …) + `.ninjatpl`; host fetches + renders | The WZ-020 headline feature | | Settings schema / settings panel / admin pages | `manifest.settings_schema`, `settings_panel`, `admin_pages` | ABI-native | Same | | Theme presets / bundled fonts / CSS manifest / icon packs | `manifest.theme_presets`, `bundled_fonts`, `css_manifest`, `required_icon_packs` | ABI-native (JSON payloads; no Go types on the wire) | Same. Fonts are **purely declarative** — the host consumes `bundled_fonts` at load; runtime *activation* (choosing a font) is a site setting, writable via `settings.update_site_setting`. | | AI actions | `manifest.ai_actions` | ABI-native | Same | | RBAC method roles / core service bindings | `manifest.rbac_method_roles`, `core_service_bindings` | ABI-native | Same (bindings host-mounted) | | Job types / RAG fetcher types / directory extensions (static fields) | `manifest.job_types`, `rag_content_fetcher_types`, `directory_extensions` | ABI-native | Job/RAG types need their hooks → code | | Migrations / assets / MF web bundle | `artifact` `migrations/`, `assets/`, `web/` (host-run/served) | ABI-native | Same | | Persistent data dir | `plugin.mod data_dir` → `manifest.data_dir` | ABI-native | N/A (no code to need it) | ## Dynamic requests (host_call families) Every family below is registered in the cms host (`plugin/wasmhost/caps`) and mirrored 1:1 by a guest SDK stub. Wire types are language-neutral: canonical UUID strings, JSON bytes for maps, protobuf for structure. | Capability | Methods | Status | |---|---|---| | Content reads | `content.get_author_profile`, `get_page`, `get_post`, `list_posts`, `slugify`, `block_note_to_html`, `generate_excerpt`, `strip_html` | ABI-native | | **Content authoring** (WZ-019) | `content.create_page`, `set_page_blocks`, `publish_page`, `set_page_seo`, `upsert_post` | ABI-native | | **Provisioning** (WZ-019) — idempotent seed/ensure, **Load-time** | `provisioner.ensure_data_table`, `merge_site_settings`, `ensure_setting`, `ensure_page`, `override_site_settings`, `ensure_menu_item`, `register_embedding_config`, `ensure_embed`, `ensure_job_schedule`, `update_data_table_row_field`, `disable_orphaned_job_schedules`, `ensure_plugin`, `ensure_custom_color`, `ensure_media` | ABI-native. `RegisterWithProvisioner` is **inert under wasm** (DESCRIBE stubs host functions): call `deps.Provisioner` from `Load` instead. `EnsureEmbed` crosses template-rendered embeds only — `RenderFunc` cannot serialize. | | Settings | `settings.get_site_settings`, `get_plugin_settings`, `update_site_setting`, **`update_plugin_settings`** (WZ-019; host-pinned to the caller's own plugin) | ABI-native | | Menus (read) | `menus.get_menu_by_name`, `get_menu_items` | ABI-native. Menu *writes* are seed-shaped → `provisioner.ensure_menu_item`. | | Media | `media.deposit` (full pipeline, deterministic-ID + `created` flag, folder, alt text, stable `media:` ref) + `provisioner.ensure_media` (immutable seed semantics) | ABI-native | | Gating / crypto / datasources / users / subscriptions | as in [wasm-abi.md](wasm-abi.md) §Capability calls | ABI-native | | Email | `email.send` | ABI-native | | AI | `ai.text_call`, `ai.tools.register` (+ execution via `HOOK_AI_TOOL_CALL`) | ABI-native | | Jobs | `jobs.submit`, **`jobs.progress`** (WZ-019; correlated to the running job via the HOOK_JOB call context — no job ID on the wire) | ABI-native | | Bridge | `bridge.register_service`, `get_service` (availability), **`bridge.invoke`** (WZ-019; opaque-payload cross-plugin calls → provider's `BridgeInvokable` or `HOOK_BRIDGE_CALL`) | ABI-native. Typed in-process Go values still don't cross — by design; `GetService` stays nil for wasm consumers. | | Embeddings / RAG / reviews / badges | as in wasm-abi.md | ABI-native | | Database | `db.query/exec/tx_*` under the per-plugin Postgres role | ABI-native | ## Host-invoked hooks (logic the plugin injects) | Hook | Contract | Status | |---|---|---| | `HOOK_DESCRIBE` | `DescribeRequest/Response` — manifest capture at publish | ABI-native | | `HOOK_LOAD` / `HOOK_UNLOAD` | `LoadRequest` (host config) / `UnloadRequest` | ABI-native | | `HOOK_RENDER_BLOCK` | html OR powered `{template, data_json}` | ABI-native | | `HOOK_RENDER_TEMPLATE` | `RenderTemplateRequest/Response` | ABI-native | | `HOOK_RENDER_TAG` / `HOOK_APPLY_FILTER` | declared-tag/filter callbacks, re-entrancy-free | ABI-native | | `HOOK_HANDLE_HTTP` | buffered HTTP + trusted identity headers | ABI-native | | `HOOK_JOB` | `JobRequest/Response` + `jobs.progress` | ABI-native | | `HOOK_RAG_FETCH` | content re-index callback | ABI-native | | `HOOK_MEDIA_HOOK` | media lifecycle events | ABI-native | | **`HOOK_AI_TOOL_CALL`** (WZ-019) | `AiToolCallRequest{slug, params_json}` → `{content, error_message}`; executes the guest handler recorded at `ai.tools.register` | ABI-native | | **`HOOK_BRIDGE_CALL`** (WZ-019) | `BridgeCallRequest{service_name, method, payload}` → `{payload}`; provider side of `bridge.invoke` | ABI-native | | **`HOOK_DIRECTORY_PANEL_SECTION`** / **`HOOK_DIRECTORY_PIN_DECORATOR`** (WZ-019) | indexed callbacks over `manifest.directory_extensions` counts; pin decorators return the mutated pin | ABI-native | **Per-instance rule for callback hooks:** anything the host may call back on an arbitrary pooled instance (AI tools, bridge services, RAG fetchers) must be registered in `Register` — which runs on every instance — not in `Load` (one instance only). Use `wasmguest.HostServices()` for deps at Register time. ## Remaining open items - **Slot / media / embed resolvers during a guest render** — `RenderContext` still cannot carry them (function values). The powered-block path sidesteps this: host-side rendering has the real resolvers. Only a plain-HTML guest block that wants to resolve `media:` refs itself is affected. Tracked for a render-focused WO; not blocking any shipping plugin. ## Conformance - Guest↔host wire parity: every capability method has a golden request/response pair (`plugin/wasmguest/caps/testdata/golden/`, generated by the guest stubs) that the cms host replays byte-for-byte through its real handlers (`cms plugin/wasmhost/caps` `TestGoldenParity`). The count check fails if a registered method lacks a golden. - Injection-not-reliance: `TestRawProtobufConformance` (cms caps package) drives representative capabilities with hand-constructed protobuf messages through the `HostCallRequest` envelope — no guest SDK in the path.