Companies
AlphaQuem pode assinar por esta empresa, para este ato, com este valor
Toda empresa tem um contrato social dizendo quem assina por ela. Nenhum sistema lê aquilo. O Companies guarda com vigência e responde na hora quem precisa assinar — pelo valor do ato, não por palpite do analista.
- Banco e fintech que abrem conta de pessoa jurídica e precisam de dossiê defensável
- Fundo de crédito que compra recebível de cedente PJ e precisa de assinatura com poderes
- Operação com mesa de análise, que precisa de fila, prazo e motivo nomeado
- Produto com cliente PJ cuja alçada de pagamento nasce na abertura da conta
- Planilha de quadro societário e de procurações mantida à mão pelo jurídico
- Cálculo manual de participação indireta para achar beneficiário final
- Regra de "quem assina" reimplementada dentro de cada tela
- Cadastro de pessoa física — isso é o People
- Abertura de conta bancária — isso é o BaaS, e quem cria a conta é o core do cliente
- Motor de decisão — a política vive no Decision Engine
- Consulta a bureau — isso é o Bureaus, consumido por fachada
21 endpoints em 1 recurso.
Resumo executivo
O Companies guarda o que a sua empresa sabe sobre outra empresa: os dados cadastrais com a origem de cada um, quem está ligado a ela (por ponteiro, nunca por cópia), quais documentos societários estão em vigor — e, principalmente, quem tem poder para assinar, em que combinação e até que valor.
Na prática ele responde uma pergunta que hoje cada tela responde do seu jeito: "esta cessão de R$ 300 mil — quem precisa assinar?" A resposta não é uma lista de nomes: são as combinações suficientes (a administradora sozinha, ou dois dos três procuradores), com a vigência de cada poder e o documento que o sustenta. A mesma pergunta serve o envelope de assinatura, a autorização da operação e a alçada de pagamento no aplicativo do cliente.
Este bloco está em alpha. O núcleo — empresa, vínculo, documento societário, representação
avaliável, nível de conhecimento calculado, ingestão em lote, fachada — está implementado e
provado em testes de integração contra Postgres real. O que falta (cadeia societária até
beneficiário final, listas, dossiê com mesa, revisão periódica, envelope de assinatura) está
nomeado no §15, com o gancho pronto e sem rota fingindo existir. O desenho completo continua em
openspec/changes/companies-block/.
| Atributo | Valor |
|---|---|
| Identificador | companies |
| Categoria | Identidade |
| Escopo | Tenant e subconta |
| Porta (standalone) | 3039 |
| Path alias | @companies |
| Status | Alpha — 2026-09 |
| Depende de | IAM; People (pela fachada, opcional por configuração); File Storage (opcional) |
O problema
negócioO cenário. Uma operação vai formalizar um contrato com uma empresa. Alguém tem de assinar por ela. A pergunta "quem?" parece trivial e não é.
O que trava hoje.
- Nenhum sistema modela poderes. O bloco de conta bancária aceita um representante legal,
num objeto opcional:
legalRepresentative?: { name, documentNumber, email, birthDate }. "Dois de três acima de R$ 100 mil" — que é o que está escrito em contrato social de PJ de porte — não cabe nesse campo. - A assinatura não sabe em que qualidade a pessoa assinou. O signatário é
{ name, email, order? }. Um diretor sem poderes e a sócia administradora são, para o sistema, o mesmo signatário. - Beneficiário final é conta de cabeça. Uma holding com 30% da empresa, e dentro dela alguém com 70%: dá 21%, e 21% passa do corte de 25%? Não passa — mas quem faz essa conta hoje é uma pessoa, numa planilha, sem guardar o caminho.
- A falta de dado vira vazio. O quadro societário automático está bloqueado por liberação contratual no fornecedor. Se a tela mostra "sem sócios" quando o certo era "não consultado", a mesa aprova sem saber.
- Não existe consulta de compliance para empresa. Existe para pessoa. Quem não sabe disso acha que está triando a empresa.
O custo de não resolver. Um contrato assinado por quem não podia é nulo — e você descobre quando tenta executá-lo. Uma conta aberta para empresa cujo beneficiário final nunca foi identificado é achado de auditoria. E a alçada que o cliente configura no aplicativo, quando não vem do que foi apurado na abertura, é a segunda versão da mesma verdade — a que ninguém revisa.
Proposta de valor
negócio| Antes | Depois |
|---|---|
| "Quem assina? Acho que a Maria" | "Para este ato e este valor: a Maria sozinha, ou Ana + Carlos" |
| Participação indireta calculada à mão | 30% × 70% = 21%, com o caminho guardado |
| "Sem sócios" quando na verdade é "não consultado" | Não contratado, não implementado e não encontrado são três coisas |
| Procuração vencida descoberta no cartório | O poder deixa de valer na avaliação seguinte |
| A alçada do app é configurada de novo, à mão | A alçada nasce na abertura e o app a consome |
| Motivo de mesa escrito em prosa livre | Motivo nomeado, com a versão da política e a linha que decidiu |
Representação avaliável. Uma rota recebe o ato e o valor e devolve as combinações suficientes. Três consumidores, uma regra.
Cadeia com caminho. O beneficiário final vem com o percentual efetivo e o caminho que o produziu — porque a mesa precisa conferir a conta, não acreditar nela.
O que não se sabe tem nome. Cadeia que não fecha publica evento e abre pendência; nunca devolve silêncio.
Decisão automática é caminho de primeira classe. Aprovar e recusar pela política, sem mesa, com a versão e a linha gravadas — e com o motivo do cliente separado do motivo interno.
Casos de uso reais
negócioCaso 1 — O termo assinado por quem tinha poder Cenário ilustrativo
Fundo de antecipação de recebíveis médicos. O cedente é pessoa jurídica por política — a esteira trata cedente pessoa física como impeditivo absoluto.
O termo de cessão é aceito eletronicamente com uma impressão de integridade de 32 bits. Isso prova que o texto aceito é o texto gerado. Não prova quem assinou pela empresa.
A empresa cedente vira registro com contrato social lido e representação conferida. Antes de montar o envelope, a esteira pergunta quem precisa assinar aquele valor. O envelope sai com os signatários que a regra apontou, e a impressão de integridade atual vira evidência dentro do envelope.
De "alguém aceitou no portal" para "a sócia administradora assinou, com poderes do contrato social vigente" — sem trocar a mesa, a política nem o validador de desembolso.
Caso 2 — A holding que escondia o beneficiário final Cenário ilustrativo
Abertura de conta de pessoa jurídica cujo quadro tem uma sócia pessoa jurídica.
A sócia PJ detém 30%. Dentro dela, duas pessoas: uma com 70%, outra com 30%. Quem precisa se identificar? A conta é simples e ninguém a guarda.
O bloco resolve: 30% × 70% = 21%, abaixo do corte de 25% — e 30% × 30% = 9%. Nenhum dos dois alcança o corte por esse caminho, e o que alcançar fica marcado como beneficiário final com o caminho anexo. Quem fica abaixo é registrado sem verificação, e isso é explícito na ficha.
A conta fica guardada com o caminho, a mesa confere em vez de refazer, e quem não precisa verificar não recebe convite.
Caso 3 — A procuração que tinha vencido Cenário ilustrativo
Empresa que opera por procurador com alçada até R$ 500.000.
A procuração venceu e ninguém percebeu. As ordens continuaram saindo.
A procuração é documento com vigência. Passada a data, a avaliação seguinte simplesmente não considera aquele procurador, e "procuração vencida" entra como pendência nomeada — com a data em que venceu.
A regra passa a se aplicar sozinha, e a pendência tem nome em vez de virar "documentação irregular".
Caso 4 — O caso que saiu da fila sozinho Cenário ilustrativo
Mesa de análise com política de aceitação versionada.
A receita líquida lida do balanço difere da faixa declarada. O caso vai à mesa. Depois o compliance decide que aquela divergência é tolerável e publica uma versão nova da política — mas os casos já na fila continuam lá.
A política é tabela de decisão versionada, e o caso pode ser reavaliado contra a versão vigente. Se passar a casar numa linha de aprovação, sai da fila sem mesa — e o auto de decisão registra que foi a versão nova que decidiu.
Mudar o apetite deixa de exigir mutirão na fila, e continua auditável.
Caso 5 — A alçada que nasce uma vez Cenário ilustrativo
Cliente pessoa jurídica que, depois de aberta a conta, opera por aplicativo.
A regra de aprovação do aplicativo — até certo valor uma assinatura, acima disso conjunta, acima de outro patamar vai à mesa — é configurada à mão, separada do que foi apurado na abertura. Duas verdades.
O aplicativo pergunta ao mesmo lugar: dado o valor, precisa de segunda assinatura? A resposta vem da representação apurada na abertura.
A regra existe num lugar só. Quem muda a representação muda as duas pontas.
Mercado e diferenciais
negócioPanorama. O mercado de KYB vende dado de empresa e, quando é generoso, cadeia societária. Middesk e Sumsub fazem isso bem; Idwall e Serasa fazem isso bem no Brasil. O que nenhum deles trata como objeto é o poder de representação: quem assina, em que combinação, até que valor, com que vigência. Isso continua sendo um PDF que alguém leu, e uma decisão que alguém tomou de cabeça — e é exatamente a informação que invalida um contrato quando está errada.
| Critério | Catalisa Companies | Middesk | Sumsub KYB | Idwall KYB |
|---|---|---|---|---|
| Cadeia societária até beneficiário final | Sim, com caminho guardado | Sim (EUA) | Sim | Sim |
| Poderes de representação avaliáveis por ato e valor | Sim | Não | Não | Não |
| Procuração com alçada e vigência | Sim | Não | Não | Parcial |
| Dossiê com mesa, prazo e motivo nomeado | Sim | Não | Parcial | Sim |
| Decisão automática com versão e linha | Sim | Não | Parcial | Parcial |
| A falta de dado tem nome | Sim | Não | Não | Não |
| Cobertura de fontes | Pelo Bureaus, um fornecedor hoje | EUA, ampla | Internacional, ampla | Brasil, ampla |
| Escopo de subconta (revendedor) | Sim | Não | Não | Não |
Nossos diferenciais.
- Poder é objeto, não campo. Copiar exige remodelar o dado: quem guarda "o representante" numa coluna não passa a expressar "dois de três acima de um valor" acrescentando campo.
- Uma regra, dois usos. A assinatura do contrato e a alçada de pagamento do aplicativo perguntam ao mesmo lugar. Quem tem as duas coisas em produtos diferentes não consegue convergir sem reescrever um dos dois.
- Honestidade sobre a falta. Distinguir "não contratado", "não implementado" e "não encontrado" é decisão de produto que custa caro tomar depois — a tela já foi desenhada acreditando no vazio.
Quando escolher o concorrente. Se você precisa de cobertura internacional de registro mercantil amanhã, Sumsub entrega e nós não. Se sua operação é nos Estados Unidos, Middesk é a escolha óbvia. Se o que você quer é uma esteira de onboarding PJ brasileira pronta, com telas, a Idwall chega mais rápido. O Companies faz sentido quando o poder de assinar é o que você precisa provar — quando a pergunta que dói não é "esta empresa existe?" e sim "esta pessoa podia assinar isto?".
Modelo de cobrança e ROI
negócioPrecificação em definição. Nenhum preço foi fechado. Os drivers estão decididos.
| Driver | Por que ele |
|---|---|
| Empresas ativas no período | A unidade que o cliente entende |
| Consultas de bureau | Custo de fornecedor por trás, repassado sem remarcação oculta |
| Documentos lidos | Leitura por modelo de visão tem custo por página |
| Avaliações de representação | Cresce com o uso real, e é barato por unidade |
Por que a unidade é justa. Cobrar por empresa ativa alinha preço a valor; cobrar por consulta puniria quem organiza o processo. E o cache por tipo de consulta do bloco de bureau impede pagar duas vezes pela mesma informação estável.
A conta de guardanapo. O custo evitado não é a consulta — é o contrato que precisou ser refeito, a conta que a auditoria mandou encerrar e o analista que refaz à mão a conta da holding em cada caso. Quando houver preço, esta seção ganha a comparação em cenário nomeado, com a data da consulta ao preço público de cada análogo.
Arquitetura
flowchart TD
Jornada["Jornada PJ / mesa / app do cliente"] -->|HTTP| Rotas
Orq["Orquestração do cliente (fila no perímetro)"] -->|HTTP idempotente| Rotas
OutroBB["Outro building block"] -->|ICompaniesFacade| Servicos
subgraph Companies
Rotas["Rotas (Hono) — /companies/api/v1"] --> Servicos["Serviços"]
Servicos --> Repr["RepresentationService → evaluate (função pura, datada)"]
Servicos --> Assur["AssuranceService (nível calculado na leitura)"]
Servicos --> Porta["PersonRefPort: people | external"]
end
Porta -->|IPeopleFacade, em lote| People
Servicos -->|IFileStorageFacade| Storage["File Storage"]
Servicos -->|EventPublisher| Stream["Stream iam-events"]
Stream --> Webhooks["webhooks-engine"]
Stream --> Audit["audit-trail"]Cinco decisões que não são óbvias — e o porquê de cada uma.
A regra de representação é um objeto, e a rota recebe o valor. CompaniesRepresentative
guarda papel, modo (isolada | conjunta), n do grupo, atos, alçada, vigência, revogação,
documento-fonte e verificação. A rota evaluate recebe o ato e o valor e devolve combinações
suficientes. Devolver "os representantes" empurraria a regra para o consumidor, e aí cada
consumidor implementa a sua. O motor (services/representation.ts) é uma função pura e
datada: recebe at, não lê relógio — por isso "quem podia assinar em 12/03?" tem resposta
reproduzível e testável sem banco.
O identificador da pessoa vem de uma porta, e o bloco guarda só o identificador. O vínculo
tem personRefKind + personRef, papel, participação e vigência. Nunca nome, nunca
documento, nenhuma chave estrangeira entre schemas, nenhuma leitura da tabela do People.
A porta tem duas implementações escolhidas por configuração do tenant: people (a fachada
IPeopleFacade, que valida e nomeia em lote, com teto de concorrência) e external (o id
opaco do registro do próprio cliente — o bloco não vê nome nenhum). O kind fica gravado na
linha: trocar o modo do tenant depois não reinterpreta o que já existia.
Procedência por atributo, nível calculado. Cada atributo cadastral tem origem
(declared · consulted · imported), unicidade por (empresa, chave, origem), e a
divergência entre origens é derivada na leitura. consulted é reservado à porta de
provedor — que nesta versão vem desligada: a empresa nasce declarada sem consulta
nenhuma, e nenhuma rota aceita consulted como entrada. O nível de conhecimento
(declared → verified → evidenced, por dimensão) não tem coluna: é calculado do que está
válido no instante da leitura e cai sozinho quando um documento vence.
Declarada e conferida são dois estados. O cliente declara o poder (ou uma leitura
automática sugere, com suggested: true); alguém com COMPANIES_VERIFY confere contra o
documento societário. Por padrão só poder conferido conta na avaliação — a política do tenant
(requireVerified: false) relaxa isso, e a resposta diz quem ficou de fora e por quê.
O CNPJ não é cifrado. É dado público da Receita. Cifrar traria uma chave a mais para girar e uma recifragem a mais para agendar, sem proteger nada. O que o bloco herda do People é a disciplina: evento e log carregam ids, nunca o documento.
No modo monolito as fachadas resolvem para serviços locais; em standalone, para chamadas HTTP. O bloco não sabe em qual modo está.
Conceitos e modelo de dados
Glossário. O vocabulário é o do produto. Não há sinônimos.
| Termo | Definição |
|---|---|
| Regra de representação | isolada ou conjunta n de m, com alçada e vigência |
| Alçada | Até quanto alguém pode, individualmente ou em conjunto (amountLimit) |
| Mesa | O patamar acima de toda alçada: requiresDesk: true |
| Poder declarado / conferido | Declarado pelo cliente; conferido por quem tem COMPANIES_VERIFY contra o documento |
| Procedência | A origem de cada atributo: declared · consulted · imported |
| Nível de conhecimento | declared → verified → evidenced, calculado na leitura, por dimensão |
| Porta de pessoa | Quem emite o identificador da pessoa: people ou external |
Modelo de dados. Seis modelos no schema companies. Toda tabela tem organizationId,
subaccountId String? e, onde há unicidade ou idempotência, ownerId.
| Modelo (Prisma) | Tabela | Propósito | Campos-chave |
|---|---|---|---|
CompaniesCompany | companies | A empresa | documentValue (dígitos, público), externalRef, displayName, status |
CompaniesAttribute | company_attributes | Atributo cadastral com procedência | key, value, source, confidence, verifiedAt, expiresAt; único por (empresa, chave, origem) |
CompaniesPersonLink | company_person_links | Ponteiro para a pessoa | personRefKind, personRef, role, sharePercent, validFrom/Until, endedAt |
CompaniesCorporateDocument | company_corporate_documents | Documento societário | type, fileId, validFrom/Until, status, readingDeadlineAt, confidence, supersedesId |
CompaniesRepresentative | company_representatives | O poder | personRef, role, mode, jointN, jointGroup, acts[], amountLimit, validFrom/Until, revokedAt, sourceDocumentId, verifiedAt, suggested |
CompaniesSettings | company_settings | Configuração por dono | personRefKind, representationPolicy, policyVersion |
Os nomes de modelo levam o prefixo Companies porque nome de modelo Prisma é global no repo
(Company já é do customers). As tabelas têm os nomes da spec.
Situação do documento. PENDING · UPLOADED · READING (com prazo) · READ · REVIEW ·
VALID · REJECTED. expired não é estado: é derivado de validUntil na leitura.
Política de representação (dado do tenant, em company_settings.representationPolicy):
{ "defaultAct": "cessao_de_creditos", "acts": ["cessao_de_creditos", "abertura_de_conta"],
"requireVerified": true, "jointAboveAmount": 100000, "deskAboveAmount": 2000000 }{ "defaultAct": "cessao_de_creditos", "acts": ["cessao_de_creditos", "abertura_de_conta"],
"requireVerified": true, "jointAboveAmount": 100000, "deskAboveAmount": 2000000 }acts vazio aceita qualquer ato. jointAboveAmount é o valor a partir do qual poder isolado
não basta; deskAboveAmount é o teto acima do qual o ato vai à mesa, sem olhar poder nenhum.
Cada PUT que muda a política incrementa policyVersion, que viaja em toda avaliação.
Referência da API
Prefixo: /companies/api/v1. Escrito a partir de src/companies/routes/companies.router.ts.
Todas as rotas exigem autenticação, permissão e contexto de organização, e aceitam chave de
subconta e o header X-Subaccount-Id.
Empresa
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
| POST | /companies | Cria pelo CNPJ (dígito verificador) ou reconhece: 201 na primeira, 200 nas seguintes; idempotente por externalRef | COMPANIES_WRITE |
| GET | /companies | Lista por cursor (limit, cursor, externalRef) | COMPANIES_READ |
| POST | /companies/resolve | Resolve até 100 referências (id, externalRef ou documento) de uma vez; cada item { company, error? } — referência que casa com mais de um dono devolve error.code: COMPANIES_AMBIGUOUS_SUBJECT | COMPANIES_READ |
| POST | /companies/ingest | Ingestão em lote: até 100 registros, cada um com empresa, vínculos, documentos e poderes; idempotência por registro; 207 | COMPANIES_WRITE |
| GET | /companies/:id | Ficha | COMPANIES_READ |
| PATCH | /companies/:id | Atualiza nome, externalRef e atributos, preservando a origem de cada um | COMPANIES_WRITE |
| GET | /companies/:id/attributes | Atributos com procedência, vigente por origem e divergência derivada | COMPANIES_READ |
| GET | /companies/:id/assurance?required= | Nível de conhecimento por dimensão, satisfied e o que falta | COMPANIES_READ |
Vínculo com pessoas
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
| GET | /companies/:id/people | Vínculos, com nome resolvido em lote pela porta | COMPANIES_READ |
| POST | /companies/:id/people | Cria o vínculo (personRef, role, sharePercent, vigência); a pessoa é validada pela porta | COMPANIES_WRITE |
| POST | /companies/:id/people/:linkId/end | Encerra o vínculo (a linha fica) | COMPANIES_WRITE |
Documento societário
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
| GET | /companies/:id/documents | Documentos com vigência e situação | COMPANIES_READ |
| POST | /companies/:id/documents | Anexa (type, fileId, vigência, requestReading); mesmo arquivo = mesmo documento | COMPANIES_WRITE |
| PATCH | /companies/:id/documents/:documentId | Veredito ou resultado de leitura (status, confidence, reading, validUntil) | COMPANIES_VERIFY |
Representação — a rota principal
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
| GET | /companies/:id/representatives | Poderes | COMPANIES_READ |
| POST | /companies/:id/representatives | Declara um poder | COMPANIES_WRITE |
| POST | /companies/:id/representatives/:representativeId/verify | Confere contra o documento-fonte | COMPANIES_VERIFY |
| DELETE | /companies/:id/representatives/:representativeId | Revoga (a linha fica: "quem podia em 12/03?" continua respondível) | COMPANIES_WRITE |
| POST | /companies/:id/representation/evaluate | Quem precisa assinar, dado { act?, amount?, at? } | COMPANIES_READ |
Registro (porta de provedor, ligada em 01/10 pela abertura PJ)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
| POST | /companies/:id/registry/consult | { purpose }. Pergunta ao bureaus company.basic e company.ownership; grava razão social, nome fantasia, situação (registrySituation), abertura, CNAE e natureza como atributos de origem consulted (provedor, data e finalidade na linha). Devolve o QSA sem criar vínculo — nome e CPF mascarado (***.456.789-**; CPF inteiro do provedor é mascarado aqui) não são pessoa. partnersAvailable: false quando o provedor não entrega QSA. Provedor com ERROR ou fora: 503 COMPANIES_REGISTRY_UNAVAILABLE, nunca "não encontrado" | COMPANIES_WRITE |
Configuração
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
| GET | /config/settings | Porta de pessoa e política de representação vigentes (com inheritedFrom) | COMPANIES_READ |
| PUT | /config/settings | Muda a porta e/ou a política; política nova incrementa policyVersion | COMPANIES_ADMIN |
A rota principal, em detalhe.
POST /companies/api/v1/companies/{id}/representation/evaluate
Content-Type: application/json
{ "act": "cessao_de_creditos", "amount": 300000 }POST /companies/api/v1/companies/{id}/representation/evaluate
Content-Type: application/json
{ "act": "cessao_de_creditos", "amount": 300000 }{
"data": {
"companyId": "a71c…", "act": "cessao_de_creditos", "amount": 300000,
"at": "2026-09-22T19:40:11.000Z",
"requiresDesk": false, "reason": "OK",
"sufficient": [
{ "mode": "isolada",
"signers": [{ "representativeId": "r1…", "person": { "kind": "people", "ref": "3f0a…" }, "role": "socio_administrador" }],
"sources": [{ "representativeId": "r1…", "documentId": "d19…" }] },
{ "mode": "conjunta", "n": 2, "m": 3, "group": "default",
"signers": [{ "representativeId": "r2…", "person": { "kind": "people", "ref": "77b…" }, "role": "procurador" },
{ "representativeId": "r3…", "person": { "kind": "people", "ref": "91e…" }, "role": "procurador" }],
"sources": [{ "representativeId": "r2…", "documentId": "d42…" }, { "representativeId": "r3…", "documentId": "d42…" }] }
],
"missing": [
{ "representativeId": "r4…", "person": { "kind": "people", "ref": "c0d…" }, "role": "procurador",
"reason": "source_document_not_valid", "detail": "2026-06-01T00:00:00.000Z" }
],
"truncated": false, "policyVersion": 3, "evaluatedAt": "2026-09-22T19:40:11.000Z"
}
}{
"data": {
"companyId": "a71c…", "act": "cessao_de_creditos", "amount": 300000,
"at": "2026-09-22T19:40:11.000Z",
"requiresDesk": false, "reason": "OK",
"sufficient": [
{ "mode": "isolada",
"signers": [{ "representativeId": "r1…", "person": { "kind": "people", "ref": "3f0a…" }, "role": "socio_administrador" }],
"sources": [{ "representativeId": "r1…", "documentId": "d19…" }] },
{ "mode": "conjunta", "n": 2, "m": 3, "group": "default",
"signers": [{ "representativeId": "r2…", "person": { "kind": "people", "ref": "77b…" }, "role": "procurador" },
{ "representativeId": "r3…", "person": { "kind": "people", "ref": "91e…" }, "role": "procurador" }],
"sources": [{ "representativeId": "r2…", "documentId": "d42…" }, { "representativeId": "r3…", "documentId": "d42…" }] }
],
"missing": [
{ "representativeId": "r4…", "person": { "kind": "people", "ref": "c0d…" }, "role": "procurador",
"reason": "source_document_not_valid", "detail": "2026-06-01T00:00:00.000Z" }
],
"truncated": false, "policyVersion": 3, "evaluatedAt": "2026-09-22T19:40:11.000Z"
}
}reason é um de OK · AMOUNT_REQUIRED · NO_REPRESENTATION · INSUFFICIENT ·
REQUIRES_DESK_ABOVE_ALL_LIMITS · REQUIRES_DESK_ABOVE_POLICY_LIMIT. Cada excluído em missing
diz por quê: not_verified · not_yet_valid · expired · revoked · act_not_covered ·
amount_above_limit · source_document_missing · source_document_not_validated ·
source_document_not_valid · isolated_not_allowed_above_amount · joint_group_incomplete.
Quatro regras que falham fechado, porque a resposta vira envelope que alguém assina:
- Combinação é de pessoas, nunca de poderes. Ana como diretora e Ana como procuradora são
uma signatária; "2 de 2" nunca é satisfeito por um humano só. A pessoa é
personRefKind:personRef; conta uma vez por combinação, sempre. conjuntaé no mínimo duas pessoas.jointNabaixo de 2 é recusado no contrato e saturado no motor; acima dejointAboveAmounttoda combinação tem pelo menos duas pessoas distintas, seja qual for o modo declarado.- Só documento
VALIDcom arquivo sustenta poder.PENDING,UPLOADED,READING,READeREVIEWsustentam nada (source_document_not_validated) — ninguém os validou. Conferir um poder contra documento não validado é recusado (COMPANIES_SOURCE_DOCUMENT_NOT_VALIDATED). - Sem valor onde o valor decide,
AMOUNT_REQUIRED. Se a política ou qualquer poder vivo tem limiar de valor e o chamador não mandouamount, a resposta ésufficient: []com esse motivo — nunca um envelope permissivo por esquecimento do consumidor.
Perguntando só com o valor — que é como o aplicativo do cliente pergunta — o defaultAct da
política é usado. requiresDesk: true não é sufficient: []: o primeiro diz "passa do
teto de todo mandato vivo, precisa de mesa"; o segundo diz "não há combinação possível" (ninguém
conferido, procuração vencida, grupo incompleto). Um poder fora por motivo estrutural não
transforma a resposta em mesa.
| Erro | Quando |
|---|---|
400 | CNPJ inválido (COMPANIES_INVALID_CNPJ); ato sem padrão (COMPANIES_ACT_REQUIRED); ato fora do catálogo (COMPANIES_ACT_UNKNOWN); pessoa desconhecida para o escopo (COMPANIES_PERSON_NOT_FOUND); documento-fonte não validado (COMPANIES_SOURCE_DOCUMENT_NOT_VALIDATED); VALID sem arquivo (COMPANIES_DOCUMENT_FILE_REQUIRED); lote acima do teto (COMPANIES_BATCH_TOO_LARGE) |
404 | Empresa, vínculo, documento ou poder inexistente ou fora do escopo do chamador |
409 | externalRef de outra empresa (COMPANIES_EXTERNAL_REF_CONFLICT); chave de idempotência já usada com outro documento (COMPANIES_IDEMPOTENCY_CONFLICT); poder aberto com a mesma chave natural e termos diferentes (COMPANIES_REPRESENTATIVE_CONFLICT); referência que casa com mais de um dono (COMPANIES_AMBIGUOUS_SUBJECT, em resolve) |
503 | Porta people sem fachada no processo (PEOPLE_FACADE_UNAVAILABLE), ou fileId sem fachada de file-storage (FILE_STORAGE_UNAVAILABLE): o bloco não grava referência que não conferiu |
Início rápido
Executado contra o serviço em bun run dev:companies (porta 3039) com um token que tenha
COMPANIES_WRITE, COMPANIES_VERIFY e COMPANIES_ADMIN.
1. Criar a empresa pelo CNPJ.
curl -s -X POST "$BASE/companies/api/v1/companies" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"document":{"value":"11.222.333/0001-81"},"name":"Clínica Vale Verde Ltda.","externalRef":"lead-481",
"attributes":[{"key":"legalName","value":"CLINICA VALE VERDE LTDA","source":"declared"}]}'curl -s -X POST "$BASE/companies/api/v1/companies" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"document":{"value":"11.222.333/0001-81"},"name":"Clínica Vale Verde Ltda.","externalRef":"lead-481",
"attributes":[{"key":"legalName","value":"CLINICA VALE VERDE LTDA","source":"declared"}]}'201 na primeira vez, 200 se já existia para este dono. O mesmo CNPJ em outra organização é
outra empresa.
2. Vincular a pessoa (id do People; ou id do seu sistema, se a porta for external).
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/people" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"personRef":"3f0a…","role":"socio_administrador","sharePercent":60}'curl -s -X POST "$BASE/companies/api/v1/companies/$ID/people" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"personRef":"3f0a…","role":"socio_administrador","sharePercent":60}'3. Anexar o contrato social.
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/documents" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"type":"contrato_social","fileId":"'"$FILE"'","validFrom":"2024-03-12T00:00:00Z"}'curl -s -X POST "$BASE/companies/api/v1/companies/$ID/documents" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"type":"contrato_social","fileId":"'"$FILE"'","validFrom":"2024-03-12T00:00:00Z"}'4. Declarar o poder e conferir.
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representatives" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"personRef":"3f0a…","role":"socio_administrador","mode":"isolada",
"acts":["cessao_de_creditos"],"amountLimit":500000,"sourceDocumentId":"'"$DOC"'"}'
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representatives/$REP/verify" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representatives" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"personRef":"3f0a…","role":"socio_administrador","mode":"isolada",
"acts":["cessao_de_creditos"],"amountLimit":500000,"sourceDocumentId":"'"$DOC"'"}'
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representatives/$REP/verify" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'5. Perguntar quem assina.
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"act":"cessao_de_creditos","amount":300000}' | jq '.data | {reason, requiresDesk, sufficient}'curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"act":"cessao_de_creditos","amount":300000}' | jq '.data | {reason, requiresDesk, sufficient}'Receitas
Receita 1 — Montar o envelope com quem tem poder
COMB=$(curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"act":"cessao_de_creditos","amount":'"$VALOR"'}')
echo "$COMB" | jq -e '.data.sufficient | length > 0' >/dev/null || { echo "$COMB" | jq '.data | {reason, requiresDesk, missing}'; exit 1; }
echo "$COMB" | jq '.data.sufficient[0].signers'COMB=$(curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"act":"cessao_de_creditos","amount":'"$VALOR"'}')
echo "$COMB" | jq -e '.data.sufficient | length > 0' >/dev/null || { echo "$COMB" | jq '.data | {reason, requiresDesk, missing}'; exit 1; }
echo "$COMB" | jq '.data.sufficient[0].signers'Armadilha. Guarde policyVersion, at e os documentId de sources junto com o envelope.
Daqui a dois anos a pergunta não vai ser "quem assinou" — vai ser "por que ele podia". E o
e-signature ainda não tem papel no signatário (§15): a junta entre "podia" e "assinou" fica no
seu registro, não dentro do envelope.
Receita 2 — Perguntar só pelo valor, como o app do cliente
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"amount":150000}' | jq '{modo: (.data.sufficient[0].mode), mesa: .data.requiresDesk, motivo: .data.reason}'curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"amount":150000}' | jq '{modo: (.data.sufficient[0].mode), mesa: .data.requiresDesk, motivo: .data.reason}'Exige defaultAct na política (PUT /config/settings); sem ele, 400 COMPANIES_ACT_REQUIRED.
Receita 3 — Migrar a base em lote
curl -s -X POST "$BASE/companies/api/v1/companies/ingest" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"items":[{"idempotencyKey":"migracao-2026/0001",
"company":{"document":{"value":"34.028.316/0001-03"},"externalRef":"mig-1"},
"links":[{"personRef":"3f0a…","role":"socio_administrador"}],
"documents":[{"type":"contrato_social","source":"imported"}],
"representatives":[{"personRef":"3f0a…","role":"socio_administrador","mode":"isolada"}]}]}'curl -s -X POST "$BASE/companies/api/v1/companies/ingest" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"items":[{"idempotencyKey":"migracao-2026/0001",
"company":{"document":{"value":"34.028.316/0001-03"},"externalRef":"mig-1"},
"links":[{"personRef":"3f0a…","role":"socio_administrador"}],
"documents":[{"type":"contrato_social","source":"imported"}],
"representatives":[{"personRef":"3f0a…","role":"socio_administrador","mode":"isolada"}]}]}'207: cada item com status (201 criado, 200 reconhecido, 207 empresa ok com filho
problemático, 4xx recusado), counts e problems. Reenviar o mesmo lote completa o que
faltou e não duplica nada: a chave do item é a chave da empresa (a mesma chave com outro CNPJ
é 409, e nenhum filho vai para a empresa errada) e deriva a chave de cada filho, escopada por
empresa. As pessoas do item são validadas uma vez por item — 100 itens com 50 vínculos
custam 100 chamadas à porta, não 5.000.
Receita 4 — Trocar a porta de pessoa para o id do seu sistema
curl -s -X PUT "$BASE/companies/api/v1/config/settings" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"personRefKind":"external"}'curl -s -X PUT "$BASE/companies/api/v1/config/settings" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"personRefKind":"external"}'A partir daí personRef é o seu id, opaco. Vínculos antigos continuam com kind: people —
o kind é gravado na linha.
Receita 5 — Quem podia assinar naquele dia
curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"act":"cessao_de_creditos","amount":100,"at":"2026-03-01T00:00:00Z"}'curl -s -X POST "$BASE/companies/api/v1/companies/$ID/representation/evaluate" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"act":"cessao_de_creditos","amount":100,"at":"2026-03-01T00:00:00Z"}'Vigência do poder, vigência do documento-fonte e revogação são avaliadas na data. A conferência não é datada nesta versão: um poder conferido hoje conta como conferido em qualquer data — limitação registrada no §15.
Integração com outros building blocks
| BB | Como se relaciona | Obrigatório |
|---|---|---|
| IAM | Token, organização, subconta e permissões | Sim |
| People | Porta people: valida e nomeia a pessoa pela IPeopleFacade, em lote. Ponteiro, nunca cópia | Só se a porta for people (padrão) |
| File Storage | Confere o fileId do documento pela fachada; ausente, grava com aviso | Opcional |
| Webhooks Engine | Entrega companies.* assinado, com retentativa | Automático |
| Audit Trail | Lê o stream; companies.* tem entrada própria no mapeador | Automático |
| E-Signature | O consumidor chama evaluate e monta o envelope. O e-signature não tem papel do signatário (§15) | Não (é do consumidor) |
| Bureaus | Porta de provedor: /registry/consult pergunta company.basic e company.ownership (origem consulted) | Só para a consulta ao registro |
| Data Extraction | Leitura de documento: a intenção e o prazo ficam gravados; a porta não está ligada | Não |
Fachada. ICompaniesFacade: duas leituras e, desde 01/10, as escritas da abertura PJ do
banking (que não tem usuário da plataforma na frente — quem prova identidade é o cliente do banco).
Cada escrita passa pelo MESMO serviço da rota (local) ou chama a própria rota (remota): mesma
validação, mesmos eventos, mesmo escopo; nenhuma tem regra própria. actorUserId é quem responde
pelo ato na trilha (o operador da mesa ao conferir).
evaluateRepresentation({ organizationId, subaccountId?, companyId, act?, amount?, at? })
getAssurance({ organizationId, subaccountId?, companyId, required? })
registerCompany({ …, cnpj, externalRef? }) // cria ou reconhece
consultRegistry({ …, companyId, purpose }) // porta de provedor
linkPerson({ …, companyId, personId, role, idempotencyKey })
attachDocument({ …, companyId, type, fileId, idempotencyKey }) // UPLOADED, nunca VALID
declareRepresentative({ …, companyId, personId, role, mode, jointN?, acts, amountLimit?, validFrom?, validUntil?, sourceDocumentId? })
reviewDocument({ …, companyId, documentId, status, actorUserId }) // COMPANIES_VERIFY
verifyRepresentative({ …, companyId, representativeId, actorUserId }) // COMPANIES_VERIFYevaluateRepresentation({ organizationId, subaccountId?, companyId, act?, amount?, at? })
getAssurance({ organizationId, subaccountId?, companyId, required? })
registerCompany({ …, cnpj, externalRef? }) // cria ou reconhece
consultRegistry({ …, companyId, purpose }) // porta de provedor
linkPerson({ …, companyId, personId, role, idempotencyKey })
attachDocument({ …, companyId, type, fileId, idempotencyKey }) // UPLOADED, nunca VALID
declareRepresentative({ …, companyId, personId, role, mode, jointN?, acts, amountLimit?, validFrom?, validUntil?, sourceDocumentId? })
reviewDocument({ …, companyId, documentId, status, actorUserId }) // COMPANIES_VERIFY
verifyRepresentative({ …, companyId, representativeId, actorUserId }) // COMPANIES_VERIFYEventos (EventPublisher.safePublish, stream iam-events; payload só com ids, papéis,
modos, valores e datas):
| Evento | Quando |
|---|---|
companies.company.created / updated | Empresa criada (nunca no reconhecimento) / atualizada |
companies.link.created / ended | Vínculo criado / encerrado |
companies.document.attached / expired | Documento anexado / varredura encontrou vigência encerrada (só avisa) |
companies.registry.consulted | A porta de provedor leu o registro: found, situação, quantos sócios, partnersAvailable, provedor, finalidade. Nunca nome nem documento |
companies.representation.updated | reason: declared · verified · revoked · policy · document_reviewed · link_ended — ato sensível, um evento só |
Configuração e operação
| Nome | Descrição | Obrigatória | Padrão |
|---|---|---|---|
COMPANIES_BATCH_MAX | Teto de itens por lote (resolve, ingest) | Não | 100 |
COMPANIES_OUTBOUND_CONCURRENCY | Teto de chamadas simultâneas à porta de pessoa e ao lote | Não | 8 |
COMPANIES_DOCUMENT_READING_TIMEOUT_MINUTES | Quanto tempo um documento fica em READING antes de o bloco desistir | Não | 30 |
COMPANIES_EXPIRY_SCAN_ENABLED | Varredura de vencimento (só avisa) | Não | true |
COMPANIES_MAX_COMBINATIONS | Teto de combinações conjuntas enumeradas por avaliação (truncated: true acima) | Não | 200 |
MODULE_COMPANIES_URL | Endereço do bloco no modo standalone | Só em standalone | — |
MODULE_PEOPLE_URL | Endereço do People, para a porta people em standalone | Só em standalone | — |
Não há chave de cifra: o CNPJ é público e fica em dígitos.
Configuração por tenant (PUT /config/settings, COMPANIES_ADMIN): personRefKind e
representationPolicy (§8). Resolução na leitura: o dono exato; depois, para subconta, a
organização; depois os padrões (people, qualquer ato, só conferido, sem tetos,
policyVersion: 0). O instalador da vertical semeia isso com a chave do tenant — o pacote
declara os atos e a política mínima (ver openspec/changes/companies-block/design.md).
Varredura. A cada 6 h, com trava no Redis, uma passada 30 s após subir. Publica
companies.document.expired para o que venceu na janela desde a última marca d'água e devolve
a UPLOADED as leituras que estouraram o prazo. Não muda mais nada.
Catálogo de erros.
| Código | Significa | O que fazer |
|---|---|---|
COMPANIES_INVALID_CNPJ | Dígito verificador não fecha | Corrija a entrada |
COMPANIES_EXTERNAL_REF_CONFLICT | externalRef já é de outra empresa deste dono | Corrija o chamador; mudar documento é correção, não criação |
COMPANIES_PERSON_NOT_FOUND | A porta people não conhece a pessoa neste escopo | Cadastre no People (ou use a porta external) |
COMPANIES_PERSON_REF_INVALID | Identificador fora do formato da porta | uuid para people; até 120 caracteres para external |
COMPANIES_ACT_REQUIRED | act ausente e política sem defaultAct | Declare o ato ou configure o padrão |
COMPANIES_ACT_UNKNOWN | Ato fora do catálogo da política | Veja details.acts |
COMPANIES_SOURCE_DOCUMENT_REQUIRED | Conferir exige documento-fonte | Informe sourceDocumentId |
COMPANIES_BATCH_TOO_LARGE | Lote acima do teto | Pagine |
COMPANIES_FILE_NOT_FOUND | fileId não é desta organização | Suba o arquivo no File Storage com o mesmo token |
COMPANIES_SOURCE_DOCUMENT_NOT_VALIDATED | Conferir exige documento VALID com arquivo | Revise o documento (PATCH .../documents/:id) antes |
COMPANIES_DOCUMENT_FILE_REQUIRED | VALID sem arquivo | Anexe o arquivo; documento que ninguém abre não valida nada |
COMPANIES_IDEMPOTENCY_CONFLICT | Mesma chave, outro documento | Corrija o chamador; nada foi gravado |
COMPANIES_REPRESENTATIVE_CONFLICT | Poder aberto com a mesma pessoa/papel/modo/validFrom e termos diferentes | Revogue o anterior ou use outro validFrom |
COMPANIES_AMBIGUOUS_SUBJECT | Chave de organização e a referência casa com mais de uma subconta | Informe X-Subaccount-Id |
FILE_STORAGE_UNAVAILABLE | Fachada de file-storage ausente no processo | O fileId não é gravado sem conferência; suba o file-storage ou anexe sem arquivo |
Segurança e compliance
Isolamento. Duas camadas, como no People. O middleware compartilhado resolve organização e
subconta e falha fechado (companies está em SUBACCOUNT_AWARE). Dentro do bloco, leitura
e posse usam predicados distintos (scopeWhere / ownerWhere): a organização vê tudo que é
dela, a subconta vê só o dela, e a unicidade usa o dono exato (owner_id, porque no Postgres
NULL != NULL). Recurso fora do escopo responde 404, nunca 403.
Dado pessoal. O vínculo guarda só o identificador da pessoa. O nome vem do People na
leitura, e o People é quem tem consentimento, relatório do titular e expurgo. A tabela
company_person_links não tem coluna de nome nem de documento — há teste lendo o
information_schema. Evento não carrega nome, documento de pessoa nem CNPJ.
Ato sensível. Toda mudança em quem pode assinar — declarar, conferir, revogar, mudar a
política, revisar o documento-fonte (document_reviewed) e encerrar o vínculo de quem
tem poder vivo (link_ended) — publica companies.representation.updated, que o audit-trail
grava com o id do poder (ou da configuração).
JSON livre é lugar onde o cliente pode colar dado pessoal. metadata (vínculo) e reading
(documento) são JSON sem esquema. O bloco não copia nada para lá — mas oferece onde colar.
Quem escreve nome, CPF ou contato ali está guardando dado pessoal fora do People, sem
consentimento, relatório de titular nem expurgo. Use-os para referências (protocolo, id de
sessão, chaves do seu sistema), nunca para o dado em si.
Permissões. COMPANIES_READ, COMPANIES_WRITE, COMPANIES_VERIFY, COMPANIES_ADMIN,
atribuídas a papel no catálogo do IAM: VIEWER lê; CUSTOMER_MANAGER lê, escreve e confere;
VERIFICATION_OPERATOR lê e confere; ADMIN tudo, inclusive COMPANIES_ADMIN (política de
representação e porta de pessoa são governança).
Limitações conhecidas
| Limitação | Detalhe | Gravidade |
|---|---|---|
| Sem cadeia societária até beneficiário final | sharePercent no vínculo é a aresta direta. Não há CompanyShareholding, resolução com caminho, ciclo nem companies.ubo.unresolved. Gancho: o vínculo já guarda participação e vigência | Fora deste PR |
| Sem listas/compliance | Nem por empresa (a consulta não existe no Bureaus) nem agregada pelas pessoas | Fora deste PR |
| Sem dossiê, mesa, prazos e revisão periódica | Sem CompanyDossier, estados captada → … → conta aberta, devolutiva, auto de decisão. Gancho: assurance já diz o que falta | Fora deste PR |
| Não monta o envelope de assinatura | O consumidor chama evaluate e monta no e-signature. O e-signature não tem papel/qualidade no signatário (SignerInput é { name, email, order? }): a junta entre "podia" e "assinou" fica no registro do consumidor | Dependência registrada |
| Porta de provedor: só registro básico e QSA | Ligada em 01/10 (/registry/consult, via bureaus): provedor, finalidade e data em cada atributo consulted. Nenhuma outra rota aceita consulted como entrada. O QSA real depende do provedor: o BigDataCorp não entrega owners por contrato (partnersAvailable: false); o MOCK devolve sócios sem CPF | Registrada |
| Leitura de documento não ligada | requestReading grava intenção e prazo; a porta ao Data Extraction não existe. O prazo evita READING eterno | Por desenho, v1 |
| Conferência não é datada | verifiedAt conta como conferido em qualquer at. Vigência, revogação e documento-fonte são datados | Registrada |
fileId é conferido por organização, não por subconta | O File Storage não tem noção de subconta (sem subaccount_id em files, fora de SUBACCOUNT_AWARE): pela fachada não dá para distinguir o arquivo de uma subconta do da irmã. Exige mudança naquele bloco | Dependência registrada |
| Vínculo encerrado não revoga o poder | Encerrar o vínculo publica representation.updated(link_ended) para os poderes vivos daquela pessoa; revogar continua sendo ato explícito | Por desenho |
| Sem cache de avaliação | A avaliação lê poderes e documentos da empresa a cada chamada (duas consultas por chave primária). Cache por (empresa, ato, faixa, policyVersion) fica para quando houver medição que o justifique | Registrada |
| Rota de assurance da fachada é uma por empresa | getAssurance da fachada recebe um companyId; não há /assurance/batch ainda | Registrada |
| Sem armazenamento imutável | A trilha é à prova de evidência, não à prova de escrita | Frente de segurança |
Perguntas frequentes
Por que o vínculo não guarda o nome da pessoa? A tela da mesa precisa dele.
Porque copiar cria duas verdades e a segunda envelhece. O nome vem do People na leitura, em
lote — a lista de vínculos custa uma chamada à fachada por 100 pessoas, não uma por linha. Se
o seu cadastro de pessoa não é o People, use a porta external e faça o join no seu sistema.
O que acontece se a fachada do People não estiver no processo?
Escrita: 503 — o bloco não grava ponteiro que não conferiu. Leitura: o vínculo sai com
resolution: "unavailable" e nome nulo; nada quebra, e a tela sabe que não resolveu.
Por que requireVerified é true por padrão?
Porque a razão de existir do bloco é provar que quem assinou podia. Poder declarado e não
conferido é afirmação. O tenant que opera só com declaração relaxa na política, e a resposta
continua dizendo quem ficou de fora e por quê.
act é o quê?
Uma string do tenant. O bloco não sabe o que é "cessão"; a política diz quais atos existem e
qual é o padrão, e cada poder diz quais atos cobre. Regra de produto não mora aqui.
Como ficam "dois de três acima de R$ 100 mil"?
Três poderes conjunta com jointN: 2 no mesmo jointGroup, mais jointAboveAmount: 100000
na política. Abaixo de 100 mil os isolados bastam; acima, só os pares. n de m é genérico —
fixar o 2 seria herdar um limite de tela.
O CNPJ não deveria ser cifrado como o CPF? O CNPJ é público; o CPF não. Cifrar o CNPJ traria uma chave para girar e nenhum dado a proteger. O que o bloco herda do People é a disciplina do evento e do log, não a cifra.
A primeira adoção — MedFund
A MedFund é a primeira. O cedente é pessoa jurídica por política e hoje vive como lead; o termo de cessão é aceito por impressão de integridade de 32 bits, sem prova de quem assinou pela empresa. O que este núcleo já permite, na ordem:
- Cadastro da empresa cedente —
POST /companiespelo CNPJ, idempotente porexternalRef(o id do lead), ouPOST /companies/ingestpara a base inteira. - Vínculo com o médico —
POST /companies/:id/peoplecom o id da pessoa no People (ou o id do lead, na portaexternal). O médico continua sendo lead no CRM. - Representação declarada e conferida —
POST .../representativescom o contrato social comosourceDocumentId, e.../verifypela mesa. Até conferir,evaluatediznot_verified. - Termo assinado por quem
evaluateapontou — o gerador do termo chamaevaluateRepresentationpela fachada comact: 'cessao_de_creditos'e o valor do borderô, e monta o envelope no e-signature comsufficient[0].signers. A impressão de integridade atual entra como evidência dentro do envelope; o validador de desembolso não muda.
O que fica para os próximos PRs: o pacote da vertical declarar cessao_de_creditos e a política
mínima (semeadas pelo instalador com a chave do tenant), e o papel do signatário no e-signature.