Adds ListPosts(ctx, ListPostsParams) to the content.Content capability so
wasm plugins can list posts with bodies — the per-plugin Postgres role
denies direct public.blog_posts reads (42501) and GetPost only fetches a
single post's metadata. Extends PostInfo with Body, AuthorName, AuthorSlug
and PublishedAt (additive; existing get_post golden unchanged). Wires all
layers: interface, ABI PostInfo/ContentListPosts{Request,Response} messages
(buf breaking clean), guest stub, and a deterministic content_list_posts
golden replayed by the cms host parity test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
559 lines
12 KiB
Protocol Buffer
559 lines
12 KiB
Protocol Buffer
// 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/invoke.proto";
|
|
|
|
option go_package = "git.dev.alexdunmow.com/block/core/abi/v1;abiv1";
|
|
|
|
// HostCallRequest is the generic guest→host capability envelope.
|
|
message HostCallRequest {
|
|
// Capability method, "<family>.<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<string, string> 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// --- 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:<uuid>").
|
|
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<string, string> 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 {}
|