// capability.proto — guest→host capability calls (WO-WZ-001). // // Each CoreServices interface (core/plugin/deps.go) becomes a host-function // family; every interface method gets one request/response message pair // here, mirroring the Go signature 1:1. UUIDs travel as canonical strings; // map[string]any / []byte-JSON values travel as JSON bytes. // // Calls cross via the generic HostCallRequest/HostCallResponse envelope; the // method string selects the pair (e.g. "content.get_author_profile" → // ContentGetAuthorProfileRequest/Response). Full method table in // core/docs/wasm-abi.md. // // Not represented here by design: // - CoreServices.Pool → db.proto (the DB driver messages) // - CoreServices.Interceptors → host-side only (never crosses) // - CoreServices.AppURL/MediaPath → LoadRequest.host_config (invoke.proto) // - CoreServices.CoreServiceBindings → manifest core_service_bindings // - RAGService.RegisterContentFetcher → manifest rag_content_fetcher_types // + the RAG_FETCH hook (callback inversion) // - ai.ToolDefinition.Handler → host→guest tool execution is a runtime-WO // concern (flagged in core/docs/wasm-abi.md) syntax = "proto3"; package abi.v1; import "google/protobuf/timestamp.proto"; import "v1/http.proto"; import "v1/invoke.proto"; option go_package = "git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1"; // HostCallRequest is the generic guest→host capability envelope. message HostCallRequest { // Capability method, "." (e.g. "crypto.encrypt_secret"). string method = 1; // Serialized family request message. bytes payload = 2; } // HostCallResponse is the generic host→guest capability return envelope. message HostCallResponse { // Serialized family response message; empty when error is set. bytes payload = 1; // Set when the capability call failed; the guest SDK surfaces it as a // normal Go error. AbiError error = 2; } // --- content.* (content.Content) --- message ContentGetAuthorProfileRequest { string id = 1; // UUID } message ContentGetAuthorProfileResponse { AuthorProfile author = 1; } // AuthorProfile mirrors content.AuthorProfile. message AuthorProfile { string id = 1; // UUID string name = 2; string slug = 3; string bio = 4; string avatar_url = 5; string website = 6; map social_links = 7; } message ContentGetPageRequest { string slug = 1; } message ContentGetPageResponse { PageInfo page = 1; } // PageInfo mirrors content.PageInfo. message PageInfo { string id = 1; // UUID string slug = 2; string title = 3; } // ContentPublishedBlockConfigsRequest asks the host for the stored content // (config JSON) of every block with the given key inside a CURRENTLY // published page snapshot. Lets a guest resolve server-authoritative // per-block settings (e.g. a captcha requirement) it cannot query itself. message ContentPublishedBlockConfigsRequest { string block_key = 1; } message ContentPublishedBlockConfigsResponse { repeated bytes configs = 1; // each entry: one block's content as JSON } message ContentGetPostRequest { string slug = 1; } message ContentGetPostResponse { PostInfo post = 1; } // PostInfo mirrors content.PostInfo. message PostInfo { string id = 1; // UUID string slug = 2; string title = 3; string excerpt = 4; string featured_image_url = 5; string author_id = 6; // UUID string body = 7; // rendered HTML; set by get_post / list_posts(include_body) string author_name = 8; string author_slug = 9; google.protobuf.Timestamp published_at = 10; } message ContentListPostsRequest { int32 limit = 1; int32 offset = 2; string category = 3; // optional category slug filter ("" = all) bool published_only = 4; bool include_body = 5; bool include_excerpt = 6; } message ContentListPostsResponse { repeated PostInfo posts = 1; } message ContentSlugifyRequest { string text = 1; } message ContentSlugifyResponse { string slug = 1; } message ContentBlockNoteToHtmlRequest { // JSON encoding of the BlockNote document map. bytes doc_json = 1; } message ContentBlockNoteToHtmlResponse { string html = 1; } message ContentGenerateExcerptRequest { string html = 1; int32 max_len = 2; } message ContentGenerateExcerptResponse { string excerpt = 1; } message ContentStripHtmlRequest { string html = 1; } message ContentStripHtmlResponse { string text = 1; } // --- settings.* (settings.Settings) + settings.update (settings.Updater) --- message SettingsGetSiteSettingsRequest {} message SettingsGetSiteSettingsResponse { // JSON encoding of the site settings map. bytes settings_json = 1; } message SettingsGetPluginSettingsRequest { string plugin_name = 1; } message SettingsGetPluginSettingsResponse { // JSON encoding of the plugin settings map. bytes settings_json = 1; } message SettingsUpdateSiteSettingRequest { string key = 1; // JSON encoding of the value. bytes value_json = 2; } message SettingsUpdateSiteSettingResponse {} // --- gating.* (gating.Gating) --- message GatingGetSubscriberTierLevelRequest { string user_id = 1; // UUID } message GatingGetSubscriberTierLevelResponse { int32 level = 1; } message GatingEvaluateAccessRequest { int32 user_tier_level = 1; // Absent rule means "no rule" (access granted). AccessRule rule = 2; } message GatingEvaluateAccessResponse { AccessResult result = 1; } // AccessRule mirrors gating.AccessRule. message AccessRule { int32 min_tier_level = 1; string override_tier_id = 2; string teaser_mode = 3; // "hard", "soft", "none" int32 teaser_percent = 4; } // AccessResult mirrors gating.AccessResult. message AccessResult { bool has_access = 1; string teaser_mode = 2; int32 teaser_percent = 3; int32 required_level = 4; } // --- crypto.* (crypto.Crypto) --- message CryptoEncryptSecretRequest { string plaintext = 1; } message CryptoEncryptSecretResponse { string ciphertext = 1; } message CryptoDecryptSecretRequest { string ciphertext = 1; } message CryptoDecryptSecretResponse { string plaintext = 1; } // --- menus.* (menus.Menus) --- message MenusGetMenuByNameRequest { string name = 1; } message MenusGetMenuByNameResponse { Menu menu = 1; } // Menu mirrors menus.Menu. message Menu { string id = 1; // UUID string name = 2; } message MenusGetMenuItemsRequest { string menu_id = 1; // UUID } message MenusGetMenuItemsResponse { repeated MenuItem items = 1; } // MenuItem mirrors menus.MenuItem. message MenuItem { string id = 1; // UUID string menu_id = 2; // UUID string label = 3; string url = 4; string page_slug = 5; optional string parent_id = 6; // UUID; unset mirrors nil *uuid.UUID int32 sort_order = 7; bool open_in_new_tab = 8; string css_class = 9; string item_type = 10; string icon = 11; } // --- datasources.* (datasources.Datasources) --- message DatasourcesResolveBucketRequest { string bucket_id = 1; // UUID } message DatasourcesResolveBucketResponse { DatasourceResult result = 1; } message DatasourcesResolveBucketByKeyRequest { string bucket_key = 1; } message DatasourcesResolveBucketByKeyResponse { DatasourceResult result = 1; } // DatasourceResult mirrors datasources.Result ([]any / map[string]any as // JSON bytes). message DatasourceResult { // JSON array of items. bytes items_json = 1; int32 total = 2; // JSON object; empty when absent. bytes meta_json = 3; } // --- datatables.* (datatables.DataTables) --- message DatatablesGetTableByKeyRequest { string table_key = 1; } message DatatablesGetTableByKeyResponse { DataTableInfo table = 1; } message DatatablesGetRowRequest { string row_id = 1; // UUID } message DatatablesGetRowResponse { DataTableRowInfo row = 1; } message DatatablesListRowsRequest { string table_id = 1; // UUID int32 limit = 2; int32 offset = 3; } message DatatablesListRowsResponse { repeated DataTableRowInfo rows = 1; } message DatatablesCreateRowRequest { string table_id = 1; // UUID bytes data_json = 2; // JSON object } message DatatablesCreateRowResponse { DataTableRowInfo row = 1; } message DatatablesUpdateRowDataRequest { string row_id = 1; // UUID bytes data_json = 2; // JSON object } message DatatablesUpdateRowDataResponse { DataTableRowInfo row = 1; } message DatatablesFindRowByFieldRequest { string table_id = 1; // UUID string field = 2; string value = 3; } message DatatablesFindRowByFieldResponse { // Unset when no row matches (the guest maps that to nil, nil). DataTableRowInfo row = 1; } // DataTableInfo mirrors datatables.Table. message DataTableInfo { string id = 1; // UUID string table_key = 2; string name = 3; int32 row_count = 4; } // DataTableRowInfo mirrors datatables.Row (data column as JSON bytes). message DataTableRowInfo { string id = 1; // UUID string table_id = 2; // UUID bytes data_json = 3; // JSON object int32 sort_order = 4; } // --- users.* (auth.PublicUsers) --- message UsersGetByUsernameRequest { string username = 1; } message UsersGetByUsernameResponse { PublicUserProfile user = 1; } message UsersGetByIdRequest { string id = 1; // UUID } message UsersGetByIdResponse { PublicUserProfile user = 1; } // PublicUserProfile mirrors auth.PublicUserProfile. message PublicUserProfile { string id = 1; // UUID string email = 2; string username = 3; string display_name = 4; string avatar_url = 5; string bio = 6; bool email_verified = 7; string role = 8; } // --- subscriptions.* (subscriptions.Subscriptions) --- message SubscriptionsGetUserTierLevelRequest { string user_id = 1; // UUID } message SubscriptionsGetUserTierLevelResponse { TierLevel tier_level = 1; } // TierLevel mirrors subscriptions.TierLevel. message TierLevel { int32 level = 1; // JSON feature payload. bytes features = 2; } message SubscriptionsGetTierBySlugRequest { string slug = 1; } message SubscriptionsGetTierBySlugResponse { Tier tier = 1; } // Tier mirrors subscriptions.Tier. message Tier { string id = 1; // UUID string name = 2; string slug = 3; int32 level = 4; string description = 5; // JSON feature payload. bytes features = 6; bool is_default = 7; int32 position = 8; } message SubscriptionsListTiersRequest {} message SubscriptionsListTiersResponse { repeated Tier tiers = 1; } message SubscriptionsListActivePlansRequest { string tier_id = 1; // UUID } message SubscriptionsListActivePlansResponse { repeated Plan plans = 1; } // Plan mirrors subscriptions.Plan. message Plan { string id = 1; // UUID string tier_id = 2; // UUID string billing_interval = 3; int32 amount = 4; string currency = 5; bool is_active = 6; google.protobuf.Timestamp created_at = 7; } // --- media.deposit (plugin.Media) --- // MediaDepositRequest mirrors plugin.MediaDeposit. message MediaDepositRequest { // Optional deterministic media row ID (UUID); empty = host generates one. string id = 1; string filename = 2; bytes data = 3; string alt_text = 4; string folder = 5; string source = 6; } // MediaDepositResponse mirrors plugin.MediaResult. message MediaDepositResponse { string id = 1; // UUID // Ready-to-use reference ("media:"). string ref = 2; bool created = 3; } // --- email.send (plugin.EmailSender) --- message EmailSendRequest { string to = 1; string subject = 2; string body = 3; } message EmailSendResponse {} // --- ai.text_call (CoreServices.AITextCall) --- message AiTextCallRequest { string task_key = 1; string system_prompt = 2; string user_message = 3; } message AiTextCallResponse { string text = 1; } // --- ai.tools.* (ai.ToolRegistry) --- // AiToolRegisterRequest mirrors ai.ToolDefinition (minus Handler, which // stays guest-side; execution direction is a runtime-WO concern). message AiToolRegisterRequest { string slug = 1; string name = 2; string description = 3; // JSON encoding of the parameter schema map. bytes parameter_schema_json = 4; } message AiToolRegisterResponse {} // --- bridge.* (plugin.PluginBridge) --- // // The bridge shares in-process Go values today; across sandboxes only the // registration/lookup surface serializes. Typed cross-plugin calls need a // runtime design (see core/docs/wasm-abi.md, open items). message BridgeRegisterServiceRequest { string plugin_name = 1; string service_name = 2; } message BridgeRegisterServiceResponse {} message BridgeGetServiceRequest { string plugin_name = 1; string service_name = 2; } message BridgeGetServiceResponse { bool available = 1; } // --- jobs.submit (plugin.JobRunner) --- message JobsSubmitRequest { string job_type = 1; // JSON job configuration. bytes config_json = 2; } message JobsSubmitResponse {} // --- embeddings.* (plugin.EmbeddingService) --- message EmbeddingsGenerateEmbeddingRequest { string text = 1; } message EmbeddingsGenerateEmbeddingResponse { repeated float embedding = 1; } message EmbeddingsEmbedContentRequest { string source_type = 1; string source_id = 2; // UUID string text = 3; } message EmbeddingsEmbedContentResponse { bool embedded = 1; } message EmbeddingsIsAvailableRequest {} message EmbeddingsIsAvailableResponse { bool available = 1; } // --- rag.* (plugin.RAGService) --- message RagQueryRequest { string query = 1; int32 limit = 2; } message RagQueryResponse { repeated RagResult results = 1; } // RagResult mirrors plugin.RAGResult. message RagResult { string content = 1; double score = 2; map metadata = 3; } message RagOnContentChangedRequest { string content_type = 1; string content_id = 2; // UUID } message RagOnContentChangedResponse {} // --- reviews.* (plugin.ReviewSubmitter) --- // ReviewsSubmitReviewRequest mirrors plugin.SubmitReviewParams. message ReviewsSubmitReviewRequest { string table_id = 1; // UUID string row_id = 2; // UUID int32 overall_rating = 3; string review_text = 4; // JSON encoding of the per-criterion ratings map. bytes ratings_json = 5; repeated string photos = 6; } message ReviewsSubmitReviewResponse { string review_id = 1; } // --- badges.refresh (plugin.BadgeRefresher) --- message BadgesRefreshBadgesRequest { string table_id = 1; // UUID string row_id = 2; // UUID } message BadgesRefreshBadgesResponse {} // --- content.* writes (content.Author, WO-WZ-019) --- // // Imperative page/post authoring. The idempotent seed-time counterparts live // in the provisioner.* family below; these are for runtime authoring and for // the WO-WZ-020 codeless seed runner. message ContentCreatePageRequest { string slug = 1; string parent_slug = 2; // "" = root string title = 3; string template_key = 4; string master_page_key = 5; // "" = none } message ContentCreatePageResponse { string page_id = 1; // UUID // False when a page with this slug already existed (the existing ID is // returned and nothing is modified). bool created = 2; } // PageBlock is one block instance placed on a page (mirrors // plugin.PageBlockConfig / MasterPageBlock). message PageBlock { string block_key = 1; string title = 2; // JSON encoding of the block's content map. bytes content_json = 3; optional string html_content = 4; string slot = 5; int32 sort_order = 6; } message ContentSetPageBlocksRequest { string page_id = 1; // UUID // Full replacement set for the page's draft document blocks. repeated PageBlock blocks = 2; } message ContentSetPageBlocksResponse {} message ContentPublishPageRequest { string page_id = 1; // UUID } message ContentPublishPageResponse {} // ContentSetPageSeoRequest mirrors the CMS UpdatePageSEO surface. Unset // optionals leave the existing value untouched. message ContentSetPageSeoRequest { string page_id = 1; // UUID optional string meta_title = 2; optional string meta_description = 3; optional string og_title = 4; optional string og_description = 5; optional string og_image = 6; optional string focus_keyphrase = 7; optional string canonical_url = 8; optional string robots_directive = 9; optional string twitter_title = 10; optional string twitter_description = 11; optional string twitter_image = 12; } message ContentSetPageSeoResponse {} // ContentUpsertPostRequest creates or updates a blog post by slug (posts live // in the dedicated blog_posts table). message ContentUpsertPostRequest { string slug = 1; string title = 2; // JSON encoding of the BlockNote document. bytes document_json = 3; optional string excerpt = 4; optional string author_profile_id = 5; // UUID optional string featured_image_id = 6; // UUID (e.g. from media.deposit) // Publish immediately after the upsert. bool publish = 7; bool is_featured = 8; } message ContentUpsertPostResponse { string post_id = 1; // UUID bool created = 2; } // --- provisioner.* (plugin.Provisioner, WO-WZ-019) --- // // Idempotent seed/ensure operations, 1:1 with the plugin.Provisioner Go // interface. In the wasm world these are LOAD-TIME capabilities: plugins call // deps.Provisioner from their Load hook (RegisterWithProvisioner cannot cross // the DESCRIBE boundary — host functions are stubbed there). All methods // check-then-create; re-running them is safe. // // EmbedConfig.RenderFunc deliberately does not cross: an ABI-provisioned // embed is template-rendered host-side. A compute-rendered embed needs a // block + hook instead. message ProvisionerEnsureDataTableRequest { string key = 1; string name = 2; string description = 3; // JSON schema of the table (json.RawMessage in DataTableConfig). bytes schema_json = 4; string primary_key = 5; } message ProvisionerEnsureDataTableResponse {} message ProvisionerMergeSiteSettingsRequest { // JSON encoding of the defaults map; existing keys win. bytes defaults_json = 1; } message ProvisionerMergeSiteSettingsResponse {} message ProvisionerEnsureSettingRequest { string key = 1; // JSON encoding of the default value. bytes default_value_json = 2; } message ProvisionerEnsureSettingResponse {} // PageSeed mirrors plugin.PageConfig. message PageSeed { string slug = 1; string parent_slug = 2; string title = 3; string template_key = 4; repeated PageBlock blocks = 5; string detail_source_type = 6; string detail_source_key = 7; string detail_slug_field = 8; bool reconcile_blocks = 9; bool reconcile_template = 10; } message ProvisionerEnsurePageRequest { PageSeed page = 1; } message ProvisionerEnsurePageResponse {} message ProvisionerOverrideSiteSettingsRequest { // JSON encoding of the overrides map; plugin values win. bytes overrides_json = 1; } message ProvisionerOverrideSiteSettingsResponse {} // ProvisionerEnsureMenuItemRequest mirrors plugin.MenuItemConfig under a // named menu. message ProvisionerEnsureMenuItemRequest { string menu_name = 1; string label = 2; string url = 3; string page_slug = 4; int32 sort_order = 5; } message ProvisionerEnsureMenuItemResponse {} // ProvisionerRegisterEmbeddingConfigRequest mirrors plugin.EmbeddingConfigDef. message ProvisionerRegisterEmbeddingConfigRequest { string table_key = 1; string text_template = 2; bool enabled = 3; } message ProvisionerRegisterEmbeddingConfigResponse {} // ProvisionerEnsureEmbedRequest mirrors plugin.EmbedConfig minus RenderFunc // (function values cannot cross; the Template renders host-side). message ProvisionerEnsureEmbedRequest { string key = 1; string title = 2; string description = 3; string icon = 4; string label_field = 5; string template = 6; string data_source_type = 7; // EmbedDataSource.Type string data_source_table_key = 8; // EmbedDataSource.TableKey } message ProvisionerEnsureEmbedResponse {} // ProvisionerEnsureJobScheduleRequest mirrors plugin.JobScheduleConfig. message ProvisionerEnsureJobScheduleRequest { string job_type = 1; string cron_expression = 2; // JSON job configuration. bytes config_json = 3; } message ProvisionerEnsureJobScheduleResponse {} message ProvisionerUpdateDataTableRowFieldRequest { string row_id = 1; // UUID string field_key = 2; // JSON encoding of the value. bytes value_json = 3; } message ProvisionerUpdateDataTableRowFieldResponse {} message ProvisionerDisableOrphanedJobSchedulesRequest { repeated string registered_types = 1; } message ProvisionerDisableOrphanedJobSchedulesResponse {} message ProvisionerEnsurePluginRequest { string name = 1; } message ProvisionerEnsurePluginResponse {} // ProvisionerEnsureCustomColorRequest mirrors plugin.CustomColorConfig. message ProvisionerEnsureCustomColorRequest { string name = 1; string light_value = 2; string dark_value = 3; string source = 4; } message ProvisionerEnsureCustomColorResponse {} // ProvisionerEnsureMediaRequest mirrors plugin.MediaDeposit with a REQUIRED // deterministic id (the template-referable key). Idempotent: an existing id // is a no-op; changed bytes warn rather than overwrite. message ProvisionerEnsureMediaRequest { string id = 1; // UUID, required string filename = 2; bytes data = 3; string alt_text = 4; string folder = 5; string source = 6; } message ProvisionerEnsureMediaResponse {} // --- settings.update_plugin_settings (settings.Updater, WO-WZ-019) --- // SettingsUpdatePluginSettingsRequest replaces the calling plugin's own // settings map. The host binds the target plugin from the caller's identity; // plugin_name is advisory and MUST match it (PERMISSION_DENIED otherwise). message SettingsUpdatePluginSettingsRequest { string plugin_name = 1; // JSON encoding of the full settings map. bytes settings_json = 2; } message SettingsUpdatePluginSettingsResponse {} // --- jobs.progress (plugin.JobHandlerFunc progress callback, WO-WZ-019) --- // JobsProgressRequest reports progress for the CURRENTLY EXECUTING job. The // host correlates it to the job via the invoking HOOK_JOB call's context, so // it is only meaningful while a JOB hook is on the stack; outside one it is // accepted and discarded. message JobsProgressRequest { int32 current = 1; int32 total = 2; string message = 3; } message JobsProgressResponse {} // --- bridge.invoke (plugin.PluginBridge cross-plugin calls, WO-WZ-019) --- // BridgeInvokeRequest calls a method on another plugin's registered bridge // service. The provider answers via HOOK_BRIDGE_CALL (wasm) or an in-process // plugin.BridgeInvokable (bundled). Payloads are opaque bytes — the two // plugins agree on the encoding (JSON by convention). message BridgeInvokeRequest { string plugin_name = 1; string service_name = 2; string method = 3; bytes payload = 4; } message BridgeInvokeResponse { bytes payload = 1; } // --- http.request (egress.Client host-mediated outbound HTTP, ADR 0023) --- // HttpRequestRequest asks the host to perform one outbound HTTPS request on // the plugin's behalf. The host enforces the plugin's egress grant (host // patterns + response cap), the platform denylist, the SSRF guard, and the // redirect policy before any bytes leave — the guest never touches a socket. // Names differ from the HANDLE_HTTP hook pair (HttpRequest/HttpResponse in // http.proto), which is inbound traffic INTO the guest mux. message HttpRequestRequest { string method = 1; // Absolute https URL (scheme required; http is refused by policy). string url = 2; map headers = 3; bytes body = 4; // Optional guest-side deadline. It may only LOWER the host's per-fetch // ceiling, never raise it; zero means the host ceiling applies. uint32 timeout_ms = 5; } // HttpRequestResponse is the fully buffered upstream response. Bodies larger // than the granted cap fail the call (EGRESS_DENIED is policy; oversize is // INTERNAL with a too-large message) — never a silent truncation. message HttpRequestResponse { int32 status = 1; map headers = 2; bytes body = 3; // URL that produced the response after any (allowlist-validated) // redirects. string final_url = 4; }