ADR-0013: Capabilities messages.forward/star/unstar/pin/unpin/markRead
- Status: aceito
- Data: 2026-07-12
Contexto
Primeiro item da fila de capabilities novas planejada após a Epic 7 (ADR-0012), coberta pelos 8 relatórios de pesquisa já produzidos naquela rodada (escopo original era só messages.edit/ delete + chats.*, mas os agentes de pesquisa levantaram candidatas adicionais em "seções secundárias" que ficaram registradas para revisão futura). Diferente da Epic 7 (fila via Workflow/multi-agente), esta rodada é executada sequencialmente, sem múltiplos agentes em paralelo, a pedido do usuário.
Reconfirmação de escopo (releitura dos relatórios salvos + verificação pontual ao vivo contra gh api para os casos ambíguos — QuePasa e WPPConnect, cujos relatórios originais focaram só nas 6 capabilities do ADR-0012):
| Capability | waha | evolution | uazapi | zapi | wuzapi | whapi | quepasa | wppconnect | cobertura |
|---|---|---|---|---|---|---|---|---|---|
messages.forward | ✅ | — | — (busca exaustiva, confirmado ausente) | ✅ | — (busca exaustiva) | ✅ | — (confirmado via legacy/routes.go, sem rota) | ✅ | 4/8 |
messages.star/unstar | ✅ | — | — (busca exaustiva, confirmado ausente) | — | — | ✅ | — | ✅ | 3/8 |
messages.pin/unpin | ✅ | — | ✅ | ✅ | — (busca exaustiva em handlers.go, zero ocorrência de "Pin") | ✅ | — (confirmado ausente) | — (confirmado ausente, só chats.pin de nível de conversa) | 4/8 |
messages.markRead | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (markAsRead) | ✅ | — (só chats.markRead/markUnread de nível de conversa existem, já implementados) | 7/8 |
messages.markRead tem a cobertura mais forte (7/8) — todo provider exceto WPPConnect confirma um endpoint de nível de MENSAGEM, distinto do chats.markRead (nível de conversa, ADR-0012). Nas ausências marcadas "confirmado", a pesquisa foi exaustiva (busca por padrão de nome de rota no spec/código-fonte completo, não just "não encontrei numa leitura superficial") — tratadas como limitação real do provider, não gap de pesquisa.
Decisão
- Todos os 6 métodos são opcionais em
MessagesApi(mesmo padrão deedit/delete, ADR-0012) — nenhum namespace novo, mudança 100% aditiva. - Tipos novos em
src/core/types.ts:ForwardMessageInput { to, messageId, fromChatId? }→forwardretornaPromise<SentMessage>(produz uma mensagem nova, comoedit).fromChatIdé opcional: a maioria dos providers resolve o chat de origem a partir do própriomessageId(formato do WhatsApp normalmente autoidentifica); só a Z-API precisa de um campo de origem explícito (messagePhone) — o adapter Z-API usa esse campo quando presente, mas o contrato não o torna obrigatório para não penalizar os demais 3 providers que não precisam dele.StarMessageInput { to, messageId }— mesma forma parastar/unstar(a direção é decidida pelo MÉTODO chamado, não por um campo booleano — mesmo padrão decontacts.block/unblock).PinMessageInput { to, messageId }— mesma forma parapin/unpin, sem campo de duração/prazo: WAHA exigeduration(não confirmado o formato exato), Whapi usa enumday/week/month, uazapi usa dias inteiros com fallback silencioso para 30 se inválido — nenhum formato converge (mesmo critério já usado parachats.muteno ADR-0012); cada adapter decide seu próprio valor/sentinela e documenta a escolha.MarkMessageReadInput { to, messageId }— nível de MENSAGEM, distinto deMarkMessageReadInput... (não confundir comchats.markRead(chatId), que já existe).
ConnectorMessagesApiganha os 6 métodos correspondentes, todos usando o helpercallMessagesMethodjá generalizado no ADR-0012 (nenhum guard-rail novo necessário).chatId/tonormalizado vianormalizeChatId(mesmo tratamento decontacts.*/chats.*) — não é opaco comogroupId.
Justificativa
- Reaproveita 100% a infraestrutura já criada no ADR-0012 (
callMessagesMethod,MessagesApiopcional) — nenhuma decisão arquitetural nova além dos tipos de input. - Separar
star/unstarepin/unpinem capabilities distintas (não um booleano) segue o precedente decontacts.block/unblockechats.archive/unarchive— permite a um adapter declarar só uma direção se só uma for confirmada (não ocorreu nesta pesquisa, mas mantém consistência de estilo com o resto do enum). - Não incluir duração em
PinMessageInputevita inventar um formato sem convergência cross-provider (mesmo critério dochats.mute, ADR-0012).
Consequências
- Enum de capabilities cresce de 40 para 46.
messages.forward/star/pintêm cobertura mais desigual (3-4/8) quemessages.markRead(7/8) — WPPConnect fica sem nenhuma das 3 primeiras por limitação real confirmada (messages.pin/markReadde nível de mensagem não existem no provider; sóchats.*de nível de conversa, já implementado). QuePasa fica semforward/pin/starpela mesma razão (rotas legacy não têm esses endpoints) — mantémmessages.markReadviaPOST /read(legacy, mesmo mecanismo de auth já confiável, sem relação com o bloqueio degroups.*/contacts.*da Epic 6).MockAdapterimplementa os 6 métodos em memória (starredMessageIds/pinnedMessageIds/readMessageIds, mesmo estilo deblockedIds/archivedChatIds) com getters de inspeção (isMessageStarred/isMessagePinned/isMessageRead) só para teste.- Changeset
minor— mudança aditiva, sem breaking change.
Alternativas consideradas
ChatsApi/MessagesApiunificados num namespace só de "ações": rejeitado — a distinção nível-de-mensagem vs. nível-de-conversa já é uma linha divisória clara no enum (ex.:chats.markReadvs.messages.markRead), manter os métodos emMessagesApipreserva essa clareza sem introduzir um namespace novo desnecessário.StarMessageInput/PinMessageInputunificados comMarkMessageReadInput(todos são{to, messageId}): considerado, mas mantidos como tipos nomeados separados por clareza de domínio — mesmo padrão já usado paraEditMessageInput/DeleteMessageInputno ADR-0012 (nomes distintos mesmo quando a forma é idêntica).