Compare commits

...

1 Commits

Author SHA1 Message Date
ealmeida 3887547f1c feat(observabilidade): padrões persistentes propõem staging no CARL
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.
2026-04-23 03:45:51 +01:00
+40
View File
@@ -24,6 +24,7 @@
* MCP_GATEWAY_TOKEN Bearer token do gateway MCP * 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) * 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 { openSessionsDb, type PatternRecord, type SessionsDb } from '../services/sessions/db.js'
import { DEFAULT_DB_PATH } from '../services/sessions/indexer.js' import { DEFAULT_DB_PATH } from '../services/sessions/indexer.js'
import { import {
@@ -34,6 +35,42 @@ import {
type Pattern, type Pattern,
} from '../services/sessions/patterns.js' } 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 { interface Args {
week?: string week?: string
publish: boolean publish: boolean
@@ -344,6 +381,8 @@ async function main(): Promise<void> {
} catch (e) { } catch (e) {
console.error(`[patterns] falha ao criar ticket para ${rec.pattern_key}:`, (e as Error).message) 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) { } catch (e) {
@@ -360,6 +399,7 @@ async function main(): Promise<void> {
for (const rec of records) { for (const rec of records) {
if (rec.consecutive_weeks >= 3 && (rec.severity === 'warning' || rec.severity === 'action')) { 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) Ticket seria criado: ${rec.title}${rec.consecutive_weeks} sem.`)
console.error(`[patterns] (dry-run) CARL staging seria proposto: pattern-${rec.pattern_key}`)
} }
} }
} }