feat: add control panel

This commit is contained in:
thanhnv
2026-07-08 19:07:35 +09:00
parent a07b15e489
commit 3be9970c15
104 changed files with 3639 additions and 461 deletions
@@ -1,8 +1,10 @@
// Read-only aggregations over CASAN harness telemetry. Formulas mirror
// packages/casan-harness/tests/generate-agentops-dashboard.py; all numbers come from real
// on-disk feeds. Missing feeds degrade to zero/empty + a `stale` flag — never fabricated.
import { existsSync, readFileSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { Injectable } from '@nestjs/common';
import { PATHS, isStale, metricsAgeSeconds, STALE_AFTER_S } from '../common/app-root.js';
import { APP_ROOT, PATHS, isStale, metricsAgeSeconds, STALE_AFTER_S } from '../common/app-root.js';
import { readJsonl, readJson, readHead, readTrace } from './telemetry.reader.js';
type Row = Record<string, any>;
@@ -10,6 +12,46 @@ const num = (v: any) => (typeof v === 'number' && isFinite(v) ? v : 0);
const sum = (rows: Row[], k: string) => rows.reduce((a, r) => a + num(r[k]), 0);
const count = (rows: Row[], pred: (r: Row) => boolean) => rows.reduce((a, r) => a + (pred(r) ? 1 : 0), 0);
const recent = (rows: Row[], n: number) => rows.slice(-n).reverse();
const arr = (v: any): any[] => (Array.isArray(v) ? v : []);
const pct = (part: number, total: number) => (total > 0 ? Math.round((part / total) * 1000) / 10 : 0);
function artifact(path: string, source: string, verifiedWhenPresent = true) {
const present = existsSync(path);
const runAt = present ? statSync(path).mtime.toISOString() : null;
return {
source,
artifact_path: relative(APP_ROOT, path) || '.',
commit: gitCommit(),
run_at: runAt,
verified: present && verifiedWhenPresent,
status: present ? (verifiedWhenPresent ? 'verified' : 'present_unverified') : 'missing',
};
}
function gitCommit(): string | null {
try {
const head = readFileSync(join(APP_ROOT, '.git', 'HEAD'), 'utf8').trim();
if (/^[0-9a-f]{40}$/i.test(head)) return head;
const ref = head.match(/^ref:\s+(.+)$/)?.[1];
if (!ref) return null;
const value = readFileSync(join(APP_ROOT, '.git', ref), 'utf8').trim();
return /^[0-9a-f]{40}$/i.test(value) ? value : null;
} catch {
return null;
}
}
function widget(
id: string,
title: string,
status: string,
summary: string,
metrics: Record<string, any>,
envelope: ReturnType<typeof artifact>,
evidence: Record<string, any> = {},
) {
return { id, title, status, summary, metrics, envelope, evidence };
}
@Injectable()
export class TelemetryService {
@@ -134,4 +176,163 @@ export class TelemetryService {
business_kpi: readJson(PATHS.businessKpi),
};
}
commandCenter() {
const metrics = readJsonl(PATHS.metrics);
const provider = readJsonl(PATHS.providerUsage);
const audit = readJsonl(PATHS.audit);
const security = readJsonl(PATHS.security);
const incidents = readJsonl(PATHS.incidents);
const actionGate = readJsonl(PATHS.actionGate);
const traceability = readJson(PATHS.traceability) as any;
const kpi = readJson(PATHS.businessKpi) as any;
const drift = readJson(PATHS.drift) as any;
const approvalStore = (readJson(PATHS.approvalInbox) as any) ?? { proposals: [], oversight: [] };
const proposals = arr(approvalStore.proposals);
const oversight = arr(approvalStore.oversight);
const head = readHead(PATHS.auditHead);
const passedReq = num(traceability?.summary?.passed);
const failedReq = num(traceability?.summary?.failed);
const totalReq = num(traceability?.summary?.requirements) || passedReq + failedReq;
const blockedSecurity = count(security, (s) => ['blocked', 'BLOCK', 'DENY'].includes(String(s.status ?? s.decision)));
const pendingApprovals = count(proposals, (p) => p.status === 'pending');
const approvedApprovals = count(proposals, (p) => p.status === 'approved' || p.status === 'auto_allowed');
const killSwitchScopes = [...new Set(incidents.filter((i) => i.action === 'kill_switch_engaged').map((i) => String(i.scope ?? 'unknown')))];
const kpis = arr(kpi?.kpis);
const kpisMet = count(kpis, (k) => k.target_met === true);
const auditVerified = audit.length > 0 && Boolean(head);
const selfImproveRelated = proposals.filter((p) => String(p.action ?? p.type ?? '').includes('improve'));
const widgets = {
maturity: widget(
'maturity',
'Maturity gauge + H1-H7 radar',
metrics.length || audit.length || security.length ? 'ok' : 'no_data',
`${metrics.length} runs, ${audit.length} governance records, ${security.length} security verdicts`,
{
runs: metrics.length,
governance_records: audit.length,
security_verdicts: security.length,
action_blocks: count(actionGate, (a) => a.outcome === 'BLOCK'),
drift_report: drift ? 'present' : 'missing',
},
artifact(PATHS.scoringReport, 'phase3-real-run-scoring.md'),
{ harness_signals: this.overview().harness_signals },
),
hitl: widget(
'hitl',
'Human-in-the-loop panel',
pendingApprovals > 0 ? 'warn' : (proposals.length > 0 ? 'ok' : 'no_data'),
`${pendingApprovals} pending, ${approvedApprovals} approved/auto-allowed`,
{
pending: pendingApprovals,
approved_or_auto: approvedApprovals,
rejected: count(proposals, (p) => p.status === 'rejected'),
oversight_events: oversight.length,
},
artifact(PATHS.approvalInbox, 'approval-inbox.json', oversight.length === 0 || Boolean(oversight[oversight.length - 1]?.hash)),
{ recent_oversight: recent(oversight, 5) },
),
kill_switch: widget(
'kill_switch',
'Kill-switch and guardrails',
killSwitchScopes.length > 0 ? 'fail' : 'ok',
killSwitchScopes.length ? `${killSwitchScopes.length} engaged scope(s)` : 'No active kill-switch incident in telemetry',
{
engaged_scopes: killSwitchScopes,
incidents: incidents.length,
critical: count(incidents, (i) => i.severity === 'CRIT'),
},
artifact(PATHS.incidents, 'incidents.jsonl'),
{ recent_incidents: recent(incidents, 5) },
),
traceability: widget(
'traceability',
'Traceability Sankey',
failedReq > 0 ? 'fail' : (totalReq > 0 ? 'ok' : 'no_data'),
totalReq > 0 ? `${passedReq}/${totalReq} requirements pass` : 'No traceability artifact found',
{
requirements: totalReq,
passed: passedReq,
failed: failedReq,
coverage_pct: pct(passedReq, totalReq),
},
artifact(PATHS.traceability, 'traceability-matrix.json'),
{ summary: traceability?.summary ?? null },
),
security: widget(
'security',
'Security posture',
blockedSecurity > 0 ? 'ok' : (security.length > 0 ? 'warn' : 'no_data'),
`${blockedSecurity}/${security.length} security verdicts blocked`,
{
verdicts: security.length,
blocked: blockedSecurity,
block_rate_pct: pct(blockedSecurity, security.length),
},
artifact(PATHS.security, 'security.jsonl'),
{ recent_security: recent(security, 5) },
),
finops: widget(
'finops',
'Token economy / FinOps',
provider.length > 0 || kpis.length > 0 ? 'ok' : 'no_data',
`$${sum(provider, 'cost_usd').toFixed(4)} provider cost, ${sum(provider, 'total_tokens')} tokens`,
{
provider_cost: sum(provider, 'cost_usd'),
provider_tokens: sum(provider, 'total_tokens'),
kpis: kpis.length,
kpis_met: kpisMet,
},
artifact(PATHS.providerUsage, 'provider-usage.jsonl'),
{ business_kpi: { status: kpi?.status ?? null, kpis_met: kpisMet, kpis: kpis.length } },
),
certification: widget(
'certification',
'Certified-run seal',
auditVerified ? 'ok' : 'warn',
auditVerified ? `Audit head present with ${audit.length} record(s)` : 'Audit chain head missing or empty',
{
audit_records: audit.length,
audit_head_present: Boolean(head),
last_decision: audit.length ? audit[audit.length - 1].decision ?? null : null,
},
artifact(PATHS.auditHead, 'audit-head.txt', auditVerified),
{ audit_head: head, recent_audit: recent(audit, 5) },
),
self_improve: widget(
'self_improve',
'Self-improve pipeline',
selfImproveRelated.length > 0 ? 'warn' : (existsSync(PATHS.selfImprove) ? 'ok' : 'no_data'),
selfImproveRelated.length ? `${selfImproveRelated.length} self-improve proposal(s) in inbox` : 'Core self-improve primitive is present; no proposal artifact queued',
{
inbox_related: selfImproveRelated.length,
primitive_present: existsSync(PATHS.selfImprove),
},
artifact(PATHS.selfImprove, 'self-improve.py'),
{ proposals: recent(selfImproveRelated, 5) },
),
};
const ticker = [
...recent(oversight, 10).map((e) => ({ at: e.at ?? e.created_at ?? null, kind: 'oversight', text: `${e.event ?? 'oversight'} ${e.proposal_id ?? ''}`.trim(), status: e.status ?? 'event' })),
...recent(incidents, 10).map((e) => ({ at: e.timestamp ?? null, kind: 'incident', text: `${e.event ?? 'incident'} ${e.scope ?? ''}`.trim(), status: e.severity ?? 'event' })),
...recent(audit, 10).map((e) => ({ at: e.timestamp ?? null, kind: 'audit', text: `${e.action ?? 'action'} ${e.decision ?? ''}`.trim(), status: e.decision ?? 'event' })),
].sort((a, b) => String(b.at ?? '').localeCompare(String(a.at ?? ''))).slice(0, 20);
return {
...this.freshness(),
generated_at: new Date().toISOString(),
widgets,
briefing: [
{ label: 'Maturity', value: widgets.maturity.summary, status: widgets.maturity.status, widget: 'maturity' },
{ label: 'Control', value: widgets.hitl.summary, status: widgets.hitl.status, widget: 'hitl' },
{ label: 'Safety', value: widgets.security.summary, status: widgets.security.status, widget: 'security' },
{ label: 'Economy', value: widgets.finops.summary, status: widgets.finops.status, widget: 'finops' },
{ label: 'Certification', value: widgets.certification.summary, status: widgets.certification.status, widget: 'certification' },
],
ticker,
};
}
}