Pular para conteúdo

Runbook — semear o catálogo de papéis de um app (app_roles)

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-04

O catálogo de papéis por app é data-driven: a migration V12 cria a tabela app_roles vazia (migration é schema, não dado). Cada app precisa ter os papéis cadastrados antes do 1º approve daquele app — senão o approve responde 422 role_not_in_catalog.

Pré-condição: o app precisa existir no catálogo apps

app_roles.app_id tem FK → apps(app_id). A linha apps nasce da catalogação da Central de Apps (POST /api/setup/provision a partir do app.json, ou backfill). Confirme:

SELECT app_id FROM apps WHERE app_id = 'bi-comercial';

Se não existir, catalogue o app primeiro (fluxo /xadm-setup / broker) — não insira papéis antes.

Semear pelo que o app declara (caminho preferido)

Quem sabe quais papéis existem é o app — é o código dele que faz o gate. Ele publica a lista em GET <base>/roles-catalog.json e o central ingere (decisão 0025):

curl -sS -X POST "https://central-backend.xadm.biz/api/admin/apps/bi-comercial/roles/sync" \
  -H "Authorization: Bearer $XADM_JWT" -H "Content-Type: application/json" -d '{}'
# {"app_id":"bi-comercial","base_url":"https://bi.sulplata.xadm.biz",
#  "declarados":4,"criados":4,"atualizados":0,"roles":["ADMIN","COMERCIAL","DIRETORIA","LUBRIFICANTE"]}
  • Idempotente: rodar de novo devolve criados:0. Rode sempre que o app ganhar um papel novo — é o passo que fecha o drift entre o gate do app e o picker do central.
  • Não deleta. Papel que sumiu da declaração fica no catálogo (apagá-lo órfãozaria os grants que já o referenciam). Remoção é o DELETE manual abaixo, depois de tratar os grants.
  • Sem production_url alcançável (e2e local), passe -d '{"base_url":"http://localhost:8101"}'.
  • 502 roles_catalog_unreachable → o app não respondeu ou devolveu JSON inválido; 422 → o base_url não é http(s) ou o app declarou um papel maior que a coluna.

Semear papel a papel (saída manual)

Quando o app ainda não serve o roles-catalog.json, ou para corrigir um caso pontual. Com um JWT xadm (xadm_admin=true):

for r in ADMIN:Administrador DIRETORIA:Diretoria COMERCIAL:Comercial LUBRIFICANTE:Lubrificante; do
  role="${r%%:*}"; label="${r##*:}"
  curl -sS -X POST "https://central-backend.xadm.biz/api/admin/apps/bi-comercial/roles" \
    -H "Authorization: Bearer $XADM_JWT" -H "Content-Type: application/json" \
    -d "{\"role\":\"$role\",\"label\":\"$label\"}"
done
  • role é gravado em UPPERCASE (machine-code; vai verbatim no claim role do JWT e é o valor que a sync rule do PowerSync compara).
  • 409 role_exists → o papel já existe (idempotente na prática; ignore).
  • Este é o caminho que drifta: o app ganha um papel, ninguém repete o POST aqui, e o papel não aparece no picker — sem erro nenhum. Prefira o sync acima.

Conferir: GET /api/admin/apps/{app}/roles (aqui {app} = bi-comercial) ou

SELECT role, label FROM app_roles WHERE app_id = 'bi-comercial' ORDER BY role;

Papéis do bi-comercial (referência)

role label significado
ADMIN Administrador vê tudo + administra usuários do app
DIRETORIA Diretoria vê tudo, não administra usuários
COMERCIAL Comercial operacional (recorte comercial na sync rule)
LUBRIFICANTE Lubrificante operacional (só lubrificante na sync rule)

pAbast intocado. Esta feature é aditiva: usuários que logam via pAbast seguem inalterados; o catálogo/aprovação só governa o acesso social (oauth_firebase).