Skip to main content

Ligar e desligar módulos por tenant e unidade — API

Um módulo, neste contexto, é uma funcionalidade inteira do produto que pode ser ligada ou desligada por tenant e, opcionalmente, sobrescrita por unidade — por exemplo peer_review (segunda opinião de laudo), ai_report, command_center, worklist, messaging, entre outras 14 chaves fixas do catálogo. A rota não guarda "o estado atual" como uma coluna mutável: guarda um histórico de controles (quem ligou/desligou e quando), e o estado exibido é sempre projetado a partir do controle ativo mais recente.

Funcionamento​

  1. Toda rota exige um access token válido (JwtAuthenticationGuard). A permissão é checada por OrganizationModuleAuthorizationService: ler exige permission:read/permission:manage no tenant (ou também na unidade, para rotas de unidade); escrever (PUT/DELETE) exige permission:manage no tenant ou na unidade. Um administrador de plataforma sempre passa.
  2. Listar/consultar no tenant (GET .../modules, GET .../modules/:key) projeta as 14 chaves do catálogo a partir do controle de tenant ativo mais recente de cada uma. enabled reflete o último estado gravado; configured diz se já existe algum controle de tenant para aquela chave — um módulo pode estar enabled: false, configured: true (foi desligado explicitamente) ou enabled: false, configured: false (nunca foi mexido), e essas duas situações são distintas.
  3. Listar/consultar na unidade (GET .../units/:unitId/modules[/:key]) aplica a mesma projeção, mas com precedência: se a unidade tem um override próprio, ele vence — mesmo que o valor seja false (desligar explicitamente na unidade sobrepõe um tenant com o módulo ligado). Sem override de unidade, herda o valor do tenant; sem nenhum dos dois, o módulo é considerado desligado (source: NONE). A resposta expõe source (UNIT/TENANT/NONE), o valor de tenant (tenantDefault) e o de unidade (unitOverride) separadamente, além do enabled já resolvido.
  4. Ligar/desligar (PUT .../modules/:key, no tenant ou na unidade) grava um novo controle (histórico) com o status pedido. Pedir o status que já está vigente é tratado como no-op: nada é gravado, nenhum evento de auditoria é emitido. Toda escrita roda dentro de uma transação com lock pessimista no tenant (e na unidade, se aplicável) e publica um evento de auditoria (outbox) com o antes/depois.
  5. Remover o controle (DELETE .../modules/:key) faz um soft-delete do controle ativo — a linha de histórico permanece, só deixa de estar ativa. É idempotente: remover quando não há controle ativo (ou quando o módulo nunca foi configurado) também devolve 204, sem erro. Depois de remover, o tenant volta a "não configurado"; a unidade volta a herdar o valor do tenant.
  6. Um módulo pode ficar administrável (ligar/desligar via esta API) sem que nenhuma regra de negócio no resto do backend realmente o aplique ainda — o catálogo interno de "consumidores" marca cada chave como ENFORCED (tem consumidor comprovado por código e teste — hoje só peer_review), PROJECTED (o estado é calculado e exposto, mas nada no backend refatorado ainda consulta) ou BLOCKED (a chave está deliberadamente impedida de virar ENFORCED até uma tarefa de migração específica). Isso é controlado por um gate de migração (script/teste), não por nenhuma rota HTTP — não afeta o comportamento observável desta API, é uma proteção interna de rollout.

Endpoints​

MétodoRotaDescrição
GET/v1/tenants/:tenantId/modulesLista o estado das 14 chaves no tenant
GET/v1/tenants/:tenantId/modules/:keyEstado de uma chave no tenant
PUT/v1/tenants/:tenantId/modules/:keyLiga/desliga o módulo no tenant
DELETE/v1/tenants/:tenantId/modules/:keyRemove o controle de tenant (volta a "não configurado")
GET/v1/tenants/:tenantId/units/:unitId/modulesLista o estado das 14 chaves na unidade, com precedência já aplicada
GET/v1/tenants/:tenantId/units/:unitId/modules/:keyEstado de uma chave na unidade
PUT/v1/tenants/:tenantId/units/:unitId/modules/:keyCria/atualiza um override de unidade
DELETE/v1/tenants/:tenantId/units/:unitId/modules/:keyRemove o override de unidade (volta a herdar do tenant)
GET/v1/operations/organization/tenants/:tenantId/modules/:key/historyHistórico completo de controles de tenant para uma chave (só platform-admin)

Versão: v1

Swagger:

Rota (Dev): http://localhost:3000/v1/tenants/:tenantId/modules

A rota de histórico está fisicamente implementada no controller de administração do submódulo unit (OrganizationAdministrationController, tag "Organization — Administration"), não no controller de módulos — é o único consumidor HTTP do serviço de histórico deste submódulo.

Lógica de decisão de PUT .../modules/:key (tenant ou unidade):

Permissões​

RotaGuardsPerfil / escopo exigido
GET de tenantJwtAuthenticationGuardpermission:read/permission:manage no tenant, ou platform-admin
PUT/DELETE de tenantJwtAuthenticationGuardpermission:manage no tenant, ou platform-admin
GET de unidadeJwtAuthenticationGuardpermission:read/permission:manage no tenant ou na unidade, ou platform-admin
PUT/DELETE de unidadeJwtAuthenticationGuardpermission:manage no tenant ou na unidade, ou platform-admin
GET de históricoJwtAuthenticationGuard, AuthorizationGuard (@Authorize({scope:{kind:'platform-admin'}})) + checagem interna assertCanManageTenantsSomente administrador de plataforma — gestor de tenant/unidade comum não acessa, mesmo com permission:manage

Headers​

HeaderObrigatórioDescrição
AuthorizationSimBearer <access_token>
Content-TypeSim em PUTapplication/json

Path parameters​

NomeTipoObrigatórioDescrição
tenantIdUUIDSimIdentificador do tenant
unitIdUUIDSim (rotas de unidade)Identificador da unidade
keystringSim (exceto list)Uma das 14 chaves do catálogo (OrganizationModuleKey); chave fora do enum → 404, não 400

Query parameters​

Nenhum.

Body​

PUT .../modules/:key — SetOrganizationModuleRestRequest:

json
{ "enabled": true }
CampoTipoObrigatórioValidação
enabledbooleanSim@IsBoolean(); corpo aceita só este campo (whitelist + forbidNonWhitelisted)

Response​

200 — estado de tenant (OrganizationTenantModuleEnvelopeRestResponse / lista):

json
{ "data": { "key": "peer_review", "enabled": true, "configured": true } }

200 — estado de unidade (OrganizationUnitModuleEnvelopeRestResponse / lista):

json
{
"data": {
"key": "peer_review",
"enabled": false,
"source": "UNIT",
"tenantDefault": true,
"unitOverride": false
}
}

200 — histórico (OrganizationTenantModuleControlHistoryEnvelopeRestResponse):

json
{
"data": [
{ "active": false, "createdAt": "2026-01-10T12:00:00.000Z", "deletedAt": "2026-02-01T09:00:00.000Z", "enabled": true },
{ "active": true, "createdAt": "2026-02-01T09:05:00.000Z", "deletedAt": null, "enabled": false }
]
}

DELETE (tenant e unidade) devolve 204 sem corpo.

Erros​

Classe de erroerrorCodeStatusQuando ocorre
Erro de validação do Nest—400tenantId/unitId não é UUID; enabled ausente/não-booleano; corpo com campo extra
OrganizationModuleNotFoundErrorORGANIZATION_MODULE_NOT_FOUND404:key não pertence às 14 chaves do catálogo
ForbiddenActionORGANIZATION_MODULE_ACCESS_FORBIDDEN403ator sem a permissão exigida no tenant/unidade
OrganizationTenantNotFoundErrorORGANIZATION_TENANT_NOT_FOUND404tenant inexistente/inativo
OrganizationUnitNotFoundErrorORGANIZATION_UNIT_NOT_FOUND404unidade inexistente/inativa, ou não pertence ao tenant do path
OrganizationModuleCapabilityDisabledErrorORGANIZATION_MODULE_CAPABILITY_DISABLED403não é lançado por nenhuma rota deste controller — pertence à checagem interna que outros módulos do backend fazem (ex. laudo verifica peer_review) antes de agir; documentado aqui por ser o mesmo catálogo
ForbiddenAction (histórico)ORGANIZATION_TENANT_MANAGE_FORBIDDEN403ator autenticado mas não é administrador de plataforma, ao chamar a rota de histórico

Regras de negócio​

IDRegraComportamento esperado
RN-01Catálogo fixo de 14 chaves, cada uma suportando os dois escopos (tenant e unidade)Chave fora do enum → 404, não 400
RN-02Override de unidade sempre vence sobre o default do tenant quando presente, mesmo que o valor seja falseDesligar explicitamente na unidade sobrepõe um tenant ligado
RN-03"Configurado" é diferente de "desligado" no tenantenabled: false, configured: true (desligado de propósito) ≠ enabled: false, configured: false (nunca configurado)
RN-04Pedir o status já vigente é no-opNão grava linha nova, não emite evento de auditoria
RN-05Remover é idempotente e é soft-deleteChamar DELETE duas vezes não falha; a linha de histórico é preservada (deletedAt), nunca apagada
RN-06Toda escrita roda em transação com lock pessimista no tenant (e unidade)Evita condição de corrida entre duas trocas concorrentes do mesmo módulo
RN-07Toda escrita publica evento de auditoria via outbox, com antes/depoisBase para a trilha de "quem ligou/desligou o quê e quando"
RN-08Existe no máximo um controle ativo por (tenant, módulo) e por (unidade, módulo)Garantido por índice único parcial no banco (WHERE deleted_at IS NULL)
RN-09Histórico de controle só existe para o escopo tenant, sem paginação, ordenado do mais antigo para o mais recente, incluindo registros removidosNão há histórico equivalente por unidade nesta API
RN-10A rota de histórico é restrita a administrador de plataformaDiferente de todas as outras rotas deste grupo, que aceitam gestor de tenant/unidade com permission:manage
RN-11Um módulo pode estar administrável sem ter efeito real no resto do sistemaO catálogo interno de consumidores (ENFORCED/PROJECTED/BLOCKED) não muda o comportamento da API, mas explica por que ligar um módulo PROJECTED (ex. power_bi) hoje não altera nada visível fora desta consulta

Variáveis de ambiente​

Nenhuma variável de ambiente controla estas rotas. O catálogo de 14 chaves e o mapeamento de consumidores são constantes no código.

Tempo médio de resposta​

A confirmar — responsável: time de Organization; data: 24/09/2026. Não há medição publicada.

Requisitos não funcionais​

RequisitoDefinição
IdempotênciaPUT: não (pedir o mesmo status é no-op, mas pedir status diferente sempre cria novo histórico) — na prática o resultado final é idempotente. DELETE: sim
PaginaçãoNão — listagens sempre trazem as 14 chaves; histórico não pagina
Rate limitNão observado
CacheNão
AuditoriaSim — evento de outbox em toda troca de status, mais o histórico completo consultável (só tenant, só platform-admin)

Relacionado​

  • 🖥️ Tela: A confirmar — responsável: time de frontend; data: 24/09/2026. (não mapeada neste levantamento)
  • 📂 Módulo: Tenant / Organização
  • 🔗 Configuração: Configuração (tenant e unidade) — mecanismo irmão, com a mesma cascata tenant→unidade, mas para valores tipados em vez de features inteiras