Skip to content

Contrato de leitura de tenants — trilho escopado (D11) ​

A leitura de tenants passa a ter dois trilhos, espelhando o padrão de users (Épico 889, F7):

TrilhoEndpointsQuemEscopo
Tenant (escopado)GET /api/v1/tenants, GET /api/v1/tenants/{id}qualquer read:tenantssó os tenants que o caller pode ver
Admin (global)GET /api/v1/admin/tenants, GET /api/v1/admin/tenants/{id}admin:system (F9: manage:all-tenants)todos os tenants

read:tenants significa "ler os tenants que você pode ver", nunca a plataforma inteira. Um admin:system que chame o trilho tenant recebe o próprio escopo — a visão global vive só no trilho admin (simetria com o trilho de users).

Escopo do caller ​

O escopo é a lista de vínculos do caller (não o tenant do token):

  • todos os tenants onde o caller tem um UserTenant ativo;
  • mais os filhos ativos de qualquer grupo onde o caller é GroupAdmin (enforcement real de read:group-tenants).

Caller sem vínculos ⇒ lista vazia (não é erro).

GET /tenants — lista escopada ​

  • Item: TenantDto (contrato existente, inalterado). Filtros type, isActive, hierarchyScope (+ parentTenantId), search, ordenação e paginação continuam valendo dentro do escopo — inclusive hierarchyScope=grouped e o resumo GET /tenants/summary (admin-list-summaries-contract).
  • Nenhum tenant fora do escopo aparece em nenhuma combinação de filtros.

GET /tenants/{id} — detalhe escopado (contrato summary) ​

  • Fora do escopo ⇒ 404 anti-disclosure (indistinguível de um id inexistente).

  • Payload: TenantSummaryDto — apenas identidade e hierarquia:

    json
    {
      "id": "…",
      "name": "…",
      "type": "Company",
      "isActive": true,
      "parentTenantId": "…",
      "parentTenantName": "…",
      "isGroupTenant": false
    }
  • Sem campos operacionais/sensíveis: nada de settings (política de MFA etc.), maxUsers, activeUserCount, domain, description ou timestamps. O contrato completo (TenantDto, com settings) é exclusivo do trilho admin.

⚠️ type como string no summary ​

TenantSummaryDto.type serializa como string ("Company" / "Group"), padrão v5 — igual ao type do tenant na resposta de login. Cuidado com a assimetria: o TenantDto da lista ainda serializa type como int (compatibilidade). Ao consumir os dois endpoints, trate type do detalhe como string e o da lista como int (ou normalize no cliente).

Trilho admin (global) — GET /admin/tenants ​

  • GET /api/v1/admin/tenants: lista global paginada, com paridade de filtros com o trilho tenant (type, isActive, hierarchyScope + parentTenantId, search, ordenação, paginação). Item: TenantDto completo.
  • GET /api/v1/admin/tenants/{id}: detalhe global com TenantDto completo (inclui os campos operacionais que o summary omite — maxUsers, activeUserCount, domain, description, timestamps).
  • Gate: admin:system (F9 passa a aceitar também manage:all-tenants). Sem ele ⇒ 403; sem token ⇒ 401. A query é ICrossTenantRequest (acesso cross-tenant exigido pelo pipeline).

Campo de referência — GET /tenants/lookup e GET /admin/tenants/lookup ​

Para selecionar um tenant ou mostrar o nome de ids já gravados (campo type: 'reference'), use o lookup do trilho, não a listagem: item { id, name, description, isActive? }, busca por nome ou domínio, até 50 por página e hidratação em lote com ?ids=. Mesma permissão e mesmo escopo da listagem do trilho. Contrato em tenant-lookup-contract.

Escrita permanece no TenantsController ​

As rotas de escrita de tenants — criar/atualizar/excluir e as operações de grupo — continuam nos endpoints atuais do TenantsController (POST/PUT/DELETE /api/v1/tenants…) com os gates atuais (create:tenants / update:tenants / delete:tenants / manage:child-tenants). Não há espelho de escrita em /admin/tenants — isso evita breaking change de rotas. Após a despromoção (F8), apenas o operador global mantém essas permissões de escrita.

UI: capability em vez de if de permissão ​

A UI de tenants deve decidir o trilho por capability (tenants.default), não por checagem de permissão espalhada: perfil escopado consome /tenants; perfil global consome /admin/tenants. O mesmo componente serve os dois perfis, trocando apenas a base do endpoint. Escrita usa sempre /tenants (gated por permissão).

Released under the MIT License.