feat: add production assurance dashboard flow

This commit is contained in:
thanhnv
2026-07-28 21:49:15 +07:00
parent ee5d1f7af5
commit cce3cbfd42
39 changed files with 1873 additions and 91 deletions
@@ -10,9 +10,10 @@ import { GoalsModule } from './goals/goals.module.js';
import { EvidenceModule } from './evidence/evidence.module.js';
import { SessionController } from './session/session.controller.js';
import { ReportsModule } from './reports/reports.module.js';
import { IngestModule } from './ingest/ingest.module.js';
@Module({
imports: [TelemetryModule, SettingsModule, KillSwitchModule, ApprovalsModule, ChatModule, ProviderAuthModule, GoalsModule, EvidenceModule, ReportsModule],
imports: [TelemetryModule, SettingsModule, KillSwitchModule, ApprovalsModule, ChatModule, ProviderAuthModule, GoalsModule, EvidenceModule, ReportsModule, IngestModule],
controllers: [HealthController, SessionController],
})
export class AppModule {}
@@ -0,0 +1,17 @@
import { Body, Controller, Headers, Inject, Post } from '@nestjs/common';
import { ok } from '../common/api-response.js';
import { IngestService } from './ingest.service.js';
@Controller('api/v1/ingest')
export class IngestController {
constructor(@Inject(IngestService) private readonly ingestService: IngestService) {}
@Post('turn')
turn(
@Body() body: unknown,
@Headers('x-casan-timestamp') timestamp?: string,
@Headers('x-casan-signature') signature?: string,
) {
return ok(this.ingestService.ingest(body, timestamp, signature));
}
}
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { IngestController } from './ingest.controller.js';
import { IngestService } from './ingest.service.js';
@Module({
controllers: [IngestController],
providers: [IngestService],
})
export class IngestModule {}
@@ -0,0 +1,119 @@
import { createHmac, timingSafeEqual } from 'node:crypto';
import { appendFileSync, existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { BadRequestException, ForbiddenException, Injectable, ServiceUnavailableException } from '@nestjs/common';
import { APP_ROOT, PATHS } from '../common/app-root.js';
type Row = Record<string, unknown>;
export interface IngestEnvelope {
schema_version: 1;
sent_at: string;
project_id: string;
trace_id: string;
receipt: Row;
metric: Row;
trace: Row;
events: Row[];
}
const SAFE_ID = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
const FORBIDDEN_KEYS = new Set([
'prompt', 'raw_prompt', 'assistant_summary', 'tool_input', 'tool_output',
'result_content', 'secret', 'password', 'authorization',
]);
export function stableJson(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
if (value && typeof value === 'object') {
const record = value as Row;
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(',')}}`;
}
return JSON.stringify(value) ?? 'null';
}
export function expectedSignature(token: string, timestamp: string, body: unknown): string {
return `sha256=${createHmac('sha256', token).update(`${timestamp}.${stableJson(body)}`).digest('hex')}`;
}
function containsForbiddenKey(value: unknown): boolean {
if (Array.isArray(value)) return value.some(containsForbiddenKey);
if (!value || typeof value !== 'object') return false;
return Object.entries(value as Row).some(([key, nested]) => (
FORBIDDEN_KEYS.has(key.toLowerCase()) || containsForbiddenKey(nested)
));
}
function safeEqual(left: string, right: string): boolean {
const leftBuffer = Buffer.from(left);
const rightBuffer = Buffer.from(right);
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
}
function atomicJson(path: string, value: unknown): void {
mkdirSync(dirname(path), { recursive: true });
const temporary = `${path}.${process.pid}.tmp`;
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
renameSync(temporary, path);
}
@Injectable()
export class IngestService {
ingest(raw: unknown, timestamp: string | undefined, signature: string | undefined) {
const token = process.env.CASAN_CP_INGEST_TOKEN;
if (!token) throw new ServiceUnavailableException('CASAN_INGEST_DISABLED');
const epoch = Number(timestamp);
if (!Number.isInteger(epoch) || Math.abs(Math.floor(Date.now() / 1000) - epoch) > 300) {
throw new ForbiddenException('CASAN_INGEST_TIMESTAMP_INVALID');
}
const expected = expectedSignature(token, String(timestamp), raw);
if (!signature || !safeEqual(signature, expected)) {
throw new ForbiddenException('CASAN_INGEST_SIGNATURE_INVALID');
}
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
throw new BadRequestException('CASAN_INGEST_INVALID_ENVELOPE');
}
const envelope = raw as Partial<IngestEnvelope>;
if (
envelope.schema_version !== 1
|| typeof envelope.project_id !== 'string'
|| typeof envelope.trace_id !== 'string'
|| !SAFE_ID.test(envelope.project_id)
|| !SAFE_ID.test(envelope.trace_id)
|| !envelope.metric
|| !envelope.trace
|| !Array.isArray(envelope.events)
|| envelope.events.length > 2000
) throw new BadRequestException('CASAN_INGEST_INVALID_ENVELOPE');
if (containsForbiddenKey(envelope)) {
throw new BadRequestException('CASAN_INGEST_RAW_CONTENT_FORBIDDEN');
}
if (
envelope.metric.trace_id !== envelope.trace_id
|| envelope.trace.trace_id !== envelope.trace_id
|| envelope.events.some((event) => event.trace_id !== envelope.trace_id)
) throw new BadRequestException('CASAN_INGEST_TRACE_MISMATCH');
const tracePath = join(PATHS.traceDir, `agentic-${envelope.trace_id}.json`);
if (existsSync(tracePath)) {
return { accepted: true, duplicate: true, trace_id: envelope.trace_id };
}
mkdirSync(dirname(PATHS.metrics), { recursive: true });
appendFileSync(PATHS.metrics, `${JSON.stringify(envelope.metric)}\n`, 'utf8');
atomicJson(tracePath, envelope.trace);
if (envelope.events.length > 0) {
const eventPath = join(PATHS.traceEventDir, `${envelope.trace_id}.jsonl`);
mkdirSync(dirname(eventPath), { recursive: true });
appendFileSync(eventPath, `${envelope.events.map((event) => JSON.stringify(event)).join('\n')}\n`, 'utf8');
}
const receiptPath = join(APP_ROOT, '.specify', 'state', 'ingested', `${envelope.trace_id}.json`);
atomicJson(receiptPath, envelope.receipt ?? {});
return {
accepted: true,
duplicate: false,
project_id: envelope.project_id,
trace_id: envelope.trace_id,
};
}
}
@@ -14,7 +14,7 @@ function breakdownRows(rows: H6Breakdown[]): string {
if (rows.length === 0) return '<tr><td colspan="6" class="empty">No records in the selected scope.</td></tr>';
return rows.map((row) => `<tr>
<td>${escapeHtml(row.key)}</td><td>${number(row.runs)}</td><td>${number(row.failures)}</td>
<td>${number(row.latency_avg_ms)} ms</td><td>${number(row.tokens)}</td><td>${money(row.cost_usd)}</td>
<td>${number(row.latency_avg_ms)} ms</td><td>${row.tokens === null ? 'Unavailable' : number(row.tokens)}</td><td>${row.cost_usd === null ? 'Unavailable' : money(row.cost_usd)}</td>
</tr>`).join('');
}
@@ -50,7 +50,7 @@ main{max-width:1180px;margin:0 auto;padding:42px 28px 72px}.hero{position:relati
@media print{body{background:#fff}main{max-width:none;padding:0}.hero,.card,.section{box-shadow:none}.hero{background:#111827!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.section{break-inside:avoid}.footer{margin-top:10px}}
</style></head><body><main>
<section class="hero"><div class="eyebrow">CASAN assurance dossier · contract v${report.schema_version}</div><h1>${escapeHtml(report.title)}</h1><div class="scope">${scope.map((item) => `<span>${escapeHtml(item)}</span>`).join('')}</div><div class="verdict ${verdictClass}">${escapeHtml(report.verdict)}</div><div class="meta">Generated ${escapeHtml(report.generated_at)} · ${escapeHtml(report.report_id)} · Freshness ${escapeHtml(report.freshness.status)}</div></section>
<section class="grid"><div class="card"><span>Governed runs</span><strong>${number(report.summary.runs)}</strong></div><div class="card"><span>Failure rate</span><strong>${report.summary.failure_rate_pct}%</strong></div><div class="card"><span>P95 latency</span><strong>${number(report.summary.latency_ms.p95)} ms</strong></div><div class="card"><span>Provider tokens</span><strong>${number(report.summary.tokens.provider_total)}</strong></div><div class="card"><span>Actual provider cost</span><strong>${money(report.summary.cost_usd.provider_actual)}</strong></div><div class="card"><span>Estimated cost</span><strong>${money(report.summary.cost_usd.estimated)}</strong></div><div class="card"><span>Alerts</span><strong>${number(report.summary.alerts)}</strong></div><div class="card"><span>Retries</span><strong>${number(report.summary.retries)}</strong></div></section>
<section class="grid"><div class="card"><span>Governed runs</span><strong>${number(report.summary.runs)}</strong></div><div class="card"><span>Failure rate</span><strong>${report.summary.failure_rate_pct}%</strong></div><div class="card"><span>P95 latency</span><strong>${number(report.summary.latency_ms.p95)} ms</strong></div><div class="card"><span>Provider tokens</span><strong>${report.summary.coverage.token_records > 0 ? number(report.summary.tokens.provider_total ?? report.summary.tokens.total ?? 0) : 'Unavailable'}</strong></div><div class="card"><span>Actual provider cost</span><strong>${report.summary.cost_usd.provider_actual !== null ? money(report.summary.cost_usd.provider_actual) : 'Unavailable'}</strong></div><div class="card"><span>Token coverage</span><strong>${report.summary.coverage.token_pct}%</strong></div><div class="card"><span>Cost coverage</span><strong>${report.summary.coverage.cost_pct}%</strong></div><div class="card"><span>Retries</span><strong>${number(report.summary.retries)}</strong></div></section>
<section class="section"><h2>Verdict findings</h2><ul class="findings">${findings}</ul></section>
<section class="section"><h2>Evidence freshness</h2><div class="table-wrap"><table><thead><tr><th>Source</th><th>Presence</th><th>State</th><th>Age</th><th>Records</th><th>Path</th></tr></thead><tbody>${sourceRows}</tbody></table></div></section>
<section class="section"><h2>Step breakdown</h2><div class="table-wrap"><table><thead><tr><th>Step</th><th>Runs</th><th>Failures</th><th>Avg latency</th><th>Tokens</th><th>Cost</th></tr></thead><tbody>${breakdownRows(report.details.by_step)}</tbody></table></div></section>
@@ -37,10 +37,18 @@ export interface H6ReportSummary {
failure_rate_pct: number;
retries: number;
latency_ms: { average: number; p50: number; p95: number; p99: number; max: number };
tokens: { input: number; output: number; total: number; provider_total: number };
cost_usd: { provider_actual: number; estimated: number };
tokens: { input: number | null; output: number | null; total: number | null; provider_total: number | null };
cost_usd: { provider_actual: number | null; estimated: number | null };
provider_calls: number;
alerts: number;
coverage: {
runtime_records: number;
token_records: number;
cost_records: number;
token_pct: number;
cost_pct: number;
quality: { complete: number; partial: number; insufficient: number; unknown: number };
};
}
export interface H6Breakdown {
@@ -48,8 +56,8 @@ export interface H6Breakdown {
runs: number;
failures: number;
latency_avg_ms: number;
tokens: number;
cost_usd: number;
tokens: number | null;
cost_usd: number | null;
}
export interface H6ReportDetails {
@@ -66,6 +74,7 @@ export type H6Report = HarnessReport<H6ReportSummary, H6ReportDetails>;
const safeFilter = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
const numberValue = (value: unknown): number => typeof value === 'number' && Number.isFinite(value) ? value : 0;
const hasNumber = (value: unknown): boolean => typeof value === 'number' && Number.isFinite(value);
const stringValue = (value: unknown, fallback: string): string => typeof value === 'string' && value.trim() ? value.trim() : fallback;
const round = (value: number, digits = 2): number => Number(value.toFixed(digits));
const configuredThreshold = (name: string, fallback: number): number => {
@@ -148,8 +157,13 @@ function grouped(rows: TelemetryRow[], keyOf: (row: TelemetryRow) => string): H6
runs: records.length,
failures: records.filter((row) => stringValue(row.status, 'unknown') === 'failed').length,
latency_avg_ms: latencies.length ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : 0,
tokens: sum(records, 'total_tokens'),
cost_usd: round(sum(records, records.some((row) => row.cost_usd !== undefined) ? 'cost_usd' : 'cost_estimate'), 6),
tokens: records.some((row) => hasNumber(row.total_tokens))
? sum(records, 'total_tokens') : null,
cost_usd: records.some((row) => hasNumber(row.cost_usd))
? round(sum(records, 'cost_usd'), 6)
: records.some((row) => hasNumber(row.cost_estimate))
? round(sum(records, 'cost_estimate'), 6)
: null,
};
}).sort((left, right) => right.runs - left.runs || left.key.localeCompare(right.key));
}
@@ -195,6 +209,29 @@ export function buildH6Report(input: H6ReportInput, query: H6ReportQuery): H6Rep
const failed = metrics.filter((row) => stringValue(row.status, 'unknown') === 'failed').length;
const degraded = metrics.filter((row) => stringValue(row.status, 'unknown') === 'degraded').length;
const success = metrics.filter((row) => ['success', 'pass', 'passed'].includes(stringValue(row.status, 'unknown'))).length;
const providerTokenRuns = new Set(provider.filter((row) => hasNumber(row.total_tokens)).map(rowRun));
const providerCostRuns = new Set(provider.filter((row) => hasNumber(row.cost_usd)).map(rowRun));
const tokenRecords = metrics.filter((row) => (
hasNumber(row.total_tokens)
|| hasNumber(row.input_tokens)
|| hasNumber(row.output_tokens)
|| providerTokenRuns.has(rowRun(row))
)).length;
const costRecords = metrics.filter((row) => (
hasNumber(row.cost_estimate)
|| providerCostRuns.has(rowRun(row))
)).length;
const tokenCoverage = metrics.length ? round((tokenRecords / metrics.length) * 100, 1) : 0;
const costCoverage = metrics.length ? round((costRecords / metrics.length) * 100, 1) : 0;
const qualityCounts = { complete: 0, partial: 0, insufficient: 0, unknown: 0 };
for (const row of metrics) {
const quality = stringValue(row.telemetry_quality, 'unknown');
if (quality === 'complete' || quality === 'partial' || quality === 'insufficient') {
qualityCounts[quality] += 1;
} else {
qualityCounts.unknown += 1;
}
}
const sourceEvidence = evidenceSources(input);
const primary = sourceEvidence.find((source) => source.source === 'metrics');
const failureRate = metrics.length ? round((failed / metrics.length) * 100, 1) : 0;
@@ -223,7 +260,19 @@ export function buildH6Report(input: H6ReportInput, query: H6ReportQuery): H6Rep
const alertCount = alertCounts.reduce((total, entry) => total + entry.count, 0);
if (alertCount > 0) findings.push({ severity: 'warning', code: 'ALERTS_PRESENT', message: `${alertCount} alert signal(s) require review.`, metric: 'alerts', value: alertCount });
if (degraded > 0) findings.push({ severity: 'warning', code: 'DEGRADED_RUNS_PRESENT', message: `${degraded} degraded run(s) are present in the selected scope.`, metric: 'degraded', value: degraded });
if (metrics.length > 0 && (tokenCoverage < 100 || costCoverage < 100)) {
findings.push({
severity: 'warning',
code: 'TELEMETRY_COVERAGE_GAP',
message: 'Some runs do not have reliable provider token or cost attribution.',
metric: 'token_cost_coverage_pct',
value: `${tokenCoverage}/${costCoverage}`,
threshold: '100/100',
});
}
if (provider.length === 0) warnings.push('No provider usage records matched the selected scope; token and actual-cost breakdown may be incomplete.');
if (metrics.length > 0 && tokenCoverage < 100) warnings.push(`${tokenCoverage}% of runtime records have reliable token attribution; unavailable values remain null, never zero.`);
if (metrics.length > 0 && costCoverage < 100) warnings.push(`${costCoverage}% of runtime records have reliable cost attribution; unavailable values remain null, never zero.`);
for (const source of sourceEvidence) {
if (source.source === 'metrics') continue;
if (!source.present) warnings.push(`Optional ${source.source} telemetry source is missing; its breakdown is unavailable.`);
@@ -243,7 +292,7 @@ export function buildH6Report(input: H6ReportInput, query: H6ReportQuery): H6Rep
const costSources = grouped(metrics, (row) => stringValue(row.cost_source, 'unknown')).map((entry) => ({
source: entry.key,
records: entry.runs,
cost_usd: entry.cost_usd,
cost_usd: entry.cost_usd ?? 0,
}));
const scope: HarnessReportScope = { project: query.project, from: query.from, to: query.to, run: query.run };
@@ -272,15 +321,37 @@ export function buildH6Report(input: H6ReportInput, query: H6ReportQuery): H6Rep
p99: percentile(latencies, 99),
max: latencies.length ? Math.max(...latencies) : 0,
},
tokens: { input: sum(metrics, 'input_tokens'), output: sum(metrics, 'output_tokens'), total: sum(metrics, 'total_tokens'), provider_total: sum(provider, 'total_tokens') },
cost_usd: { provider_actual: round(sum(provider, 'cost_usd'), 6), estimated: round(sum(metrics, 'cost_estimate'), 6) },
tokens: {
input: metrics.some((row) => hasNumber(row.input_tokens)) ? sum(metrics, 'input_tokens') : null,
output: metrics.some((row) => hasNumber(row.output_tokens)) ? sum(metrics, 'output_tokens') : null,
total: metrics.some((row) => hasNumber(row.total_tokens)) ? sum(metrics, 'total_tokens') : null,
provider_total: provider.some((row) => hasNumber(row.total_tokens)) ? sum(provider, 'total_tokens') : null,
},
cost_usd: {
provider_actual: provider.some((row) => hasNumber(row.cost_usd)) ? round(sum(provider, 'cost_usd'), 6) : null,
estimated: metrics.some((row) => hasNumber(row.cost_estimate)) ? round(sum(metrics, 'cost_estimate'), 6) : null,
},
provider_calls: provider.length,
alerts: alertCount,
coverage: {
runtime_records: metrics.length,
token_records: tokenRecords,
cost_records: costRecords,
token_pct: tokenCoverage,
cost_pct: costCoverage,
quality: qualityCounts,
},
},
thresholds: { failure_rate_pct: failureThreshold, p95_latency_ms: p95Threshold, freshness_age_s: STALE_AFTER_S },
findings,
evidence_sources: sourceEvidence,
data_quality: { status: !primary?.present ? 'insufficient' : warnings.length ? 'partial' : 'complete', warnings },
data_quality: {
status: (
!primary?.present
|| (metrics.length > 0 && tokenRecords === 0 && costRecords === 0)
) ? 'insufficient' : warnings.length ? 'partial' : 'complete',
warnings,
},
available_filters: { projects: availableProjects, runs: availableRuns },
details: {
by_status: counted(metrics, (row) => stringValue(row.status, 'unknown')).map(({ key, count: countValue }) => ({ status: key, count: countValue })),
@@ -1,4 +1,4 @@
import { BadRequestException, Controller, Get, Header, Inject, Query, Res } from '@nestjs/common';
import { BadRequestException, Controller, Get, Header, Inject, Param, Query, Res } from '@nestjs/common';
import type { Response } from 'express';
import { ok } from '../common/api-response.js';
import { parseH6ReportQuery, type H6ReportQueryParams } from './h6-report.js';
@@ -34,4 +34,24 @@ export class ReportsController {
response.setHeader('Content-Disposition', `attachment; filename="casan-h6-report-${stamp}.${format}"`);
response.send(body);
}
@Get('run/:traceId')
run(@Param('traceId') traceId: string) {
return ok(this.reports.run(traceId));
}
@Get('run/:traceId/export')
@Header('Cache-Control', 'no-store')
exportRun(
@Param('traceId') traceId: string,
@Query('format') rawFormat: string | undefined,
@Res() response: Response,
) {
const format = rawFormat ?? 'json';
if (format !== 'json' && format !== 'html') throw new BadRequestException('RUN_REPORT_INVALID_FORMAT');
const report = this.reports.run(traceId);
response.type(format === 'html' ? 'text/html; charset=utf-8' : 'application/json; charset=utf-8');
response.setHeader('Content-Disposition', `attachment; filename="casan-run-${traceId}.${format}"`);
response.send(this.reports.serializeRun(report, format));
}
}
@@ -1,8 +1,10 @@
import { Module } from '@nestjs/common';
import { ReportsController } from './reports.controller.js';
import { ReportsService } from './reports.service.js';
import { TelemetryModule } from '../telemetry/telemetry.module.js';
@Module({
imports: [TelemetryModule],
controllers: [ReportsController],
providers: [ReportsService],
exports: [ReportsService],
@@ -1,12 +1,20 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { PATHS, telemetryFreshness } from '../common/app-root.js';
import { readJsonl } from '../telemetry/telemetry.reader.js';
import { TelemetryService } from '../telemetry/telemetry.service.js';
import { buildH6Report, type H6Report, type H6ReportQuery } from './h6-report.js';
import { renderH6ReportHtml } from './h6-report.html.js';
import { HARNESS_REPORT_CATALOG } from './report.contract.js';
import { buildRunAssuranceReport, type RunAssuranceReport } from './run-report.js';
import { renderRunAssuranceHtml } from './run-report.html.js';
import { APP_ROOT } from '../common/app-root.js';
@Injectable()
export class ReportsService {
constructor(@Inject(TelemetryService) private readonly telemetry: TelemetryService) {}
catalog() {
return { schema_version: 1, reports: HARNESS_REPORT_CATALOG };
}
@@ -25,4 +33,35 @@ export class ReportsService {
serializeH6(report: H6Report, format: 'json' | 'html'): string {
return format === 'html' ? renderH6ReportHtml(report) : `${JSON.stringify(report, null, 2)}\n`;
}
run(traceId: string): RunAssuranceReport {
if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(traceId)) {
throw new NotFoundException('CASAN_RUN_NOT_FOUND');
}
const graph = this.telemetry.traceGraph(traceId);
const traceResult = this.telemetry.run(traceId);
const metrics = readJsonl(PATHS.metrics);
const metric = [...metrics].reverse().find((row) => row.trace_id === traceId) ?? null;
let config: Record<string, unknown> = {};
try {
config = JSON.parse(readFileSync(join(APP_ROOT, '.casan', 'config.json'), 'utf8')) as Record<string, unknown>;
} catch {
config = {};
}
const report = buildRunAssuranceReport({
traceId,
graph,
trace: traceResult.trace,
metric,
config,
});
if (report.verdict === 'not_found') throw new NotFoundException('CASAN_RUN_NOT_FOUND');
return report;
}
serializeRun(report: RunAssuranceReport, format: 'json' | 'html'): string {
return format === 'html'
? renderRunAssuranceHtml(report)
: `${JSON.stringify(report, null, 2)}\n`;
}
}
@@ -0,0 +1,57 @@
import type { RunAssuranceReport } from './run-report.js';
const escapeHtml = (value: unknown): string => String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;');
const badgeClass = (status: string): string => {
if (status === 'pass' || status === 'certified') return 'pass';
if (status === 'warning' || status === 'in_progress') return 'warn';
if (status === 'queued' || status === 'skipped') return 'muted';
return 'fail';
};
export function renderRunAssuranceHtml(report: RunAssuranceReport): string {
const gates = report.gates.map((gate) => `
<article class="gate">
<div class="gate-head"><strong>${escapeHtml(gate.title)}</strong><span class="badge ${badgeClass(gate.status)}">${escapeHtml(gate.status)}</span></div>
<p>${escapeHtml(gate.reason)}</p>
<small>${escapeHtml(gate.updated_at ?? 'No timestamp')}</small>
<details><summary>Evidence fields</summary><pre>${escapeHtml(JSON.stringify(gate.evidence, null, 2))}</pre></details>
</article>`).join('');
const tokenValue = report.summary.token_usage_available ? 'Available' : 'Unavailable';
const costValue = report.summary.cost_available ? 'Available' : 'Unavailable';
return `<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>CASAN assurance receipt · ${escapeHtml(report.trace_id)}</title>
<style>
:root{color-scheme:light;--ink:#0f172a;--muted:#64748b;--line:#e2e8f0;--panel:#fff;--bg:#f8fafc;--indigo:#4f46e5}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 Inter,ui-sans-serif,system-ui,-apple-system,sans-serif}
main{max-width:1160px;margin:0 auto;padding:40px 24px 64px}.hero{position:relative;overflow:hidden;border-radius:28px;background:#111827;color:#fff;padding:32px;box-shadow:0 24px 60px rgba(15,23,42,.18)}
.hero:after{content:"";position:absolute;right:-80px;top:-100px;width:280px;height:280px;border-radius:50%;background:rgba(99,102,241,.3);filter:blur(50px)}.eyebrow{color:#a5b4fc;font-size:11px;font-weight:800;letter-spacing:.18em;text-transform:uppercase}
h1{position:relative;margin:8px 0 2px;font-size:30px;letter-spacing:-.035em}.trace{position:relative;color:#94a3b8;font:12px ui-monospace,SFMono-Regular,monospace;word-break:break-all}
.summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:18px 0}.metric,.section{border:1px solid var(--line);background:var(--panel);border-radius:18px;padding:18px}.metric span{display:block;color:var(--muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em}.metric strong{display:block;margin-top:8px;font-size:22px}
.section{margin-top:16px}.section h2{margin:0 0 14px;font-size:16px}.rail{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:10px}.gate{min-width:0;border:1px solid var(--line);border-radius:15px;padding:14px;background:#fff}.gate-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.gate p{min-height:44px;color:#475569;font-size:12px}.gate small{color:#94a3b8;font-size:10px}.gate details{margin-top:10px;color:#64748b;font-size:10px}.gate summary{cursor:pointer;font-weight:700}.gate pre{max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:8px;background:#f8fafc;padding:8px;font-size:9px}.badge{border-radius:999px;padding:3px 7px;font-size:9px;font-weight:800;text-transform:uppercase}.pass{background:#dcfce7;color:#166534}.warn{background:#fef3c7;color:#92400e}.fail{background:#ffe4e6;color:#9f1239}.muted{background:#f1f5f9;color:#64748b}
.note{color:var(--muted);font-size:12px}.footer{margin-top:18px;color:#94a3b8;font-size:11px}
@media(max-width:900px){.summary{grid-template-columns:repeat(2,1fr)}.rail{grid-template-columns:1fr 1fr}}@media(max-width:520px){main{padding:18px 12px}.hero{padding:22px}.rail,.summary{grid-template-columns:1fr}}
@media print{body{background:#fff}main{max-width:none;padding:0}.hero,.metric,.section{box-shadow:none;break-inside:avoid}}
</style></head><body><main>
<section class="hero"><div class="eyebrow">CASAN · evidence-backed assurance</div><h1>${escapeHtml(report.verdict.replace('_', ' ').toUpperCase())}</h1><div class="trace">${escapeHtml(report.trace_id)}</div></section>
<section class="summary">
<div class="metric"><span>Harness gates</span><strong>${report.summary.gates_observed}/7</strong></div>
<div class="metric"><span>H6 quality</span><strong>${escapeHtml(report.summary.telemetry_quality)}</strong></div>
<div class="metric"><span>Token usage</span><strong>${tokenValue}</strong></div>
<div class="metric"><span>Cost</span><strong>${costValue}</strong></div>
<div class="metric"><span>Duration</span><strong>${report.summary.duration_ms === null ? 'Unavailable' : `${report.summary.duration_ms} ms`}</strong></div>
<div class="metric"><span>Tool calls</span><strong>${report.summary.tool_calls}</strong></div>
<div class="metric"><span>Failures</span><strong>${report.summary.failures}</strong></div>
<div class="metric"><span>Evidence source</span><strong>${report.source.trace_found && report.source.graph_found ? 'Verified' : 'Partial'}</strong></div>
</section>
<section class="section"><h2>Live assurance rail · H1 → H7</h2><div class="rail">${gates}</div></section>
<section class="section"><h2>Certification</h2><p>Strength: <strong>${escapeHtml(report.certification.strength ?? 'unknown')}</strong></p><p class="note">${escapeHtml(report.certification.reasons.join(' · ') || 'No certification reason recorded.')}</p></section>
<div class="footer">Generated ${escapeHtml(report.generated_at)} · Project ${escapeHtml(report.project.id)} · Edition ${escapeHtml(report.project.edition ?? 'unknown')} · Maturity ${escapeHtml(report.project.maturity?.status ?? 'not assessed')}. No maturity score or unavailable telemetry value is hard-coded.</div>
</main></body></html>`;
}
@@ -0,0 +1,147 @@
export interface RunGateSnapshot {
id: string;
title: string;
description: string;
status: string;
reason: string;
updated_at: string | null;
evidence: Record<string, unknown>;
}
export interface RunAssuranceReport {
schema_version: 1;
report_id: string;
generated_at: string;
trace_id: string;
project: {
id: string;
edition: string | null;
maturity: { level: number | null; status: string } | null;
};
verdict: 'certified' | 'non_certified' | 'in_progress' | 'not_found';
certification: {
strength: string | null;
reasons: string[];
finalized_at: string | null;
};
summary: {
gates_observed: number;
gates_total: 7;
tool_calls: number;
failures: number;
duration_ms: number | null;
telemetry_quality: string;
token_usage_available: boolean;
cost_available: boolean;
};
gates: RunGateSnapshot[];
h6: Record<string, unknown> | null;
source: {
trace_found: boolean;
graph_found: boolean;
metric_found: boolean;
};
}
type Row = Record<string, unknown>;
const stringValue = (value: unknown, fallback = ''): string => (
typeof value === 'string' && value.trim() ? value.trim() : fallback
);
const numberValue = (value: unknown, fallback = 0): number => (
typeof value === 'number' && Number.isFinite(value) ? value : fallback
);
const hasNumber = (value: unknown): boolean => (
typeof value === 'number' && Number.isFinite(value)
);
export function buildRunAssuranceReport(input: {
traceId: string;
graph: {
found: boolean;
terminal: boolean;
progress: number;
nodes: RunGateSnapshot[];
};
trace: Row | null;
metric: Row | null;
config: Row;
now?: Date;
}): RunAssuranceReport {
const now = input.now ?? new Date();
const trace = input.trace ?? {};
const metric = input.metric ?? {};
const certified = trace.certified === true;
const traceFound = input.trace !== null;
const metricFound = input.metric !== null;
const verdict = (
!traceFound && !input.graph.found && !metricFound ? 'not_found'
: !input.graph.terminal && !trace.finalized_at ? 'in_progress'
: certified ? 'certified' : 'non_certified'
);
const maturity = input.config.maturity && typeof input.config.maturity === 'object'
? input.config.maturity as Record<string, unknown>
: null;
const duration = (
hasNumber(metric.duration_ms) ? Number(metric.duration_ms)
: hasNumber(metric.latency_ms) ? Number(metric.latency_ms)
: null
);
const costObject = metric.cost && typeof metric.cost === 'object'
? metric.cost as Record<string, unknown>
: {};
return {
schema_version: 1,
report_id: `RUN-${input.traceId}`,
generated_at: now.toISOString(),
trace_id: input.traceId,
project: {
id: stringValue(
input.config.project_id ?? trace.project_id ?? metric.project_id,
'unknown',
),
edition: stringValue(
input.config.edition ?? input.config.target_level_name,
) || null,
maturity: maturity ? {
level: hasNumber(maturity.level) ? Number(maturity.level) : null,
status: stringValue(maturity.status, 'not_assessed'),
} : null,
},
verdict,
certification: {
strength: stringValue(
trace.certification_strength ?? metric.certification_strength,
) || null,
reasons: Array.isArray(trace.certification_reasons)
? trace.certification_reasons.filter(
(value): value is string => typeof value === 'string')
: [],
finalized_at: stringValue(trace.finalized_at ?? metric.finished_at) || null,
},
summary: {
gates_observed: input.graph.progress,
gates_total: 7,
tool_calls: numberValue(trace.tool_calls ?? metric.tool_calls),
failures: numberValue(trace.failures ?? metric.failures),
duration_ms: duration,
telemetry_quality: stringValue(metric.telemetry_quality, 'unknown'),
token_usage_available: (
hasNumber(metric.total_tokens)
|| hasNumber(metric.input_tokens)
|| hasNumber(metric.output_tokens)
),
cost_available: (
hasNumber(metric.cost_estimate)
|| hasNumber(costObject.amount)
),
},
gates: input.graph.nodes,
h6: input.metric,
source: {
trace_found: traceFound,
graph_found: input.graph.found,
metric_found: metricFound,
},
};
}
@@ -16,6 +16,11 @@ export class TelemetryController {
return ok(this.svc.overview());
}
@Get('project')
project() {
return ok(this.svc.projectProfile());
}
// Production reverse proxy policy must restrict this aggregate-only endpoint
// to the monitoring network / service account.
@Get('metrics')
@@ -5,5 +5,6 @@ import { TelemetryService } from './telemetry.service.js';
@Module({
controllers: [TelemetryController],
providers: [TelemetryService],
exports: [TelemetryService],
})
export class TelemetryModule {}
@@ -59,9 +59,9 @@ const pct = (part: number, total: number) => (total > 0 ? Math.round((part / tot
function gateStatus(value: unknown): HarnessGateStatus {
const status = String(value ?? '').toLowerCase();
if (['success', 'pass', 'passed', 'allow', 'allowed', 'answered'].includes(status)) return 'pass';
if (['warn', 'warning'].includes(status)) return 'warning';
if (['block', 'blocked', 'deny', 'denied'].includes(status)) return 'blocked';
if (['success', 'pass', 'passed', 'allow', 'allowed', 'answered', 'opened', 'certified'].includes(status)) return 'pass';
if (['warn', 'warning', 'degraded', 'partial', 'insufficient'].includes(status)) return 'warning';
if (['block', 'blocked', 'deny', 'denied', 'flag', 'non_certified'].includes(status)) return 'blocked';
if (['fail', 'failed', 'error'].includes(status)) return 'error';
if (status === 'running') return 'running';
if (status === 'skipped') return 'skipped';
@@ -232,11 +232,23 @@ export class TelemetryService {
const runs = metrics.length;
const latencies = metrics.map((m) => num(m.latency_ms)).filter((x) => x > 0);
const latestMetric = metrics.length ? metrics[metrics.length - 1] : null;
const latestTraceId = typeof latestMetric?.trace_id === 'string' ? latestMetric.trace_id : null;
const latestTrace = latestTraceId ? readTrace(PATHS.traceDir, latestTraceId) : null;
const latestGraph = latestTraceId ? this.traceGraph(latestTraceId) : null;
return {
...this.freshness(),
totals: {
runs,
total_cost: sum(metrics, 'cost_estimate'),
token_coverage_pct: pct(
count(metrics, (m) => m.total_tokens != null || m.input_tokens != null || m.output_tokens != null),
runs,
),
cost_coverage_pct: pct(
count(metrics, (m) => m.cost_estimate != null || (m.cost && m.cost.amount != null)),
runs,
),
avg_latency_ms: latencies.length ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : 0,
failures: count(metrics, (m) => m.status === 'failed'),
hallucination_signals: sum(metrics, 'hallucination_signals'),
@@ -260,6 +272,39 @@ export class TelemetryService {
head: readHead(PATHS.auditHead),
last_decision: audit.length ? audit[audit.length - 1].decision ?? null : null,
},
latest_assurance: latestTraceId && latestMetric && latestGraph ? {
trace_id: latestTraceId,
certified: latestTrace?.certified === true,
certification_strength: latestTrace?.certification_strength ?? latestMetric.certification_strength ?? null,
telemetry_quality: latestMetric.telemetry_quality ?? 'unknown',
duration_ms: latestMetric.duration_ms ?? latestMetric.latency_ms ?? null,
tool_calls: latestMetric.tool_calls ?? 0,
failures: latestMetric.failures ?? 0,
graph: latestGraph,
} : null,
};
}
projectProfile() {
const config = readJson(join(APP_ROOT, '.casan', 'config.json')) as Row | null;
const maturity = config?.maturity && typeof config.maturity === 'object'
? config.maturity as Row
: null;
return {
project_id: String(config?.project_id ?? APP_ROOT.split('/').pop() ?? 'unknown'),
app_root: APP_ROOT,
edition: String(config?.edition ?? config?.target_level_name ?? 'unknown'),
edition_status: String(config?.edition_status ?? 'unknown'),
maturity: {
level: typeof maturity?.level === 'number' ? maturity.level : null,
status: String(maturity?.status ?? 'not_assessed'),
evidence: typeof maturity?.evidence === 'string' ? maturity.evidence : null,
},
enforcement_mode: String(config?.enforcement_mode ?? 'unknown'),
integration_mode: String(config?.integration_mode ?? 'unknown'),
clients: Array.isArray(config?.clients)
? config.clients.filter((value): value is string => typeof value === 'string')
: [],
};
}
@@ -351,13 +396,45 @@ export class TelemetryService {
},
});
}
const gateMap: Record<string, string> = {
H1: 'H1-context',
H2: 'H2-tool',
H3: 'H3-eval',
H4: 'H4-security',
H5: 'H5-governance',
H6: 'H6-agentops',
H7: 'H7-orchestration',
};
for (const evidence of arr(legacyTrace.evidence)) {
const harnesses = String(evidence.h ?? '').split('/');
for (const harness of harnesses) {
const mappedGate = gateMap[harness];
if (!mappedGate) continue;
events.push({
timestamp: String(evidence.at ?? legacyTrace.finalized_at ?? ''),
trace_id: safeId,
gate_id: mappedGate,
status: gateStatus(evidence.decision),
reason: `${String(evidence.kind ?? 'legacy-evidence')}: ${String(evidence.detail ?? '')}`,
evidence: {
kind: evidence.kind ?? null,
decision: evidence.decision ?? null,
detail: evidence.detail ?? null,
certification_strength: legacyTrace.certification_strength ?? null,
},
});
}
}
}
for (const metric of readJsonl<Row>(PATHS.metrics).filter((row) => row.trace_id === safeId)) {
const telemetryQuality = String(metric.telemetry_quality ?? '');
events.push({
timestamp: String(metric.timestamp ?? ''),
trace_id: safeId,
gate_id: 'H6-agentops',
status: gateStatus(metric.status),
status: ['partial', 'insufficient'].includes(telemetryQuality)
? 'warning'
: gateStatus(metric.status),
reason: String(metric.step ?? 'Legacy runtime metric'),
evidence: {
latency_ms: metric.latency_ms ?? null,