'use strict'; /** * Guarda de envio real (Slack) e idempotencia — secoes 12, 13 e 15 da * missao original, usando o MESMO vocabulario (SEND_ENABLED/FORCE_SEND) * ja em uso no projeto irmao "[DEV] Coordenador GLPI - Chamados Abertos * v2 (Shadow)" (workflow y6JFEnp7Z2IJEUe2), para manter consistencia no * ambiente em vez de inventar um padrao novo. * * Regra de decisao (todas as condicoes descritas na missao reduzidas a uma * formula unica, testada exaustivamente em sendGuard.test.js): * * podeEnviar = * SHADOW_MODE === false (absoluto — nada sobrepoe) * && DEV_MODE === false (absoluto — nada sobrepoe; enquanto este * workflow viver em DEV, NUNCA envia) * && SEND_ENABLED === true (absoluto) * && (isScheduled || FORCE_SEND === true) (manual so envia com override explicito) * * FORCE_SEND so tem efeito na execucao MANUAL (destrava o "manual nao * envia por padrao"). Ele nunca sobrepoe SHADOW_MODE/DEV_MODE/SEND_ENABLED. */ function canSend({ triggerType, config }) { const isScheduled = triggerType === 'scheduled'; if (config.SHADOW_MODE === true) { return { podeEnviar: false, motivo: 'SHADOW_MODE=true (absoluto)' }; } if (config.DEV_MODE === true) { return { podeEnviar: false, motivo: 'DEV_MODE=true (absoluto, sem override enquanto viver em DEV)' }; } if (config.SEND_ENABLED !== true) { return { podeEnviar: false, motivo: 'SEND_ENABLED=false' }; } if (!isScheduled && config.FORCE_SEND !== true) { return { podeEnviar: false, motivo: 'execucao manual sem FORCE_SEND=true' }; } return { podeEnviar: true, motivo: isScheduled ? 'scheduled + guards ok' : 'manual + FORCE_SEND=true' }; } /** * Hash estavel (nao criptografico — so precisa ser deterministico e * compacto) do texto da mensagem, para a chave de idempotencia detectar * se o CONTEUDO mudou entre duas tentativas do mesmo periodo. */ function computeMessageHash(texto) { const str = String(texto || ''); let hash = 5381; for (let i = 0; i < str.length; i += 1) { hash = (hash * 33) ^ str.charCodeAt(i); } // >>> 0 converte para unsigned 32-bit antes de exibir em base36 (compacto) return (hash >>> 0).toString(36); } /** * Chave logica de idempotencia (secao 13): duas execucoes com a MESMA * chave representam o MESMO relatorio logico e nao devem ser enviadas * duas vezes, mesmo com retry/execucao manual. */ function buildSendKey({ reportType, periodStart, periodEnd, destination, messageHash }) { return [reportType, periodStart, periodEnd, destination, messageHash].join('|'); } /** * Verifica se ja existe um envio com status=success para a mesma chave * logica (report_type + period + destination + message_hash). */ function isDuplicate(sendLogRows, key) { return (sendLogRows || []).some( (row) => row && row.send_key === key && row.status === 'success' ); } module.exports = { canSend, computeMessageHash, buildSendKey, isDuplicate };