Catalisa.Building Blocks
Catálogo/Identidade/Companies

Companies

Alpha

Quem pode assinar por esta empresa, para este ato, com este valor

21
Endpoints
6
Entidades
0
Provedores
Tenant
Escopo
3039
Porta
2026-09
Desde

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.

Para quem é
  • 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
Substitui
  • 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
O que não é
  • 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
O que dá para fazer

21 endpoints em 1 recurso.

Explorar a API →
01

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/.

AtributoValor
Identificadorcompanies
CategoriaIdentidade
EscopoTenant e subconta
Porta (standalone)3039
Path alias@companies
StatusAlpha — 2026-09
Depende deIAM; People (pela fachada, opcional por configuração); File Storage (opcional)

02

O problema

negócio

O 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.


03

Proposta de valor

negócio
AntesDepois
"Quem assina? Acho que a Maria""Para este ato e este valor: a Maria sozinha, ou Ana + Carlos"
Participação indireta calculada à mão30% × 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órioO poder deixa de valer na avaliação seguinte
A alçada do app é configurada de novo, à mãoA alçada nasce na abertura e o app a consome
Motivo de mesa escrito em prosa livreMotivo 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.


04

Casos de uso reais

negócio

Caso 1 — O termo assinado por quem tinha poder Cenário ilustrativo

Contexto

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.

A dor

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 solução com o BB

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.

O resultado

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

Contexto

Abertura de conta de pessoa jurídica cujo quadro tem uma sócia pessoa jurídica.

A dor

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.

A solução com o BB

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.

O resultado

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

Contexto

Empresa que opera por procurador com alçada até R$ 500.000.

A dor

A procuração venceu e ninguém percebeu. As ordens continuaram saindo.

A solução com o BB

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.

O resultado

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

Contexto

Mesa de análise com política de aceitação versionada.

A dor

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 solução com o BB

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.

O resultado

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

Contexto

Cliente pessoa jurídica que, depois de aberta a conta, opera por aplicativo.

A dor

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.

A solução com o BB

O aplicativo pergunta ao mesmo lugar: dado o valor, precisa de segunda assinatura? A resposta vem da representação apurada na abertura.

O resultado

A regra existe num lugar só. Quem muda a representação muda as duas pontas.


05

Mercado e diferenciais

negócio

Panorama. 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érioCatalisa CompaniesMiddeskSumsub KYBIdwall KYB
Cadeia societária até beneficiário finalSim, com caminho guardadoSim (EUA)SimSim
Poderes de representação avaliáveis por ato e valorSimNãoNãoNão
Procuração com alçada e vigênciaSimNãoNãoParcial
Dossiê com mesa, prazo e motivo nomeadoSimNãoParcialSim
Decisão automática com versão e linhaSimNãoParcialParcial
A falta de dado tem nomeSimNãoNãoNão
Cobertura de fontesPelo Bureaus, um fornecedor hojeEUA, amplaInternacional, amplaBrasil, ampla
Escopo de subconta (revendedor)SimNãoNãoNão

Nossos diferenciais.

  1. 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.
  2. 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.
  3. 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?".


06

Modelo de cobrança e ROI

negócio

Precificação em definição. Nenhum preço foi fechado. Os drivers estão decididos.

DriverPor que ele
Empresas ativas no períodoA unidade que o cliente entende
Consultas de bureauCusto de fornecedor por trás, repassado sem remarcação oculta
Documentos lidosLeitura por modelo de visão tem custo por página
Avaliações de representaçãoCresce 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.


07

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á.


08

Conceitos e modelo de dados

Glossário. O vocabulário é o do produto. Não há sinônimos.

TermoDefinição
Regra de representaçãoisolada ou conjunta n de m, com alçada e vigência
AlçadaAté quanto alguém pode, individualmente ou em conjunto (amountLimit)
MesaO patamar acima de toda alçada: requiresDesk: true
Poder declarado / conferidoDeclarado pelo cliente; conferido por quem tem COMPANIES_VERIFY contra o documento
ProcedênciaA origem de cada atributo: declared · consulted · imported
Nível de conhecimentodeclared → verified → evidenced, calculado na leitura, por dimensão
Porta de pessoaQuem 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)TabelaPropósitoCampos-chave
CompaniesCompanycompaniesA empresadocumentValue (dígitos, público), externalRef, displayName, status
CompaniesAttributecompany_attributesAtributo cadastral com procedênciakey, value, source, confidence, verifiedAt, expiresAt; único por (empresa, chave, origem)
CompaniesPersonLinkcompany_person_linksPonteiro para a pessoapersonRefKind, personRef, role, sharePercent, validFrom/Until, endedAt
CompaniesCorporateDocumentcompany_corporate_documentsDocumento societáriotype, fileId, validFrom/Until, status, readingDeadlineAt, confidence, supersedesId
CompaniesRepresentativecompany_representativesO poderpersonRef, role, mode, jointN, jointGroup, acts[], amountLimit, validFrom/Until, revokedAt, sourceDocumentId, verifiedAt, suggested
CompaniesSettingscompany_settingsConfiguração por donopersonRefKind, 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):

json
{ "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.


09

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étodoRotaDescriçãoPermissão
POST/companiesCria pelo CNPJ (dígito verificador) ou reconhece: 201 na primeira, 200 nas seguintes; idempotente por externalRefCOMPANIES_WRITE
GET/companiesLista por cursor (limit, cursor, externalRef)COMPANIES_READ
POST/companies/resolveResolve 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_SUBJECTCOMPANIES_READ
POST/companies/ingestIngestão em lote: até 100 registros, cada um com empresa, vínculos, documentos e poderes; idempotência por registro; 207COMPANIES_WRITE
GET/companies/:idFichaCOMPANIES_READ
PATCH/companies/:idAtualiza nome, externalRef e atributos, preservando a origem de cada umCOMPANIES_WRITE
GET/companies/:id/attributesAtributos com procedência, vigente por origem e divergência derivadaCOMPANIES_READ
GET/companies/:id/assurance?required=Nível de conhecimento por dimensão, satisfied e o que faltaCOMPANIES_READ

Vínculo com pessoas

MétodoRotaDescriçãoPermissão
GET/companies/:id/peopleVínculos, com nome resolvido em lote pela portaCOMPANIES_READ
POST/companies/:id/peopleCria o vínculo (personRef, role, sharePercent, vigência); a pessoa é validada pela portaCOMPANIES_WRITE
POST/companies/:id/people/:linkId/endEncerra o vínculo (a linha fica)COMPANIES_WRITE

Documento societário

MétodoRotaDescriçãoPermissão
GET/companies/:id/documentsDocumentos com vigência e situaçãoCOMPANIES_READ
POST/companies/:id/documentsAnexa (type, fileId, vigência, requestReading); mesmo arquivo = mesmo documentoCOMPANIES_WRITE
PATCH/companies/:id/documents/:documentIdVeredito ou resultado de leitura (status, confidence, reading, validUntil)COMPANIES_VERIFY

Representação — a rota principal

MétodoRotaDescriçãoPermissão
GET/companies/:id/representativesPoderesCOMPANIES_READ
POST/companies/:id/representativesDeclara um poderCOMPANIES_WRITE
POST/companies/:id/representatives/:representativeId/verifyConfere contra o documento-fonteCOMPANIES_VERIFY
DELETE/companies/:id/representatives/:representativeIdRevoga (a linha fica: "quem podia em 12/03?" continua respondível)COMPANIES_WRITE
POST/companies/:id/representation/evaluateQuem precisa assinar, dado { act?, amount?, at? }COMPANIES_READ

Registro (porta de provedor, ligada em 01/10 pela abertura PJ)

MétodoRotaDescriçãoPermissã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étodoRotaDescriçãoPermissão
GET/config/settingsPorta de pessoa e política de representação vigentes (com inheritedFrom)COMPANIES_READ
PUT/config/settingsMuda a porta e/ou a política; política nova incrementa policyVersionCOMPANIES_ADMIN

A rota principal, em detalhe.

http
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 }
json
{
  "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. jointN abaixo de 2 é recusado no contrato e saturado no motor; acima de jointAboveAmount toda combinação tem pelo menos duas pessoas distintas, seja qual for o modo declarado.
  • Só documento VALID com arquivo sustenta poder. PENDING, UPLOADED, READING, READ e REVIEW sustentam 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 mandou amount, 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.

ErroQuando
400CNPJ 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)
404Empresa, vínculo, documento ou poder inexistente ou fora do escopo do chamador
409externalRef 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)
503Porta 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

10

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.

bash
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).

bash
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.

bash
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.

bash
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.

bash
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}'

11

Receitas

Receita 1 — Montar o envelope com quem tem poder

bash
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

bash
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

bash
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

bash
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

bash
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.


12

Integração com outros building blocks

BBComo se relacionaObrigatório
IAMToken, organização, subconta e permissõesSim
PeoplePorta people: valida e nomeia a pessoa pela IPeopleFacade, em lote. Ponteiro, nunca cópiaSó se a porta for people (padrão)
File StorageConfere o fileId do documento pela fachada; ausente, grava com avisoOpcional
Webhooks EngineEntrega companies.* assinado, com retentativaAutomático
Audit TrailLê o stream; companies.* tem entrada própria no mapeadorAutomático
E-SignatureO consumidor chama evaluate e monta o envelope. O e-signature não tem papel do signatário (§15)Não (é do consumidor)
BureausPorta de provedor: /registry/consult pergunta company.basic e company.ownership (origem consulted)Só para a consulta ao registro
Data ExtractionLeitura de documento: a intenção e o prazo ficam gravados; a porta não está ligadaNã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).

ts
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_VERIFY
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_VERIFY

Eventos (EventPublisher.safePublish, stream iam-events; payload só com ids, papéis, modos, valores e datas):

EventoQuando
companies.company.created / updatedEmpresa criada (nunca no reconhecimento) / atualizada
companies.link.created / endedVínculo criado / encerrado
companies.document.attached / expiredDocumento anexado / varredura encontrou vigência encerrada (só avisa)
companies.registry.consultedA porta de provedor leu o registro: found, situação, quantos sócios, partnersAvailable, provedor, finalidade. Nunca nome nem documento
companies.representation.updatedreason: declared · verified · revoked · policy · document_reviewed · link_ended — ato sensível, um evento só

13

Configuração e operação

NomeDescriçãoObrigatóriaPadrão
COMPANIES_BATCH_MAXTeto de itens por lote (resolve, ingest)Não100
COMPANIES_OUTBOUND_CONCURRENCYTeto de chamadas simultâneas à porta de pessoa e ao loteNão8
COMPANIES_DOCUMENT_READING_TIMEOUT_MINUTESQuanto tempo um documento fica em READING antes de o bloco desistirNão30
COMPANIES_EXPIRY_SCAN_ENABLEDVarredura de vencimento (só avisa)Nãotrue
COMPANIES_MAX_COMBINATIONSTeto de combinações conjuntas enumeradas por avaliação (truncated: true acima)Não200
MODULE_COMPANIES_URLEndereço do bloco no modo standaloneSó em standalone—
MODULE_PEOPLE_URLEndereço do People, para a porta people em standaloneSó 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ódigoSignificaO que fazer
COMPANIES_INVALID_CNPJDígito verificador não fechaCorrija a entrada
COMPANIES_EXTERNAL_REF_CONFLICTexternalRef já é de outra empresa deste donoCorrija o chamador; mudar documento é correção, não criação
COMPANIES_PERSON_NOT_FOUNDA porta people não conhece a pessoa neste escopoCadastre no People (ou use a porta external)
COMPANIES_PERSON_REF_INVALIDIdentificador fora do formato da portauuid para people; até 120 caracteres para external
COMPANIES_ACT_REQUIREDact ausente e política sem defaultActDeclare o ato ou configure o padrão
COMPANIES_ACT_UNKNOWNAto fora do catálogo da políticaVeja details.acts
COMPANIES_SOURCE_DOCUMENT_REQUIREDConferir exige documento-fonteInforme sourceDocumentId
COMPANIES_BATCH_TOO_LARGELote acima do tetoPagine
COMPANIES_FILE_NOT_FOUNDfileId não é desta organizaçãoSuba o arquivo no File Storage com o mesmo token
COMPANIES_SOURCE_DOCUMENT_NOT_VALIDATEDConferir exige documento VALID com arquivoRevise o documento (PATCH .../documents/:id) antes
COMPANIES_DOCUMENT_FILE_REQUIREDVALID sem arquivoAnexe o arquivo; documento que ninguém abre não valida nada
COMPANIES_IDEMPOTENCY_CONFLICTMesma chave, outro documentoCorrija o chamador; nada foi gravado
COMPANIES_REPRESENTATIVE_CONFLICTPoder aberto com a mesma pessoa/papel/modo/validFrom e termos diferentesRevogue o anterior ou use outro validFrom
COMPANIES_AMBIGUOUS_SUBJECTChave de organização e a referência casa com mais de uma subcontaInforme X-Subaccount-Id
FILE_STORAGE_UNAVAILABLEFachada de file-storage ausente no processoO fileId não é gravado sem conferência; suba o file-storage ou anexe sem arquivo

14

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).


15

Limitações conhecidas

LimitaçãoDetalheGravidade
Sem cadeia societária até beneficiário finalsharePercent 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ênciaFora deste PR
Sem listas/complianceNem por empresa (a consulta não existe no Bureaus) nem agregada pelas pessoasFora deste PR
Sem dossiê, mesa, prazos e revisão periódicaSem CompanyDossier, estados captada → … → conta aberta, devolutiva, auto de decisão. Gancho: assurance já diz o que faltaFora deste PR
Não monta o envelope de assinaturaO 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 consumidorDependência registrada
Porta de provedor: só registro básico e QSALigada 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 CPFRegistrada
Leitura de documento não ligadarequestReading grava intenção e prazo; a porta ao Data Extraction não existe. O prazo evita READING eternoPor desenho, v1
Conferência não é datadaverifiedAt conta como conferido em qualquer at. Vigência, revogação e documento-fonte são datadosRegistrada
fileId é conferido por organização, não por subcontaO 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 blocoDependência registrada
Vínculo encerrado não revoga o poderEncerrar o vínculo publica representation.updated(link_ended) para os poderes vivos daquela pessoa; revogar continua sendo ato explícitoPor desenho
Sem cache de avaliaçãoA 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 justifiqueRegistrada
Rota de assurance da fachada é uma por empresagetAssurance da fachada recebe um companyId; não há /assurance/batch aindaRegistrada
Sem armazenamento imutávelA trilha é à prova de evidência, não à prova de escritaFrente de segurança

16

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:

  1. Cadastro da empresa cedente — POST /companies pelo CNPJ, idempotente por externalRef (o id do lead), ou POST /companies/ingest para a base inteira.
  2. Vínculo com o médico — POST /companies/:id/people com o id da pessoa no People (ou o id do lead, na porta external). O médico continua sendo lead no CRM.
  3. Representação declarada e conferida — POST .../representatives com o contrato social como sourceDocumentId, e .../verify pela mesa. Até conferir, evaluate diz not_verified.
  4. Termo assinado por quem evaluate apontou — o gerador do termo chama evaluateRepresentation pela fachada com act: 'cessao_de_creditos' e o valor do borderô, e monta o envelope no e-signature com sufficient[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.