feat(monitor): integração Beszel/Stack Observer, tickets do observer e novos sites no uptime

This commit is contained in:
2026-10-28 16:00:59 +00:00
parent 2bbc12e8b4
commit ca9a07b1d9
12 changed files with 1110 additions and 7 deletions
+211
View File
@@ -0,0 +1,211 @@
/**
* Stack Observer Service
* Reads divergences from observer.db (SQLite)
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import sqlite3 from 'sqlite3'
import { open } from 'sqlite'
import path from 'path'
import { fileURLToPath } from 'url'
import os from 'os'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
interface Evaluation {
id: number
source: string
entity_id: string
site_sig: string
result_real: string
result_nominal: string
tokens: number
reincidence_count: number
judge_method: string
confidence: number
evaluated_at: number
category: string
}
interface DivergenceStats {
total_divergences: number
by_category: Record<string, number>
by_judge: Record<string, number>
recent_24h: number
high_confidence: number
}
/**
* Open observer.db connection
* DB is located at ~/Dados/Dev/stack-observer/observer.db
*/
async function openObserverDb() {
const dbPath = path.join(
os.homedir(),
'Dados/Dev/stack-observer/observer.db'
)
return open({
filename: dbPath,
driver: sqlite3.Database
})
}
/**
* Get all divergences (evaluations)
* Filters for result_real != result_nominal
*/
export async function getDivergences() {
const db = await openObserverDb()
try {
// Get divergences with divergence flag
const divergences = await db.all<Evaluation[]>(`
SELECT
id, source, entity_id, site_sig,
result_real, result_nominal,
tokens, reincidence_count,
judge_method, confidence, evaluated_at, category
FROM evaluations
WHERE result_real IS NOT NULL
AND category IN ('automation-liveness', 'automation-failure')
ORDER BY evaluated_at DESC
LIMIT 100
`)
// Calculate stats
const now = Math.floor(Date.now() / 1000)
const day_ago = now - 86400
const stats: DivergenceStats = {
total_divergences: 0,
by_category: {},
by_judge: {},
recent_24h: 0,
high_confidence: 0
}
if (divergences) {
stats.total_divergences = divergences.length
for (const div of divergences) {
// Count by category
stats.by_category[div.category] = (stats.by_category[div.category] ?? 0) + 1
// Count by judge method
stats.by_judge[div.judge_method] = (stats.by_judge[div.judge_method] ?? 0) + 1
// Count recent
if (div.evaluated_at >= day_ago) {
stats.recent_24h++
}
// Count high confidence
if (div.confidence >= 0.9) {
stats.high_confidence++
}
}
}
return {
divergences: divergences ?? [],
stats,
generated_at: new Date().toISOString()
}
} finally {
await db.close()
}
}
/**
* Get single divergence by ID
*/
export async function getDivergenceById(id: string) {
const db = await openObserverDb()
try {
const evaluation = await db.get<Evaluation>(`
SELECT * FROM evaluations
WHERE id = ?
`, [id])
return evaluation ?? null
} finally {
await db.close()
}
}
/**
* Get divergence trend (last 7 days)
*/
export async function getDivergenceTrend() {
const db = await openObserverDb()
try {
interface TrendRow {
date: string
count: number
}
const trend = await db.all<TrendRow[]>(`
SELECT
DATE(evaluated_at, 'unixepoch') as date,
COUNT(*) as count
FROM evaluations
WHERE result_real IS NOT NULL
AND category IN ('automation-liveness', 'automation-failure')
AND evaluated_at >= (unixepoch() - 604800)
GROUP BY date
ORDER BY date DESC
`)
return trend ?? []
} finally {
await db.close()
}
}
/**
* Get list of automations (hermes_jobs)
* F1b: Colector lê de ~/.hermes/data/action-log.db
*/
export async function getAutomations() {
const db = await openObserverDb()
try {
const automations = await db.all(`
SELECT
action_id, who, what, where_field, when_ts,
result, result_real, why, how, detail, session_id, created_at,
ingested_at
FROM hermes_jobs
ORDER BY created_at DESC
LIMIT 100
`)
return automations ?? []
} finally {
await db.close()
}
}
/**
* Get list of gate events
* F1a: Colector lê de /tmp/stack-observer/gate-events.queue
*/
export async function getGateEvents() {
const db = await openObserverDb()
try {
const events = await db.all(`
SELECT
id, gate_id, event_type, payload, timestamp, ingested_at
FROM gate_events
ORDER BY timestamp DESC
LIMIT 100
`)
return events ?? []
} finally {
await db.close()
}
}