Skip to content

abp.proto — contrato normativo

syntax = "proto3";
// ABP — Allocation Booking Protocol, v1 (skeleton em design).
// A forma NORMATIVA dos comandos da ABP. Itera junto com spec/abp-v0.md.
// Transport: gRPC + gRPC-Web + JSON method-style sobre HTTP (sem REST), padrão Armeria.
package abp.v1;
// ─────────────────────────────────────────────────────────────────────────────
// Serviço: um Channel credenciado booka Units de um Bucket contra o Registry.
// ─────────────────────────────────────────────────────────────────────────────
service AllocationBooking {
// Leitura idempotente da disponibilidade de Units num Bucket.
rpc Check (CheckRequest) returns (CheckResponse);
// Aloca Units num Booking, com TTL. Cada item é EXPLÍCITO (Unit específica) ou
// QUANTITATIVO (N lugares do Bucket; engine escolhe = matchmaking). Multi-item, com
// política de fulfillment (atômico / o-que-dá / threshold). (EPP create)
// O Booking = o "Order"/carrinho (N allocations, 1+ Buckets/domínios).
rpc Hold (HoldRequest) returns (HoldResponse);
// Cart incremental: acrescenta itens a um Booking ainda HELD (carrinho aberto).
rpc AddItems (AddItemsRequest) returns (HoldResponse);
// Cart incremental: remove allocations de um Booking ainda HELD.
rpc RemoveItems (RemoveItemsRequest) returns (Booking);
// hold → comprometido (PII do comprador anexada).
rpc Confirm (ConfirmRequest) returns (Booking);
// confirmado → pago (captura via Billing). Separado do Confirm.
rpc Settle (SettleRequest) returns (Booking);
// pago → emite o artefato. Idempotente.
rpc Fulfill (FulfillRequest) returns (Booking);
// FULFILLED → USED: check-in na catraca (gate). Validado contra a capacidade FÍSICA:
// standby (overbook) só entra se houver vaga (no-show), senão → BUMPED. (= boarding aéreo)
rpc Redeem (RedeemRequest) returns (Booking);
// pago/emitido → estornado real (devolve Unit ao Bucket). Booking inteiro OU subconjunto
// de allocations (refund parcial/por-item).
rpc Refund (RefundRequest) returns (Booking);
// transferência nominal pós-venda (troca o titular da Allocation). (EPP transfer)
rpc Transfer(TransferRequest)returns (Booking);
// estende o TTL do hold. (EPP renew)
rpc Extend (ExtendRequest) returns (Booking);
// libera hold/booking explicitamente (= auto no expiry). (EPP delete)
rpc Release (ReleaseRequest) returns (Booking);
// estado de um Booking. (EPP info)
rpc Get (GetRequest) returns (Booking);
// ── Eixo de RESOLUÇÃO (ortogonal à FSM; ver spec §5.1) ──
// Concretiza allocations ANÔNIMAS → Units escolhidas pelo Channel. Por padrão só age
// sobre ANONYMOUS (CONCRETE → ALREADY_ASSIGNED); `overwrite` faz reseat (libera a antiga atômico).
rpc Assign (AssignRequest) returns (Booking);
// Concretiza allocations ANÔNIMAS → o Registry escolhe (best-available/strategy).
rpc AutoAssign (AutoAssignRequest) returns (Booking);
// Devolve uma Allocation CONCRETE → ANONYMOUS (larga a Unit, mantém a vaga/contagem).
rpc Unassign (UnassignRequest) returns (Booking);
// Troca atômica de Units entre DUAS allocations CONCRETE (sem janela de assento solto).
rpc Swap (SwapRequest) returns (SwapResponse);
// NOTA: ingestão/admin (publicar Repository, materializar/abrir/fechar Buckets) e
// seleção de producer/tenant NÃO fazem parte do protocolo — são responsabilidade da
// IMPLEMENTAÇÃO de servidor (no caso Eventheca: o produto Reservations). O ABP é a
// superfície de CONSUMO da saga, aberta por padrão (auth/authz por cima = da impl).
}
// ─────────────────────────────────────────────────────────────────────────────
// FSM do Booking (ver spec §5).
// ─────────────────────────────────────────────────────────────────────────────
enum BookingState {
BOOKING_STATE_UNSPECIFIED = 0;
HELD = 1; // TTL correndo
CONFIRMED = 2; // comprador comprometido
PAID = 3; // pagamento capturado
FULFILLED = 4; // artefato emitido
USED = 5; // check-in (sinal do Access)
REFUNDED = 6; // estorno real
EXPIRED = 7; // TTL estourou
RELEASED = 8; // liberação explícita
}
// Result-codes tipados (ver spec §7).
enum ResultCode {
RESULT_CODE_UNSPECIFIED = 0;
OK = 1;
UNIT_UNAVAILABLE = 2;
OVERSELL_PREVENTED = 3;
HOLD_EXPIRED = 4;
POLICY_VIOLATION = 5;
IDEMPOTENT_REPLAY = 6;
QUOTA_EXHAUSTED = 7; // reservado p/ crédito-de-Channel (futuro)
ALREADY_ASSIGNED = 8; // Assign sobre allocation já CONCRETE sem `overwrite`
NOT_RESOLVED = 9; // Unassign/Swap sobre allocation ainda ANONYMOUS
BUMPED = 10; // Redeem negado: capacidade física esgotada (standby de overbook)
}
// Natureza da disponibilidade de um Bucket — conceito do PROTOCOLO (agnóstico). O vocabulário
// de domínio (Seat/GA no Tickets, vaga, credencial) mora no catalog/implementador, NÃO aqui.
enum BucketKind {
BUCKET_KIND_UNSPECIFIED = 0;
QUALITATIVE = 1; // disponibilidade endereçável: tem Units distintas → resolução aplicável
QUANTITATIVE = 2; // disponibilidade fungível: só capacidade, sem Unit → resolução N/A
}
// Eixo de RESOLUÇÃO de uma Allocation — ORTOGONAL à FSM comercial (ver spec §5.1).
// Permite Hold/Confirm/Settle/Fulfill com a Unit ainda indefinida. A ORDEM entre os dois
// eixos (ex.: "concretizar antes do Fulfill") NÃO é do protocolo — é política do
// implementador/catalog (Tickets pode exigir CONCRETO p/ Estatuto do Torcedor; Parking não).
enum ResolutionState {
RESOLUTION_STATE_UNSPECIFIED = 0;
ANONYMOUS = 1; // contagem garantida no Bucket; Unit ainda TBD (sem oversell)
CONCRETE = 2; // Unit específica atribuída
}
// Política de fulfillment de um Hold multi-item (server-default OU por pedido; ver spec §6).
enum FulfillmentMode {
FULFILLMENT_MODE_UNSPECIFIED = 0; // trata como ATOMIC
ATOMIC = 1; // tudo-ou-nada (cross-Bucket = 2 fases)
BEST_EFFORT = 2; // reserva o que dá; resultado por-item
THRESHOLD = 3; // sucesso se ao menos `min_units` vingarem
}
// ─────────────────────────────────────────────────────────────────────────────
// Mensagens (skeleton — campos vão amadurecer com a spec).
// ─────────────────────────────────────────────────────────────────────────────
// Referência a um Bucket (a disponibilidade disputada). producer/tenant NÃO entra —
// é roteamento da impl (1 servidor ABP por producer).
message BucketRef {
string repository_id = 1; // Repository = evento/jogo (agrega Buckets)
string bucket_id = 2; // Bucket = setor × tipo; partição do escritor único.
// A natureza (QUALITATIVE/QUANTITATIVE) é determinada pelo bucket_id — não se repete no ref;
// é exposta informativamente no Discovery (BucketSummary.kind).
}
// Referência a uma Unit CONCRETA dentro de um Bucket.
message UnitRef {
BucketRef bucket = 1;
string unit_id = 2; // só em Bucket QUALITATIVE (quantitativo não tem Unit endereçável)
}
// Uma Unit plugada a um titular dentro de um Booking. Carrega o eixo de RESOLUÇÃO:
// ANONYMOUS = vaga garantida no `bucket`, `unit_id` vazio; CONCRETE = `unit_id` preenchido.
message Allocation {
string allocation_id = 1; // id dentro do Booking (alvo de Assign)
BucketRef bucket = 2; // onde a vaga vive (sempre presente)
string unit_id = 3; // "" enquanto ANONYMOUS
ResolutionState resolution = 4;
string holder_ref = 5; // titular (comprador) — PII via customer-identity
bool standby = 6; // está no headroom de overbook (sem vaga física garantida)
}
message Booking {
string booking_id = 1;
string channel_id = 2; // quem bookou (registrar)
BookingState state = 3;
repeated Allocation allocations = 4; // N Units (multi-item)
string booking_token = 5; // p/ idempotência de fulfill
int64 hold_expires_at_unix = 6; // TTL
ResultCode result = 7;
}
// Check aceita Units concretas E/OU Buckets (contagem disponível — p/ Hold quantitativo).
message CheckRequest {
repeated UnitRef units = 1; // checar Units específicas
repeated BucketRef buckets = 2; // checar quantos lugares restam num Bucket
}
message BucketAvailability { BucketRef bucket = 1; int32 available = 2; int32 capacity = 3; }
message CheckResponse {
repeated UnitRef available = 1;
repeated UnitRef unavailable = 2;
repeated BucketAvailability bucket_availability = 3;
}
// Pedido QUANTITATIVO: N lugares de um Bucket, sem dizer quais (engine resolve depois).
// = MakeReservationItem(repository, grouper, group, quantity, strategy) do PlugHub. O
// `bucket.bucket_id` é a folha de segmentação obtida no Discovery (ListBuckets).
message QuantityRequest {
BucketRef bucket = 1;
int32 quantity = 2;
string strategy = 3; // best-available / adjacência / faixa de preço (livre por ora; estruturar)
}
// Um item de um Hold: EXPLÍCITO (Unit específica) OU QUANTITATIVO (N do Bucket).
message HoldItem {
oneof target {
UnitRef explicit = 1; // cadeira/vaga específica
QuantityRequest quantitative = 2; // N lugares; nasce ANONYMOUS
}
string holder_ref = 3; // titular do item (ex.: cada CPF na compra família)
}
message HoldRequest {
string channel_id = 1;
repeated HoldItem items = 2;
int64 ttl_seconds = 3;
string idempotency_key = 4;
FulfillmentMode fulfillment = 5;
int32 min_units = 6; // usado quando fulfillment = THRESHOLD
}
// Resultado por-item (relevante p/ BEST_EFFORT / THRESHOLD; em ATOMIC ou tudo OK ou nada).
message AllocationResult {
int32 item_index = 1; // índice no HoldRequest.items
ResultCode result = 2;
Allocation allocation = 3; // preenchido quando result = OK
}
message HoldResponse {
Booking booking = 1; // agregado com as allocations que vingaram
repeated AllocationResult results = 2; // detalhe por-item
}
// ── Eixo de resolução: concretizar / mexer em allocations ──
message AssignItem {
string allocation_id = 1; // qual allocation do Booking
string unit_id = 2; // Unit concreta escolhida (deve pertencer ao Bucket da allocation)
bool overwrite = 3; // true = reseat (allocation já CONCRETE; libera a antiga atômico)
}
message AssignRequest {
string booking_id = 1;
repeated AssignItem items = 2;
string idempotency_key = 3;
string reason = 4; // p/ resolução INVOLUNTÁRIA (re-acomodação por inventory-shrink) — audit/notificação
}
// Registry escolhe as Units pras allocations anônimas (todas, ou as do critério).
message AutoAssignRequest {
string booking_id = 1;
string strategy = 2; // best-available etc; "" = todas as anônimas
string idempotency_key = 3;
string reason = 4; // idem Assign (re-acomodação)
}
message UnassignRequest {
string booking_id = 1;
repeated string allocation_ids = 2; // allocations CONCRETE → voltam a ANONYMOUS
string idempotency_key = 3;
string reason = 4; // ex.: "inventory_block" (Unit puxada pela organização)
}
// Referência a uma allocation específica (pode ser de outro Booking → swap entre titulares).
message AllocationRef { string booking_id = 1; string allocation_id = 2; }
message SwapRequest {
AllocationRef a = 1; // troca a Unit de `a` com a de `b` (ambas CONCRETE)
AllocationRef b = 2;
string idempotency_key = 3;
}
message SwapResponse { Booking a = 1; Booking b = 2; } // os dois Bookings afetados
message ConfirmRequest { string booking_id = 1; string buyer_ref = 2; string idempotency_key = 3; }
message SettleRequest { string booking_id = 1; string payment_ref = 2; string idempotency_key = 3; }
message FulfillRequest { string booking_id = 1; string idempotency_key = 2; }
// Refund do Booking inteiro (allocation_ids vazio) OU de um subconjunto (refund por-item).
message RefundRequest { string booking_id = 1; string reason = 2; string idempotency_key = 3; repeated string allocation_ids = 4; }
// Cart incremental (Booking ainda HELD).
message AddItemsRequest { string booking_id = 1; repeated HoldItem items = 2; FulfillmentMode fulfillment = 3; int32 min_units = 4; string idempotency_key = 5; }
message RemoveItemsRequest { string booking_id = 1; repeated string allocation_ids = 2; string idempotency_key = 3; }
message TransferRequest { string booking_id = 1; string new_holder_ref = 2; string idempotency_key = 3; }
message ExtendRequest { string booking_id = 1; int64 additional_seconds = 2; }
message ReleaseRequest { string booking_id = 1; }
message GetRequest { string booking_id = 1; }
// Check-in na catraca de UMA allocation. Standby pode voltar BUMPED se a física esgotou.
message RedeemRequest { string booking_id = 1; string allocation_id = 2; string idempotency_key = 3; }
// ─────────────────────────────────────────────────────────────────────────────
// Service Discovery — LEITURA da saga (browse): o consumidor enxerga, conforme suas
// permissões, quais Repositories existem e a disponibilidade dentro deles. Fiel ao
// PlugHub (RepoGrouper/RepoGroup/Item* + sp_ehub_repo_*). Segmentação em ÁRVORE
// (parent/is_parent), dois níveis: Repository (segmenta eventos) e Bucket (segmenta a
// disponibilidade dentro de um evento — a folha = o Bucket). Identidade do consumidor
// (Subject/Channel) p/ filtrar por permissão vem da SESSÃO, não destas mensagens.
// ─────────────────────────────────────────────────────────────────────────────
service Discovery {
// nível Repository (segmentar eventos: competição, estádio, data, rodada…)
rpc ListRepositoryGroupers (ListRepositoryGroupersRequest) returns (ListRepositoryGroupersResponse);
rpc ListRepositoryGroups (ListRepositoryGroupsRequest) returns (ListRepositoryGroupsResponse);
rpc ListRepositories (ListRepositoriesRequest) returns (ListRepositoriesResponse);
// nível Bucket (segmentar a disponibilidade dentro de um Repository: setor, tipo…)
rpc ListBucketGroupers (ListBucketGroupersRequest) returns (ListBucketGroupersResponse);
rpc ListBuckets (ListBucketsRequest) returns (ListBucketsResponse); // folha = Bucket + contagem
// nível Unit (cadeiras individuais de UM Bucket — p/ lugar marcado / mapa de assento).
// Aberto (read); escopo de 1 Bucket por vez (limita payload). GA não tem Unit endereçável.
rpc ListUnits (ListUnitsRequest) returns (ListUnitsResponse); // = Item do PlugHub
}
// Dimensão de agrupamento (árvore). Ex.: grouper "Campeonato", "Estádio", "Setor".
message Grouper {
string id = 1;
string parent = 2; // "" = raiz
bool is_parent = 3; // tem filhos
map<string, string> metadata = 4;
}
// Um valor dentro de um Grouper (árvore). Ex.: grouper=Campeonato, group=Brasileirão.
message Group {
string id = 1;
string grouper = 2;
string parent = 3;
bool is_parent = 4;
int32 count = 5; // repos (nível Repo) ou buckets (nível Bucket) no grupo
map<string, string> metadata = 6;
}
// Resumo de um Repository pra browse (metadata enriquece: campeonato, rodada, data…).
message RepositorySummary {
string repository_id = 1;
string repo_type = 2;
map<string, string> metadata = 3;
}
// Resumo de um Bucket pra browse — carrega a disponibilidade (o "Check-por-Bucket" do browse).
message BucketSummary {
BucketRef bucket = 1;
BucketKind kind = 2; // QUALITATIVE (lista Units) vs QUANTITATIVE (só contagem)
int32 available = 3;
int32 capacity = 4;
map<string, string> metadata = 5; // setor, tipo…
}
// Filtros opcionais (espelham os params do PlugHub: GrouperExact/Group/Parent/ListAll/RepoType).
message ListRepositoryGroupersRequest { string grouper_exact = 1; string parent = 2; bool list_all = 3; }
message ListRepositoryGroupersResponse { repeated Grouper groupers = 1; }
message ListRepositoryGroupsRequest { string grouper = 1; string group_exact = 2; string parent = 3; bool list_all = 4; }
message ListRepositoryGroupsResponse { repeated Group groups = 1; int32 return_count = 2; }
message ListRepositoriesRequest { string grouper = 1; string group = 2; string repo_type = 3; }
message ListRepositoriesResponse { repeated RepositorySummary repositories = 1; int32 return_count = 2; }
message ListBucketGroupersRequest { string repository_id = 1; string grouper_exact = 2; string parent = 3; bool list_all = 4; }
message ListBucketGroupersResponse { repeated Grouper groupers = 1; }
message ListBucketsRequest { string repository_id = 1; string grouper = 2; string group = 3; }
message ListBucketsResponse { repeated BucketSummary buckets = 1; int32 return_count = 2; }
// Cadeira individual pra browse de lugar marcado (= Item do PlugHub). booking_status só
// distingue available vs ocupado (FSM completa fica no Booking; aqui é leitura leve).
message UnitSummary {
string unit_id = 1;
bool available = 2; // false se já em hold/vendido
string label = 3; // "A12"
map<string, string> metadata = 4; // posição/atributos pro mapa de assento
}
message ListUnitsRequest { BucketRef bucket = 1; } // 1 Bucket por vez
message ListUnitsResponse { repeated UnitSummary units = 1; int32 return_count = 2; }