core/abi/proto/v1/capability.proto
Alex Dunmow e663b77b47 content: add Content.ListPosts capability for blog listing
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>
2026-07-04 01:00:14 +08:00

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 {}