rpc-api-contract
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRPC / API contract
RPC / API 契约
How to write a business action that any channel — web, WhatsApp agent, MCP, cron, third party — can call without surprises. The same operation, one definition.
Why this exists (canon): the blocker to exposing our logic to agents was never "Postgres vs TypeScript" — it was the lack of a uniform contract (inconsistent naming, heterogeneous return types, no envelope, uneven idempotency). This skill is the operating procedure; the full rationale and the LaPyme benchmark live in the company brain page+estrategia/caso-lapyme. We assemble public conventions: Google AIP (resource-oriented + custom methods), RFC 7807/9457 (errors), Stripe / IETF Idempotency-Key, PostgREST api-schema.productos/profundizacion-tecnica/rpc-contract
如何编写业务动作,让任何渠道(web、WhatsApp agent、MCP、cron、第三方)都能无歧义地调用。同一操作,唯一定义。
制定背景(核心准则): 将我们的逻辑暴露给Agent的阻碍从来不是“选Postgres还是TypeScript”,而是缺少统一契约(命名不一致、返回类型不统一、无返回封装、幂等性实现参差不齐)。本规范是操作流程;完整的设计依据和LaPyme基准测试见公司知识库页面以及estrategia/caso-lapyme。我们整合了以下公开规范:Google AIP(面向资源 + 自定义方法)、RFC 7807/9457(错误格式)、Stripe / IETF Idempotency-Key、PostgREST api-schema。productos/profundizacion-tecnica/rpc-contract
When to apply
适用场景
- Creating a new business action (a mutation or a non-trivial read meant to be called by a channel).
- Exposing an existing function to a new channel (web → agent/MCP/third party).
- Reviewing a migration that adds/changes a callable function.
Forward-only: applies to functions created from 2026-06-16. Legacy / unprefixed / callables stay as they are until deliberately migrated. No mass retrofit — strangler fig (wrap on demand when a function gets exposed to a new channel).
rpc_*fn_- 创建新的业务动作(供渠道调用的变更操作或非简单查询操作)。
- 将现有函数暴露给新渠道(从web扩展到agent/MCP/第三方)。
- 评审新增或修改可调用函数的数据库迁移。
仅向前适用: 仅对2026-06-16之后创建的函数生效。存量的 / 无前缀 / 可调用函数保持原样,除非主动迁移。不进行批量改造 —— 采用绞杀者模式(当函数需要暴露给新渠道时按需封装)。
rpc_*fn_The architecture (where this fits)
架构定位(本规范所处层级)
web UI · WhatsApp agent · MCP · cron ← channels (each a thin adapter)
│ all call the same core
▼
schema `api` = business actions (this contract) ← single definition
▼
public / private / <domain> schemas = internal logic, triggers, helpersA channel (the WhatsApp agent, the web) brings its own LLM/UI. An MCP server for users is just the actions wrapped in MCP protocol — no logic of its own (same model as LaPyme's + ). Both bottom out on .
api.*api.*mcp.*api.* web UI · WhatsApp agent · MCP · cron ← channels (each a thin adapter)
│ all call the same core
▼
schema `api` = business actions (this contract) ← single definition
▼
public / private / <domain> schemas = internal logic, triggers, helpers渠道(如WhatsApp agent、web端)自带各自的LLM/UI。面向用户的MCP服务器只是用MCP协议封装了动作 —— 本身不包含业务逻辑(与LaPyme的 + 模式一致)。两者最终都调用。
api.*api.*mcp.*api.*The 9 rules
9条规则
R1 — The boundary is the schema, not the prefix. Exposed actions live in a dedicated schema (PostgREST serves one schema and generates its OpenAPI). Everything else is internal and not exposed. This is what makes the surface enumerable → OpenAPI → SDK + docs + MCP tools.
apiR2 — Naming (forward-only). Exposed: (AIP style): , , , , , , , . New internal: . Legacy stays.
api.<verb>_<noun>crear_actualizar_confirmar_anular_revertir_listar_obtener_calcular_…_previewfn_R3 — Success envelope, never . Every returns :
voidapi.*jsonbjson
{ "data": { "...": "the affected resource(s): ids, amounts, state" },
"effects": { "...": "what the command changed, grouped by domain" },
"warnings":[ { "code": "STOCK_NEGATIVO", "message": "..." } ] }data{}nulleffectsreferences/<project>.mdeffectsoksuccessRETURNS voidapi.*R4 — Errors, RFC 7807 style. with a parseable, prefixed code: . The gateway maps it to (). Codes in , domain-prefixed where it helps ().
RAISE EXCEPTIONRESOURCE_NOT_FOUND: educador % no existeapplication/problem+json{type, title, status, detail, code}MAYUS_SNAKELIQUIDACION_PERIODO_CERRADOR5 — Idempotency, explicit. Mutations accept and use on a unique constraint — never a prior (races under READ COMMITTED). Declare the state in the COMMENT: or .
p_idempotency_keyINSERT … ON CONFLICTIF EXISTS[IDEMPOTENT: ON CONFLICT (...)][NOT IDEMPOTENT: caller guarantees single invocation]R6 — Uniform auth. + + an internal access check ( / ) that does not depend on the caller's RLS. Identity is resolved server-side (closure / JWT) — never a caller-supplied argument an LLM could forge.
SECURITY DEFINERSET search_path = ''establecimiento_idid_hubR7 — Documentation is mandatory. on every : what it does, its idempotency mark (R5), and which it produces. (This is what enforces and what auto-generates the docs.)
COMMENT ON FUNCTIONapi.*effectsdb-reviewerR8 — Preview/confirm for costly or irreversible ops. Expose a that returns the same shape without committing (dry-run), plus a that executes.
…_previeweffectsconfirmar_…R9 — Version without breaking. Don't mutate a live contract. New signature → , deprecate the old ≥ 2 cycles. Version the schema ( → ) if needed.
api.*_v2apiapi_v1R1 — 边界是schema,而非前缀。 对外暴露的动作存放在专用的**** schema中(PostgREST服务单个schema并生成其OpenAPI)。其他所有内容都是内部的,不对外暴露。这使得暴露面可枚举 → 生成OpenAPI → 生成SDK + 文档 + MCP工具。
apiR2 — 命名规则(仅向前适用)。 对外暴露的函数命名为(AIP风格):、、、、、、、。新增的内部函数前缀为。存量函数保持不变。
api.<动词>_<名词>crear_actualizar_confirmar_anular_revertir_listar_obtener_calcular_…_previewfn_R3 — 成功返回封装,禁止返回。 每个函数都返回:
voidapi.*jsonbjson
{ "data": { "...": "the affected resource(s): ids, amounts, state" },
"effects": { "...": "what the command changed, grouped by domain" },
"warnings":[ { "code": "STOCK_NEGATIVO", "message": "..." } ] }data{}nulleffectsreferences/<project>.mdeffectsoksuccessapi.*RETURNS voidR4 — 错误格式遵循RFC 7807。 时使用可解析的、带前缀的错误码:。网关会将其映射为格式()。错误码使用格式,必要时添加领域前缀(如)。
RAISE EXCEPTIONRESOURCE_NOT_FOUND: educador % no existeapplication/problem+json{type, title, status, detail, code}MAYUS_SNAKELIQUIDACION_PERIODO_CERRADOR5 — 幂等性显式声明。 变更操作接受参数,并在唯一约束上使用****实现幂等 —— 禁止先执行判断(在READ COMMITTED隔离级别下会有竞态问题)。在函数COMMENT中声明幂等状态: 或 。
p_idempotency_keyINSERT … ON CONFLICTIF EXISTS[IDEMPOTENT: ON CONFLICT (...)][NOT IDEMPOTENT: caller guarantees single invocation]R6 — 统一鉴权。 使用 + + 内部访问校验( / ),不依赖调用方的RLS。身份在服务端解析(闭包 / JWT)—— 绝对不能使用调用方传入的参数,因为LLM可能伪造该参数。
SECURITY DEFINERSET search_path = ''establecimiento_idid_hubR7 — 强制要求文档。 每个函数都必须添加:说明函数用途、幂等性标记(R5)以及产生的。(这是会强制执行的检查项,也是自动生成文档的依据。)
api.*COMMENT ON FUNCTIONeffectsdb-reviewerR8 — 高成本或不可逆操作需提供预览/确认机制。 提供函数,以相同的结构返回结果但不提交(试运行),同时提供函数执行实际操作。
…_previeweffectsconfirmar_…R9 — 版本化不破坏兼容性。 不要修改正在使用的契约。新增签名 → 加后缀,旧版本至少保留2个周期后再废弃。必要时可对schema做版本化( → )。
api.*_v2apiapi_v1Template (new compliant action)
模板(符合规范的新增动作)
sql
create or replace function api.crear_<noun>(
p_body jsonb,
p_idempotency_key text
) returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_actor <type> := <resolve from session, NOT from p_body>; -- R6
v_id <type>;
v_effects jsonb := '{}'::jsonb;
begin
-- validate (read-only) → RAISE 'CODE: detalle' on failure (R4)
insert into <domain>.<table> (...)
values (...)
on conflict (<idempotency unique key>) do nothing -- R5
returning id into v_id;
if v_id is null then
select id into v_id from <domain>.<table> where <idempotency key> = ...;
end if;
-- collect side-effects into v_effects, grouped by domain (R3)
return jsonb_build_object(
'data', jsonb_build_object('<noun>_id', v_id, 'estado', '...'),
'effects', v_effects,
'warnings', '[]'::jsonb
);
end; $$;
comment on function api.crear_<noun>(jsonb, text) is
'Crea <noun>. [IDEMPOTENT: ON CONFLICT (<key>)]. effects: {<dominios>}.';sql
create or replace function api.crear_<noun>(
p_body jsonb,
p_idempotency_key text
) returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_actor <type> := <resolve from session, NOT from p_body>; -- R6
v_id <type>;
v_effects jsonb := '{}'::jsonb;
begin
-- validate (read-only) → RAISE 'CODE: detalle' on failure (R4)
insert into <domain>.<table> (...)
values (...)
on conflict (<idempotency unique key>) do nothing -- R5
returning id into v_id;
if v_id is null then
select id into v_id from <domain>.<table> where <idempotency key> = ...;
end if;
-- collect side-effects into v_effects, grouped by domain (R3)
return jsonb_build_object(
'data', jsonb_build_object('<noun>_id', v_id, 'estado', '...'),
'effects', v_effects,
'warnings', '[]'::jsonb
);
end; $$;
comment on function api.crear_<noun>(jsonb, text) is
'Crea <noun>. [IDEMPOTENT: ON CONFLICT (<key>)]. effects: {<dominios>}.';Per-project specifics
各项目特有说明
The key taxonomy and the schema bootstrap differ per repo. See (e.g. , ) for that repo's effect domains, identity resolution, and which legacy functions are already wrapped.
effectsapireferences/<project>.mdreferences/perennia-backoffice.mdreferences/gestionganadera.mdeffectsapireferences/<project>.mdreferences/perennia-backoffice.mdreferences/gestionganadera.mdRelationship to other skills
与其他规范的关系
- enforces this on every migration (R1/R3/R5/R6/R7). If a check here isn't in db-reviewer yet, flag it.
db-reviewer - covers the performance side (volatility, indexes).
supabase-postgres-best-practices - Channels (WhatsApp agent, MCP) must call — not tables directly, not generic CRUD.
api.*
- 会在每次数据库迁移时强制执行本规范(R1/R3/R5/R6/R7)。如果这里的检查项还未加入db-reviewer,请反馈。
db-reviewer - 涵盖性能相关的最佳实践(波动性、索引)。
supabase-postgres-best-practices - 各渠道(WhatsApp agent、MCP)必须调用—— 禁止直接访问表,也不能使用通用CRUD。
api.*