0023 — Segmento denormalizado em bi_produto/bi_movimento (role-gating PowerSync)¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-02 · Decidido em: 2026-09-02
Contexto¶
O PowerSync vai passar a filtrar o bucket por role do JWT (central-backend 016): COMERCIAL vê
só combustível, LUBRIFICANTE só lubrificante, ADMIN/DIRETORIA tudo. bi_movimento
mistura os dois segmentos → o corte precisa ser row-level. A data-query do PowerSync não
faz JOIN nem subquery cross-table — a sync rule só enxerga a própria linha. Hoje bi_movimento só
tem cod_produto; o segmento vive no nome do produto (regra que hoje é regex no cliente Flutter,
produto_labels.dart). Migração V20.
Decisão¶
-
Coluna
segmento TEXTdenormalizada embi_produtoebi_movimento(combustivel/lubrificante/NULL). A sync rule lêWHERE segmento = 'combustivel'direto na linha debi_movimento— sem JOIN. -
Regra derivada do nome, dono único
Segmento.doNome(processing/):combustivel←^\s*ONU\s+\d+;lubrificante←MAXON\s+OILou\bON\s*LUB; senãoNULL.combustiveltem precedência. Autoridade = handoffintegracao/powersync(porta doproduto_labels.dart, que vive no cliente Flutter, fora deste repo). -
População inline na pipeline, diff-aware — não
UPDATE bi_movimento … FROM bi_produtopor carga. Os três readers que constroemProduto+Movimentono mesmo loop (Relatorio18Processor,MargemDiaProcessor.MovimentoSheetReaderreusado porMargemConsolidadaProcessor) chamamSegmento.doNome(nome)e setam nos dois;segmentoentra noWHERE IS DISTINCT FROMdos upserts. -
Backfill one-shot no
V20(ALTER ADD COLUMN IF NOT EXISTS+UPDATEdas duas + índiceidx_movimento_segmento). PG usa~*com\y; a paridade com o regex Java (\b) é garantida por teste (SegmentoBackfillParityTest, Postgres real), não por diff contra o Dart. -
NULL(Arla/Ureia) fica fora do COMERCIAL — a sync rule dá ao COMERCIALsegmento='combustivel'; linhaNULLsó chega em ADMIN/DIRETORIA.
Consequências¶
- Diff-aware preservado:
UPDATEcross-table por carga reescreveria a linha mesmo sem mudança (bumpaxmin→ checkpoint PowerSync desnecessário em todo movimento tocado). Inline +WHERE IS DISTINCT FROMnão toca linha inalterada — coerente com a doutrina doBulkUpserter. TestepipelineNasceComSegmentoEReuploadNaoBumpaXminprova (reupload exato →xminintacto). - Sem GRANT novo pro
powersync_role: coluna em tabela existente herda oGRANT SELECTtable-level (V13/default privileges); a regra de GRANT-cinto para tabela nova está no modelo de dados. - Mudança futura do regex não recomputa segmento de produto com
nomeestável (onome IS DISTINCTgate bloqueia) → exigiria nova migration de backfill, não só redeploy. Aceitável (regra de segmento muda raro e deliberado). - Divergência de
nomeentre arquivos (pré-existente donomediff-aware): segmento reflete o último arquivo a escrever onome. Não introduzido aqui; só vira trabalho se aparecer segmento errado em prod (aí, precedência de fonte probi_produto). - Fora deste repo: sync rule (
../powersync/), token com claimrole+ gating (central-backend 016), login do bi-comercial enviandoapp_id. A sync rule só aplica o YAML vivo depois desegmentopopulado em prod.
Alternativas consideradas¶
UPDATE bi_movimento m SET segmento = p.segmento FROM bi_produto pa cada carga (SQL literal do handoff): descartada —UPDATEpuro bumpaxminem todo movimento tocado = churn de checkpoint PowerSync, contra a doutrina diff-aware. Inline no processador é o caminho da casa.- Regex/sets de
cod_produtona SQL da sync rule (sem coluna): impossível — movimento não tem o nome, e lista curada decod_produtonão cobre código novo (conflito Paulínia). Denormalizar é o único corte confiável row-level. - Unificar com o filtro "só combustível" do
ComprasProcessor(startsWith("ONU")): descartada — aquele classifica fornecedor de compra (bi_compra/bi_fornecedor), com propósito e bordas próprios;Segmentoclassifica produto/movimento pro role-gating. Unificar arrastaria COMPRAS pro escopo e exigiria re-testar, sem ganho funcional. Regras separadas de propósito (spec 013, L9).
Correção 2026-09-14: o ponteiro para o
CLAUDE.md/HOWTO-DEPLOY.mdpassou para a página dona do fato; os dois arquivos deixaram de carregar esse conteúdo.Correção 2026-09-14: o andaime
.ia/013saiu do repo depois de destilado; o durável está nesta decisão e no modelo de dados.