core/docs/abi-capability-surface.md
Alex Dunmow 925b6051a3 docs(abi): capability-surface matrix + wasm-abi.md updated for WO-WZ-019
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>
2026-07-04 10:28:44 +08:00

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_dirmanifest.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 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 renderRenderContext 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.