Commit Graph
29 Commits
Author SHA1 Message Date
GustavoandClaude Sonnet 5 482b7ef48e fix(v2): redefine AI actionability semantics + version the classifier (AI_CLASSIFIER_VERSION)
Production-readiness cycle. Classifier was excessively conservative (real
Run A: ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE both 0) and made concrete
semantic mistakes on real tickets:
- #34419: technician "Rafael Brito" (internal) classified as WAITING_VENDOR
  waiting_for -- an internal technician is not a vendor.
- #37389: ticket had concrete open tasks ("Realizar um inventário das
  máquinas", "Alinhar o dia...") but was classified INSUFFICIENT_CONTEXT.
- Generic next_action ("Revisar chamado") repeated across unrelated
  tickets, proving nothing about real actionability.

Rewrote the system prompt (n8n/nodes/prepare-ai-prompt.node.js) with:
practical actionability definition ("suporte consegue fazer algo util
agora", explicit inclusion list); contatar-vs-aguardar distinction with
examples; WAITING_* requires positive evidence (action already taken AND
external dependency still open); technician-vs-vendor distinction citing
the TECNICO context field; STATUS Planejado alone != blocked/scheduled;
SCHEDULED requires an explicit date/window; mandatory chronological
interpretation (most recent apontamento wins over a superseded old
blocker); next_action must be concrete, with explicit accepted/rejected
examples; INSUFFICIENT_CONTEXT bar raised (ask "is there a diagnostic
step available even without full info" first).
n8n/nodes/prepare-ai-prompt.node.test.js: 10 new tests asserting each
rule's text is still present in the prompt (deterministic proxy --
behavioral proof comes from the real DEV run, inspected manually).

logic/classification.js: added AI_CLASSIFIER_VERSION=3 (stamped on every
valid classification); added a deterministic safety net (not just a
prompt hope) -- isWaitingForInternalTechnician() rejects WAITING_VENDOR
when waiting_for matches the ticket's own TECNICO field, routing to a new
fallback_reason 'ai_semantic_conflict' instead of accepting a known-wrong
semantic classification; added next_action genericness rejection
(isGenericNextAction) for ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE only.
n8n/nodes/validate-ai-response.node.js: now passes the ticket into
validateAiClassification() so the technician guard has the facts it needs.

logic/cache.js: added buildCacheKey(contextHash, classifierVersion) ->
"<hash>:v<version>", stored in the same last_context_hash column (no
schema migration) -- bumping AI_CLASSIFIER_VERSION now forces a cache
miss on every ticket without manually clearing the Data Table.
n8n/nodes/decide-needs-ai.node.js / prepare-memoria-analise-record.node.js
updated to compute/persist the composite key.

logic/config.js: AI_MAX_CANDIDATES default raised 30->60 (now equals
CANDIDATE_POOL_SIZE) -- no candidate should go unanalyzed by an
artificial cap now that Run B already proved the cache makes repeat runs
cheap; AI_FALLBACK stays reserved for real provider failures. Also added
SEND_ENABLED (default false) and ENVIRONMENT config fields for the real
Slack send gate (used by logic/slack.js, next commit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 20:49:54 -03:00
GustavoandClaude Sonnet 5 4534099b50 docs: add corrective-cycle final report (Run A/Run B, real execution data)
Live DEV sync completed (all 11 changed nodes pushed, including the
transitive-dependency fix), then two real executions against production
GLPI in shadow mode:

- Run A (execution 14259): 45 candidates, 30 real AI calls (0 errors),
  15 correctly routed to AI_FALLBACK instead of fake ACTIONABLE_NOW,
  3 critical overrides. ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE still 0 --
  reported honestly, not forced.
- Run B (execution 14263, ~1 min later, no data changes): 0 AI calls,
  45/45 cache hits, cached AI_FALLBACK items correctly stayed fallback
  (proves the cache is_fallback bug fix), Top10 correctly shrank
  10->5 as WAITING_VENDOR/WAITING_REQUESTER items hit cooldown.

Full reincident-ticket table (7 tickets) with real evidence, real
before/after HTML-normalization examples (tickets #37389, #36530),
shadow diff vs legacy, webhook-debug-removed confirmation, production-
untouched confirmation. Verdict: CONTINUAR SHADOW MODE.

Workflow y6JFEnp7Z2IJEUe2 ends this cycle at active:false, 33 nodes,
temp webhook removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 20:09:52 -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 485234dc88 feat(v2): add data-quality audit queue for INSUFFICIENT_CONTEXT, require non-empty next_action
buildDataQualityReview extracts an auditable record per INSUFFICIENT_CONTEXT
ticket (ticket_id, title, ai_reason, context_size, has_description,
followup_count, task_count, has_solution, last_interaction_at) -- not sent
anywhere, just for calibration. Also tightened validateAiClassification to
reject an empty/whitespace-only next_action (was previously accepted).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:52:22 -03:00
GustavoandClaude Sonnet 5 34f61abc3b fix(v2): centralize AI_FALLBACK/INSUFFICIENT_CONTEXT eligibility policy in cooldown.js
applyFallbackPolicy (new, in logic/cooldown.js alongside the isCriticalOverride
it reuses) is now the single place that decides whether an AI_FALLBACK or
INSUFFICIENT_CONTEXT ticket can enter the Top10: blocked by default, let
through only via an explicit critical override (SLA vencido + prioridade/
urgencia alta, OR sem_tecnico + prioridade/urgencia alta -- isCriticalOverride
extended to cover both, previously only covered SLA+priority). score.js
calls this policy and sets excludedFromTop10/exclusionReason/decisionReason
accordingly, with an auditable *_critical_override or *_no_override reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:51:17 -03:00
GustavoandClaude Sonnet 5 00a42db269 fix(v2): AI_FALLBACK replaces the ACTIONABLE_NOW-with-confidence-0 lie, normalize null-like strings
Fallback (ai_disabled, ai_error, invalid_json, ai_max_candidates_cap --
any candidate the AI never actually analyzed) now produces classification
'AI_FALLBACK', an explicit technical state that is never part of the
11-class enum sent to OpenAI (the AI itself can never legitimately return
it). Previously it silently claimed ACTIONABLE_NOW with confidence 0,
which made unanalyzed tickets indistinguishable from genuinely confirmed
ones at the classification-label level.

Also normalizes blocker_type/waiting_for when the model returns the
literal string "null"/"none"/"n/a"/"" instead of JSON null -- done
once, in the validation layer (logic/classification.js), not scattered
around the workflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:49:22 -03:00
GustavoandClaude Sonnet 5 db7a261a10 feat(v2): normalize GLPI text before context/hash, add real ticket description
context.normalizeTicket now runs normalizeGlpiText on titulo, descricao
(new field, sourced from glpi_tickets.content -- never fetched before),
and every followup/task texto. buildAiContextText restructured into
delimited sections (TITULO / DESCRICAO ORIGINAL / STATUS-TECNICO-GRUPO /
FOLLOW-UPS / TAREFAS) instead of one flat string.

hash.buildContextFingerprint now includes titulo/descricao and the
normalized followup/task texto (previously only id/date/status), so:
(a) the same content encoded differently (HTML vs escaped entities)
produces the same hash, and (b) editing a followup's actual content
(same id/date) is now detected as a real context change -- previously
silently invisible to the hash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:47:32 -03:00
GustavoandClaude Sonnet 5 d7650308a9 feat(v2): add robust GLPI text normalization (HTML/entities, multi-level escaping)
Handles: numeric entities (&#60; &#62;), named entities (&amp; &nbsp; etc),
HTML tags, multi-level escaping (up to 5 decode iterations, capped to
avoid infinite loop), whitespace/newline collapsing. Preserves dates,
names, numbers, error codes, hostnames, versions -- only strips markup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:45:36 -03:00
GustavoandClaude Sonnet 5 6bbdaa5fe8 docs: add final mission report (real execution data, no fabricated numbers)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:33:04 -03:00
GustavoandClaude Sonnet 5 2881fb52de docs: update schema introspection note with confirmed findings from real run
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 16:31:22 -03:00
GustavoandClaude Sonnet 5 ea105d525c fix(v2): three real bugs found by an actual end-to-end run against production GLPI data
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>
2026-08-24 16:30:13 -03:00
GustavoandClaude Sonnet 5 af48ea69d8 feat(v2): create DEV data tables, deploy V2 workflow (inactive), record schema introspection
- 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>
2026-08-24 15:47: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
GustavoandClaude Sonnet 5 00bb89f4f4 feat(v2): candidate pool SQL (admin exclusion preserved, no hard LIMIT 10) + verbatim legacy SQL for shadow mode
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:19:39 -03:00
GustavoandClaude Sonnet 5 60015c0972 feat(v2): add short message and coordination digest builders
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:18:15 -03:00
GustavoandClaude Sonnet 5 d3917707e0 feat(v2): add Top10 selection with soft group diversity, hard 10-item cap
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:17:37 -03:00
GustavoandClaude Sonnet 5 749be7dbae feat(v2): add auditable final score combining actionability, baseline and cooldown
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:17:06 -03:00
GustavoandClaude Sonnet 5 014c48d578 feat(v2): add state-based cooldown with critical override
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:16:25 -03:00
GustavoandClaude Sonnet 5 efba7792fa feat(v2): add safe AI fallback and cache/reanalysis decision
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:15:50 -03:00
GustavoandClaude Sonnet 5 c445e00fc5 feat(v2): add strict schema validation for AI classification JSON
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:15:21 -03:00
GustavoandClaude Sonnet 5 7dccb85efa feat(v2): add ticket normalization and bounded AI context builder
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:14:50 -03:00
GustavoandClaude Sonnet 5 30e532f811 feat(v2): add context fingerprint hash that ignores time-only fields
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:14:17 -03:00
GustavoandClaude Sonnet 5 32ebe0d4f2 feat(v2): add parseConfig with clamped TOP_N and safe defaults
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:13:48 -03:00
GustavoandClaude Sonnet 5 ff5620c95c chore: scaffold node:test runner for V2 deterministic logic
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:13:19 -03:00
GustavoandClaude Sonnet 5 49f487d636 docs: add V2 implementation plan (17 tasks, TDD for deterministic logic)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:12:08 -03:00
GustavoandClaude Sonnet 5 de147107d5 docs: add V2 design spec (approved)
docs/superpowers/specs/2026-08-24-coordenador-glpi-v2-design.md
Covers Etapa 0 recovery findings, architecture, data model, AI schema,
scoring, message format, shadow mode. Approved by user in chat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:00:34 -03:00
GustavoandClaude Sonnet 5 1eb1f80b4f chore: baseline import of existing Coordenador GLPI V1 project
Snapshot of the pre-existing, working V1 automation (n8n workflow +
SQL query + docker-compose + README) exactly as found on disk, before
any V2 changes. .env is intentionally not tracked (gitignored,
contains local secrets).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:00:26 -03:00