pluginsdk/abi/proto/v1/invoke.proto
Alex Dunmow b6d40ed8ac feat: bootstrap block/pluginsdk — the proto-first plugin SDK (P1)
Bootstrap the plugin-facing SDK module so the fleet migration (P2) becomes a
mechanical block/core/X -> block/pluginsdk/X import rewrite.

- abi/proto/v1: the single source-of-truth ABI proto tree, copied from the cms
  authoring source (cms/backend/abi/proto/v1) with go_package retargeted to
  git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1. Kills the hand-kept
  cms/core proto duplication (audit gap 4).
- abi/v1: generated Go bindings (buf generate); byte-identical to core's abi/v1
  save the embedded go_package path.
- plugin/ (registration + DI surface), plugin/wasmguest/** (transport shim,
  caps stubs, bnwasm db driver, testdata fixtures + golden .pb), and the
  guest-facing type packages: blocks (+builtin/shared/tags), templates
  (+pongo/bn), auth, settings, content, gating, crypto, rbac, video, ai,
  subscriptions, menus, datasources. Internal imports rewritten core ->
  pluginsdk; zero block/core references remain.
- README/AGENTS(+CLAUDE symlink)/Makefile: proto is the contract, Go is one
  binding; no replace directives; templates/bn is a synced copy authored in cms.

Spec: cms docs/superpowers/specs/2026-07-07-proto-first-plugin-sdk-design.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:51:00 +08:00

279 lines
8.8 KiB
Protocol Buffer

// invoke.proto — the bn_invoke envelope, hook catalog, and the job/lifecycle
// hook payloads (WO-WZ-001).
//
// Every host→guest call crosses the wasm boundary as one guest export:
//
// bn_invoke(hook_id, ptr, len) → packed(ptr, len)
//
// where (ptr, len) frames a serialized InvokeRequest and the packed return
// frames a serialized InvokeResponse. Hook-specific payloads (render.proto,
// http.proto, and the messages below) travel inside InvokeRequest.payload /
// InvokeResponse.payload. See core/docs/wasm-abi.md.
syntax = "proto3";
package abi.v1;
import "v1/manifest.proto";
option go_package = "git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1";
// Hook identifies the guest entry point being invoked.
enum Hook {
HOOK_UNSPECIFIED = 0;
// Render one block: payload = RenderBlockRequest / RenderBlockResponse.
HOOK_RENDER_BLOCK = 1;
// Render one template: payload = RenderTemplateRequest / RenderTemplateResponse.
HOOK_RENDER_TEMPLATE = 2;
// Forward a buffered HTTP request to the guest's mux:
// payload = HttpRequest / HttpResponse.
HOOK_HANDLE_HTTP = 3;
// Run a background job handler: payload = JobRequest / JobResponse.
HOOK_JOB = 4;
// Plugin load lifecycle: payload = LoadRequest / LoadResponse.
HOOK_LOAD = 5;
// Plugin unload lifecycle: payload = UnloadRequest / UnloadResponse.
HOOK_UNLOAD = 6;
// Re-fetch content for RAG re-indexing:
// payload = RagFetchRequest / RagFetchResponse.
HOOK_RAG_FETCH = 7;
// Media lifecycle event delivery: payload = MediaHookRequest / MediaHookResponse.
HOOK_MEDIA_HOOK = 8;
// Capture the static manifest at publish time:
// payload = DescribeRequest / DescribeResponse.
HOOK_DESCRIBE = 9;
// Invoke a plugin-declared template tag (manifest.declared_tags) that the
// host engine hit while rendering a powered block:
// payload = RenderTagRequest / RenderTagResponse.
HOOK_RENDER_TAG = 10;
// Apply a plugin-declared template filter (manifest.declared_filters):
// payload = ApplyFilterRequest / ApplyFilterResponse.
HOOK_APPLY_FILTER = 11;
// Execute a guest-registered AI tool handler (ai.tools.register):
// payload = AiToolCallRequest / AiToolCallResponse.
HOOK_AI_TOOL_CALL = 12;
// Invoke a method on a bridge service this plugin registered
// (bridge.register_service): payload = BridgeCallRequest / BridgeCallResponse.
HOOK_BRIDGE_CALL = 13;
// Render one directory panel section (DirectoryExtensions.PanelSections):
// payload = DirectoryPanelSectionRequest / DirectoryPanelSectionResponse.
HOOK_DIRECTORY_PANEL_SECTION = 14;
// Run one directory pin decorator (DirectoryExtensions.PinDecorators):
// payload = DirectoryPinDecoratorRequest / DirectoryPinDecoratorResponse.
HOOK_DIRECTORY_PIN_DECORATOR = 15;
}
// InvokeRequest is the host→guest call envelope.
message InvokeRequest {
Hook hook = 1;
// Serialized hook-specific request message (see Hook value comments).
bytes payload = 2;
// Milliseconds the guest has to answer; the host also enforces this
// deadline on the wasm instance (WithCloseOnContextDone).
int64 deadline_ms = 3;
}
// InvokeResponse is the guest→host return envelope.
message InvokeResponse {
// Serialized hook-specific response message; empty when error is set.
bytes payload = 1;
// Set when the hook failed; the host treats decode failures and traps as
// implicit ABI_ERROR_CODE_INTERNAL.
AbiError error = 2;
}
// AbiErrorCode classifies boundary-crossing failures.
enum AbiErrorCode {
ABI_ERROR_CODE_UNSPECIFIED = 0;
// The handler ran and failed; message carries the Go error text.
ABI_ERROR_CODE_INTERNAL = 1;
// The payload could not be decoded.
ABI_ERROR_CODE_DECODE = 2;
// The hook/capability is not implemented by the callee.
ABI_ERROR_CODE_UNIMPLEMENTED = 3;
// The call exceeded its deadline; the instance is considered poisoned.
ABI_ERROR_CODE_DEADLINE_EXCEEDED = 4;
// The caller is not entitled to this capability.
ABI_ERROR_CODE_PERMISSION_DENIED = 5;
// A db.* call named a transaction handle the host has already expired
// (dropped at the call-chain deadline, WO-WZ-007). Distinct from a real
// fault: the unit of work is retryable in a fresh transaction. Emitted by
// the cms dbexec side (adopted separately) and mapped guest-side to
// bnwasm.ErrTxExpired.
ABI_ERROR_CODE_TX_EXPIRED = 6;
}
// AbiError is the structured error carried by InvokeResponse and
// HostCallResponse.
message AbiError {
AbiErrorCode code = 1;
string message = 2;
}
// --- DESCRIBE ---
// DescribeRequest asks the guest for its static manifest (publish time only).
message DescribeRequest {
// ABI major version of the calling host/publish tool.
uint32 host_abi_version = 1;
}
// DescribeResponse returns the static manifest.
message DescribeResponse {
PluginManifest manifest = 1;
}
// --- LOAD / UNLOAD ---
// LoadRequest carries the static host configuration the .so world exposed as
// CoreServices.AppURL / CoreServices.MediaPath.
message LoadRequest {
HostConfig host_config = 1;
}
// HostConfig mirrors the plain-value CoreServices fields.
message HostConfig {
string app_url = 1; // CoreServices.AppURL
string media_path = 2; // CoreServices.MediaPath
}
message LoadResponse {}
message UnloadRequest {}
message UnloadResponse {}
// --- JOB ---
// JobRequest dispatches one background job to the guest handler registered
// for job_type (manifest.job_types).
message JobRequest {
string job_type = 1;
// JSON job configuration (json.RawMessage in JobHandlerFunc).
bytes config_json = 2;
}
// JobResponse returns the handler's JSON result.
message JobResponse {
bytes result_json = 1;
}
// --- RAG_FETCH ---
// RagFetchRequest asks the guest's registered content fetcher
// (manifest.rag_content_fetcher_types) for a content item's text.
message RagFetchRequest {
string content_type = 1;
string content_id = 2; // UUID
}
// RagFetchResponse mirrors plugin.ContentFetcher's return values.
message RagFetchResponse {
string title = 1;
string text = 2;
}
// --- MEDIA_HOOK ---
// MediaHookRequest delivers one media lifecycle event
// (plugin.MediaHooksProvider).
message MediaHookRequest {
oneof event {
MediaAnalyzedEvent media_analyzed = 1; // OnMediaAnalyzed
ModerationDecisionEvent moderation_decision = 2; // OnModerationDecision
}
}
message MediaHookResponse {}
// MediaAnalyzedEvent mirrors plugin.MediaAnalyzedEvent (UUIDs as strings).
message MediaAnalyzedEvent {
string media_id = 1;
string analysis_id = 2;
string content_hash = 3;
string status = 4;
string source_plugin = 5;
string source_type = 6;
string source_ref_id = 7;
string safe_adult = 8;
string safe_violence = 9;
string safe_racy = 10;
}
// --- AI_TOOL_CALL ---
// AiToolCallRequest executes the guest-side Handler of a tool the plugin
// registered via ai.tools.register (ai.ToolHandler).
message AiToolCallRequest {
string slug = 1;
// JSON encoding of the params map.
bytes params_json = 2;
}
// AiToolCallResponse mirrors ai.ToolResult. A handler-level failure travels
// in error_message (a normal tool outcome the model sees); AbiError stays
// reserved for transport/decode/panic failures.
message AiToolCallResponse {
string content = 1;
string error_message = 2;
}
// --- BRIDGE_CALL ---
// BridgeCallRequest asks THIS plugin (the provider) to run a method on one of
// its registered bridge services. The consumer side is the bridge.invoke
// capability; payload encoding is agreed between the two plugins (JSON by
// convention).
message BridgeCallRequest {
string service_name = 1;
string method = 2;
bytes payload = 3;
}
message BridgeCallResponse {
bytes payload = 1;
}
// --- DIRECTORY_PANEL_SECTION / DIRECTORY_PIN_DECORATOR ---
// DirectoryPanelSectionRequest renders the index-th registered panel section
// (DirectoryExtensions.PanelSections[index], counted in
// manifest.directory_extensions.panel_section_count).
message DirectoryPanelSectionRequest {
uint32 index = 1;
// JSON encoding of the row data map.
bytes data_json = 2;
}
message DirectoryPanelSectionResponse {
string html = 1;
}
// DirectoryPinDecoratorRequest runs the index-th registered pin decorator,
// which mutates the pin map in place; the mutated pin is returned.
message DirectoryPinDecoratorRequest {
uint32 index = 1;
// JSON encoding of the pin map (mutated by the decorator).
bytes pin_json = 2;
// JSON encoding of the row data map (read-only input).
bytes data_json = 3;
}
message DirectoryPinDecoratorResponse {
// JSON encoding of the mutated pin map.
bytes pin_json = 1;
}
// ModerationDecisionEvent mirrors plugin.ModerationDecisionEvent.
message ModerationDecisionEvent {
string media_id = 1;
string analysis_id = 2;
string status = 3;
string previous_status = 4;
string source_plugin = 5;
string source_type = 6;
string source_ref_id = 7;
string moderated_by = 8;
string note = 9;
}