From 3887547f1c9a4389d983b8d2319ab4182c5462be Mon Sep 17 00:00:00 2001 From: Emanuel Almeida Date: Thu, 23 Apr 2026 03:45:51 +0100 Subject: [PATCH] =?UTF-8?q?feat(observabilidade):=20padr=C3=B5es=20persist?= =?UTF-8?q?entes=20prop=C3=B5em=20staging=20no=20CARL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quando um padrão atinge ≥3 semanas consecutivas com severity warning/action, além de abrir ticket no Desk, propõe também como staging entry no /media/ealmeida/Dados/.carl/carl.json para revisão e eventual promoção a regra. Idempotente por pattern_key. Dry-run log-only. Fecha o feedback loop Observabilidade → CARL identificado na análise do sistema: padrões detectados empiricamente viram propostas de regras. --- api/scripts/sessions-patterns.ts | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/api/scripts/sessions-patterns.ts b/api/scripts/sessions-patterns.ts index 6f34cf9..237dae2 100644 --- a/api/scripts/sessions-patterns.ts +++ b/api/scripts/sessions-patterns.ts @@ -24,6 +24,7 @@ * MCP_GATEWAY_TOKEN Bearer token do gateway MCP * MCP_GATEWAY_URL URL do MCP desk-crm (default https://gateway.descomplicar.pt/v1/desk-crm/mcp) */ +import { readFileSync, writeFileSync, existsSync } from 'node:fs' import { openSessionsDb, type PatternRecord, type SessionsDb } from '../services/sessions/db.js' import { DEFAULT_DB_PATH } from '../services/sessions/indexer.js' import { @@ -34,6 +35,42 @@ import { type Pattern, } from '../services/sessions/patterns.js' +const CARL_JSON_PATH = '/media/ealmeida/Dados/.carl/carl.json' + +/** + * Propor padrão persistente como staging entry no carl.json. + * Idempotente: não duplica por pattern_key. + * Só dispara para severity action ou warning com ≥3 semanas consecutivas. + */ +function proposeCarlStagingEntry(p: PatternRecord): void { + if (!existsSync(CARL_JSON_PATH)) return + try { + const raw = readFileSync(CARL_JSON_PATH, 'utf-8') + const carl = JSON.parse(raw) as { staging?: unknown[] } + const staging = (carl.staging ??= []) + const stagingId = `pattern-${p.pattern_key}` + const exists = staging.some((e) => typeof e === 'object' && e !== null && (e as { id?: string }).id === stagingId) + if (exists) return + staging.push({ + id: stagingId, + type: 'pattern-proposal', + source: 'observabilidade-patterns', + name: `Padrão recorrente: ${p.title}`, + description: p.description, + severity: p.severity, + consecutive_weeks: p.consecutive_weeks, + affected_count: p.affected_count, + sample_session_ids: p.sample_session_ids, + proposed_at: new Date().toISOString(), + status: 'pending-review', + }) + writeFileSync(CARL_JSON_PATH, JSON.stringify(carl, null, 2), 'utf-8') + console.error(`[patterns] proposto em CARL staging: ${stagingId}`) + } catch (err) { + console.error(`[patterns] falha ao propor em CARL staging: ${(err as Error).message}`) + } +} + interface Args { week?: string publish: boolean @@ -344,6 +381,8 @@ async function main(): Promise { } catch (e) { console.error(`[patterns] falha ao criar ticket para ${rec.pattern_key}:`, (e as Error).message) } + // Propor como staging no CARL — idempotente por pattern_key + proposeCarlStagingEntry(rec) } } } catch (e) { @@ -360,6 +399,7 @@ async function main(): Promise { for (const rec of records) { if (rec.consecutive_weeks >= 3 && (rec.severity === 'warning' || rec.severity === 'action')) { console.error(`[patterns] (dry-run) Ticket seria criado: ${rec.title} — ${rec.consecutive_weeks} sem.`) + console.error(`[patterns] (dry-run) CARL staging seria proposto: pattern-${rec.pattern_key}`) } } }