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>
279 lines
8.8 KiB
Protocol Buffer
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;
|
|
}
|