abi-capability-surface.md is the canonical plugin-need → ABI-mechanism map (manifest / host_call / hook), with WZ-020 rows marked. wasm-abi.md: content family drift fixed (list_posts), new families and hooks documented, open items closed except slot/media/embed render resolvers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.6 KiB
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 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:<id>, …) + .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:<uuid> ref) + provisioner.ensure_media (immutable seed semantics) |
ABI-native |
| Gating / crypto / datasources / users / subscriptions | as in wasm-abi.md §Capability calls | ABI-native |
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 —
RenderContextstill 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 resolvemedia: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/capsTestGoldenParity). 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 theHostCallRequestenvelope — no guest SDK in the path.