Root cause found for why the real Slack message showed "→ Validar e
executar" for nearly every ACTIONABLE_NOW ticket: logic/message.js's
ACTION_LABELS map had a hardcoded entry for ACTIONABLE_NOW that always
won over the AI's actual next_action field in shortActionLabel(). The
AI's real, specific, already-validated-as-non-generic next_action (e.g.
"Realizar inventário das máquinas") was being silently discarded for
display purposes on every single message. Fixed by removing the
ACTIONABLE_NOW entry -- it now falls through to the existing
next_action-based fallback (already there, just unreachable for that
class before). UNASSIGNED_ACTIONABLE intentionally keeps its fixed
"Atribuir técnico" label -- that IS the correct display for that class.
logic/classification.js: expanded GENERIC_NEXT_ACTION_PHRASES with the
exact phrase that leaked through ("validar e executar") plus the
mission's additional list (validar chamado, prosseguir atendimento,
executar chamado, tratar chamado, analisar chamado). Bumped
AI_CLASSIFIER_VERSION 3->4 since this changes what next_action values
validate as non-generic -- forces reclassification of any cached
candidate that only passed under the older, narrower blacklist.
n8n/nodes/validate-ai-response.node.js: generic-next-action rejections
now get their own distinct fallback_reason ('generic_or_unsupported_
next_action') instead of being lumped into 'invalid_json', matching the
same pattern already used for the technician-vendor conflict.
logic/slack.js + logic/config.js: added FORCE_SEND (default false).
Manual-triggered runs now never send for real even with SEND_ENABLED=
true in production, unless FORCE_SEND is also explicitly set -- protects
against someone clicking "Execute Workflow" in production and
accidentally re-sending today's digest.
Added format-safety regression tests (no literal backslash-n, URL never
bleeds into the next item's id, no markdown-wrapped URLs) -- verified
these were never actually triggered in a real send (the reported
corruption was n8n's raw JSON data-inspector view, not a delivered
Slack message; both manual runs in question had shouldSend:false).
169/169 tests passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ran the deployed workflow for real (temporary webhook trigger, briefly
activated, immediately deactivated and removed after each attempt) against
the real GLPI MySQL database and a real OpenAI credential. Found and fixed:
1. $env access is blocked on this n8n instance (N8N_BLOCK_ENV_ACCESS_IN_NODE).
parseConfig($env) threw "access to env vars denied" on the very first
Code node. Config now comes from the "Config - Coordenador GLPI v2" Set
node instead (same pattern the V1 workflow already used) -- expanded
that node with all 13 tunable parameters, and pointed the 3 affected
node drivers (build-sql-candidatos, decide-needs-ai, select-top10) at
it instead of $env.
2. Data Table "upsert" operation requires both `columns` (values) and a
separate `filters` (match condition) -- I had only set columns with
matchingColumns, which isn't enough; n8n refused to activate the
workflow ("Missing or invalid required parameters: filters"). Added
filters.conditions matching on ticket_id to both memory-writing Data
Table nodes.
3. Real architecture bug: "Data Table - Buscar Memoria" sat inline in the
41-candidate chain (Mesclar Contexto -> Buscar Memoria -> Decidir
Necessidade de IA). A bulk "get all rows" Data Table node run with 41
input items produced 41 identical fetches and its OWN output (the empty
memory table on a first run) replaced the 41 candidates entirely --
"Decidir Necessidade de IA" received 0 items and the whole main branch
silently skipped. Moved the memory fetch to a parallel branch off
Config (fires once) and added a real Merge node ("Merge - Aguardar
Memoria") to guarantee both the candidates and the memory rows have
executed before "Decidir Necessidade de IA" runs -- same synchronization
pattern already used for context and for the shadow comparison.
Also fixed a cosmetic bug found in the real message output: buildTopMessage
produced a quadruple newline between the header and the first ticket block
([header, '', ...blocks].join('\n\n') double-counts the blank line already
inserted by the '\n\n' join). Added a regression test.
Verified with a real execution (id 13599, 24/08/2026 19:25 UTC) against the
live GLPI database: 41 real candidates, 30 sent to OpenAI (capped), 11
fallback-capped, memory table populated with 41 real rows including
genuine WAITING_VENDOR/WAITING_REQUESTER classifications with real
blocker_type/waiting_for fields extracted from real ticket task content,
and a real shadow_runs row comparing Legacy x V2 (4 tickets removed by V2:
2 for INSUFFICIENT_CONTEXT, 1 WAITING_REQUESTER, 1 WAITING_VENDOR; 4 added).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Fixed 3 remaining "return [...].join(...)" false-positive triggers in
logic/message.js (n8n-mcp's static validator flags any inner return-array
pattern as if it were the node's own output shape) by extracting each to
a named variable before joining. All 3 occurrences of the pattern lived
inside buildTopMessage/buildCoordinationDigest, which get fully inlined
into both "Code - Montar Mensagem Curta" and "Code - Montar Digest
Coordenacao" (whichever function is actually called, the whole module is
inlined), so both nodes needed the same fix.
- Deployed [DEV] Coordenador GLPI - Chamados Abertos v2 (Shadow)
(id y6JFEnp7Z2IJEUe2) to devn8n.gtigrupo.com.br: 36 nodes, inactive,
n8n_validate_workflow reports valid:true (0 errors, 1 minor deprecation
warning on continueOnFail).
- Ran n8n_autofix_workflow: 6 safe typeVersion upgrades applied and mirrored
back into n8n/workflow.template.json so future rebuilds stay in sync.
- Found and fixed a real safety issue: creating the workflow before the
"MySQL GLPI - Coordenador" credential existed caused n8n to silently
auto-bind the 3 MySQL nodes to the only other mySql-type credential in
the instance (an unrelated Asaas billing credential). Cleared those
credential bindings (both live and in the template) rather than leave
them pointing at the wrong database.
- Created the two Data Tables the pipeline needs: coordenador_glpi_memoria
(17 columns) and coordenador_glpi_shadow_runs (6 columns).
- Documented the pending schema introspection (glpi_tickettasks.state
column name unverified, blocked on the MySQL credential) and an unrelated
discovery (two other Data Tables, dev_glpi_v2_insights_memory and
dev_glpi_v2_run_history, appeared on the DEV instance mid-session from a
source other than this one) in docs/superpowers/notes/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>