Commit Graph
8 Commits
Author SHA1 Message Date
GustavoandClaude Sonnet 5 26b06e07d5 feat(v2): REAL Slack send confirmed working end-to-end
After the user added the missing OAuth scopes (channels:join,
chat:write.public) and reinstalled the Slack App, updated the credential
with the refreshed token and retried the real test send: SUCCESS. The
digest message was actually posted to #chamados-suporte (channel
C0BJNBTPFT5) by "Bot Coordenador", confirmed via the real Slack API
response (ok:true, real message ts/channel/bot_profile returned).
Code - Registrar Envio Sucesso correctly recorded status:'success' --
this record is intentionally left in place (not cleared like the earlier
failed-test rows), since it now correctly represents a real send and its
whole purpose is to prevent a duplicate resend of today's digest.

Also found and fixed a real cosmetic bug from that same successful send:
n8n's Slack node appends an "Automated with this n8n workflow" link
footer by default (otherOptions.includeLinkToWorkflow defaults to true)
-- not part of the message spec (minimal digest only). Disabled it.
Added a regression test.

Reverted SEND_ENABLED to false and deactivated pending final user
confirmation before permanent production activation.

159/159 tests passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 21:37:17 -03:00
GustavoandClaude Sonnet 5 617c67493f fix(v2): envio-digest insert nodes were missing the required columns mapping
Found running the real Slack test send (with the user's explicit
confirmation to try SEND_ENABLED=true once): the Slack node itself fired
correctly and got a real response back from Slack's API ("invalid_arguments"
-- confirms the pipeline mechanics, credential and node config work end
to end up to that point). Separately, once the failure branch tried to
record it, "Data Table - Registrar Envio Digest (Falha)" crashed with
"Cannot convert undefined or null to object" inside n8n's own insert
operation -- both new envio-digest insert nodes had operation:"insert"
and dataTableId but no "columns" mapping, which is a required property
for insert/update/upsert (confirmed via get_node nodes-base.dataTable).
Fixed both nodes with the same columns.mappingMode:"defineBelow" shape
already used by the existing "Data Table - Registrar Shadow Run" node.

Added a regression test scanning every dataTable node with an insert/
update/upsert operation for a columns.mappingMode.

158/158 tests passing.

Separately (not fixed here, needs the user): the actual Slack
"invalid_arguments" error suggests $vars.SLACK_CHANNEL_TECNICO isn't
resolving to a valid channel on this n8n instance -- no tool available
to this agent can list/inspect n8n instance Variables to confirm. Left
SEND_ENABLED=false and the workflow inactive pending the user's input on
the correct channel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 21:02:05 -03:00
GustavoandClaude Sonnet 5 86039ecfe4 fix(v2): Data Table - Buscar Envios Recentes needs alwaysOutputData (real bug found in DEV validation)
Found running the real validation execution against DEV: the envios
digest table starts empty (the normal state for every day until the
first successful send), the "get" operation returned 0 rows, and n8n
does not execute downstream nodes when a node outputs 0 items -- so
Code - Guard de Envio Slack, the IF, the real Slack node, and everything
else in the send branch silently never ran. This would have broken the
happy path on every single day in production, not just an edge case.

Fixed with alwaysOutputData: true on that node (forces at least one
output item even with 0 matches; the resulting empty {} item doesn't
false-positive in alreadySentSuccessfully since none of its fields match
a real record's shape). Re-ran the validation execution after the fix:
Code - Guard de Envio Slack now runs correctly (shouldSend: false,
reason: 'send_disabled' with SEND_ENABLED=false as expected), and the
IF node's true branch (Slack - Enviar Mensagem) is confirmed to never
fire while disabled.

Added a regression test asserting alwaysOutputData on this node.

157/157 tests passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 20:56:02 -03:00
GustavoandClaude Sonnet 5 d64b1132b9 feat(v2): wire Slack send branch + Schedule Trigger into the template, generalize transitive-dep test
n8n/workflow.template.json:
- Added the 10 new nodes for the real Slack send branch (Data Table -
  Buscar Envios Recentes, Code - Guard de Envio Slack, IF - Deve Enviar
  Slack?, Slack - Enviar Mensagem, Code - Pular Envio Slack, Code -
  Registrar Envio Sucesso/Falha, the two Data Table - Registrar Envio
  Digest nodes, Code - Lancar Erro Envio Slack) and wired every
  connection -- Code - Montar Digest Coordenacao now fans out to both
  the existing shadow-diff branch (unchanged, always runs) and the new
  send branch in parallel, so a Slack failure never loses the shadow
  audit trail.
- Renamed "Schedule Trigger - Dias uteis 08h (desligado em DEV)" ->
  "Schedule Trigger - Dias uteis 08h": it was already wired to Config
  and already had the correct cron (0 8 * * 1-5) from earlier in the
  project -- this cycle didn't need to build it, just stop calling it
  "desligado" now that it's genuinely production-track (activation is
  controlled by the workflow's own active flag, not the node's name).
- Added settings.timezone: "America/Sao_Paulo" explicitly (previously
  implicit/unset) -- matches the timezone already hardcoded into
  buildTopMessage's dateStr computation elsewhere in this codebase, not
  a new assumption.
- Config node: AI_MAX_CANDIDATES 30->60, added ENVIRONMENT/SEND_ENABLED.

tools/build-workflow.test.js: 10 new tests (connections integrity after
a node rename -- would have caught a real bug this cycle where renaming
the Schedule Trigger orphaned its connection key; Schedule Trigger wired
+ correct cron/timezone; Manual Trigger still present; real Slack node
exists with the right credential/onError; guard runs before Slack;
per-ticket envio registration only fires after the success Data Table
write; Slack failure always registers before throwing; AI_MAX_CANDIDATES
== CANDIDATE_POOL_SIZE; SEND_ENABLED/ENVIRONMENT present; GLPI SQL/query
scan for INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/TRUNCATE; dataTable node
operation values are valid enum members -- caught a real bug, "getRows"
[the MCP tool's action name] isn't a valid node parameter value, it's
"get"). Replaced the narrow normalize.js-specific transitive-dependency
test with a general one that scans every logic/*.js top-level function
and asserts any node calling it also has it inlined -- this caught a
second real instance of the same bug class (slack.js's use of hash.js's
djb2Hash, missing from 3 new node headers) before it reached a live run.

n8n/deployed/coordenador-glpi-v2.n8n.json regenerated (43 nodes).

156/156 tests passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 20:51:15 -03:00
GustavoandClaude Sonnet 5 edecd271b9 fix(v2): fix missing transitive dependency (normalize.js) in 3 node drivers
Found while syncing this cycle's fixes to the live DEV workflow and
preparing Run A: tools/build-workflow.js's readLogicSource() only inlines
whatever a node's own `@requires` header lists -- it does not resolve
transitive dependencies. context.js started doing `require('./normalize')`
earlier this cycle, but the three node drivers that need normalizeTicket/
buildAiContextText (which call normalizeGlpiText internally) still only
declared `@requires context.js`, never `normalize.js` itself.

The result compiles fine (new Function(...) syntax-checks pass -- a
ReferenceError is a runtime error, not a syntax error) but would throw
"normalizeGlpiText is not defined" the moment any of these three nodes
actually ran:
- Code - Mesclar Contexto e Calcular Hash (merge-context-hash.node.js)
- Code - Normalizar Candidatos Base (normalize-candidates.node.js)
- Code - Preparar Prompt IA (prepare-ai-prompt.node.js)

This never surfaced in Run 1 (prior session) because normalize.js/the
descricao field didn't exist yet at that point, and it didn't surface in
this cycle's local test suite because node--test only exercises the pure
logic/*.js modules directly (with real requires), never the inlined,
concatenated jsCode a node actually runs in n8n.

Fixed by adding normalize.js explicitly to each of the three headers,
matching the codebase's existing convention for multi-file deps (e.g.
compute-score.node.js already lists `@requires cooldown.js score.js`
together instead of relying on any transitive resolution).

Added a regression test that scans every generated Code node for calls to
normalizeGlpiText/normalizeTicket/buildAiContextText and asserts the
normalizeGlpiText function definition is actually present in that same
inlined source -- would have caught this before it ever reached DEV.

102/102 tests passing (was 101/101 -- +1 new regression test).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 20:01:14 -03:00
GustavoandClaude Sonnet 5 3c87ab55e1 fix(v2): fix cache is_fallback bug, wire observability + data quality, drop debug webhook, fix GLPI URL
- Real bug found by code review while implementing this cycle: "Code -
  Reusar Classificacao em Cache" always hardcoded is_fallback: false when
  reconstructing a classification from memory, even when the cached
  classification was itself AI_FALLBACK. On a cache hit for a previously
  fallback-classified ticket, this would have let it skip
  applyFallbackPolicy's exclusion entirely (the policy checks
  aiClassification.is_fallback first). Fixed to derive is_fallback from
  the cached ai_classification value.
- select-top10.node.js now carries `resolucao` (ia | cache | fallback_cap)
  through to the scored item, previously dropped -- needed to tell cache
  hits apart from real AI calls in observability.
- Code - Comparar Legacy x V2 now computes the full observability summary
  (candidate_count, cache_hits/misses, ai_requested/success/errors,
  ai_fallback_count and by-reason breakdown, classification_counts,
  insufficient_context_count, actionable_real_count,
  critical_override_count, top10_count, blocked_count,
  coordination_count) and the data-quality review (buildDataQualityReview
  over INSUFFICIENT_CONTEXT tickets), both stored in the shadow_runs
  `summary` JSON column -- no new Data Table needed.
- Removed the debug webhook entirely (Debug - Ver Painel, Data Table -
  Buscar Shadow Runs Recentes debug branch, Code - Montar Painel Debug,
  Debug - Responder) and the now-unused debug-panel.node.js driver.
  Workflow went from 37 to 33 nodes. Debugging goes through n8n execution
  data, the Data Tables, and the shadow diff instead, as requested.
- Fixed glpi_base_url: was still V1's own placeholder
  (https://glpi.suaempresa.com.br, never a real value, not report
  anonymization) -- confirmed the real domain (https://glpi.gtigrupo.com.br)
  from the live, active GLPI x WhatsApp workflow's own GLPI API config in
  the same n8n instance. SQL still only ever receives it through
  {GLPI_BASE_URL} substitution from the Config node, never hardcoded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 19:50:48 -03:00
GustavoandClaude Sonnet 5 a99a25cb1a feat(v2): complete n8n workflow template (36 nodes) covering full V2 pipeline + shadow branch
- 14 new Code node drivers implementing the pipeline stages: normalize
  candidates, build context SQL, merge context + hash, decide AI need,
  prepare AI prompt, validate AI response, AI-disabled fallback, reuse
  cached classification, compute score, select top 10, prepare memory/
  envio records, build message/digest, compare legacy x V2, debug panel.
- Consolidated the two context queries (followups/tasks) into a single
  UNION ALL query tagged by tipo, and added a "Merge - Unir Shadow"
  synchronization node before comparing legacy x V2: n8n does not
  guarantee relative execution order between sibling branches that only
  share an early common ancestor, so cross-branch data was only safe to
  read by name once a real Merge node forced both to complete first.
- Reordered score calculation before the memory upsert (score is needed
  in the persisted row) and turned both Data Table writes into
  side-effect branches that fan out from the main path instead of
  gating it, so the Data Table node's own output shape never needs to
  carry the full pipeline state forward.
- Fixed a duplicate-identifier bug in the build script: score.js's
  require('./cooldown') survived inlining and collided with
  cooldown.js's own computeCooldown function in the same Code node
  scope. build-workflow.js now strips inter-module requires when
  inlining, and a new test syntax-checks every generated jsCode string
  plus asserts no leftover require() calls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:31:44 -03:00
GustavoandClaude Sonnet 5 ec596a29a8 feat(v2): add build script that inlines tested logic/*.js into n8n Code nodes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:20:54 -03:00