feat(phase-6): WS3+WS4+WS6 — Analytics, API Tiers, AGNTCY Compliance

WS3 — Advanced Analytics Dashboard:
- DB migration: analytics_events table (tenant_id, date, metric_type, count)
- AnalyticsService: recordEvent (fire-and-forget), getTokenTrend, getAgentActivity, getAgentUsageSummary
- Analytics hooks in OAuth2Service (token_issued) and AgentService (agent_registered/deactivated)
- AnalyticsController + routes/analytics.ts (gated by ANALYTICS_ENABLED flag)
- Portal: TokenTrendChart (recharts LineChart), AgentHeatmap (recharts heatmap), /analytics page

WS4 — API Gateway Tiers:
- DB migration: tenant_tiers table; src/config/tiers.ts (free/pro/enterprise limits)
- TierService: getStatus, initiateUpgrade (Stripe), applyUpgrade; TierLimitError in errors.ts
- tierEnforcement middleware (Redis-backed daily call/token counters; TIER_ENFORCEMENT flag)
- Agent count enforcement in AgentService.create()
- Stripe webhook updated to call TierService.applyUpgrade() on checkout.session.completed
- TierController + routes/tiers.ts; Portal: /settings/tier page with upgrade flow

WS6 — AGNTCY Compliance Certification:
- ComplianceService: generateReport() (Redis-cached 5 min), exportAgentCards()
- Compliance sections: agent-identity (DID + credential expiry checks), audit-trail (Merkle chain)
- ComplianceController updated with getComplianceReport, exportAgentCards handlers
- routes/compliance.ts: new AGNTCY routes (gated by COMPLIANCE_ENABLED flag); SOC2 routes unaffected

QA:
- 28 new unit tests: AnalyticsService (8), TierService (9), ComplianceService (11) — all pass
- 673 total unit tests passing; 0 TypeScript errors across API and portal
- AGNTCY conformance test suite at tests/agntcy-conformance/ (4 protocol tests)
- Portal builds cleanly: 9 routes including /analytics and /settings/tier
- Feature flags verified: ANALYTICS_ENABLED, TIER_ENFORCEMENT, COMPLIANCE_ENABLED

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SentryAgent.ai Developer
2026-04-04 02:20:09 +00:00
parent 0fad328329
commit eea885db04
34 changed files with 4262 additions and 25 deletions

View File

@@ -0,0 +1,99 @@
'use client';
/**
* TokenTrendChart — Recharts LineChart showing daily token issuance counts.
*
* This component is designed to be lazy-loaded via `next/dynamic`. Do NOT
* import it directly from a page; use dynamic(() => import('./TokenTrendChart'))
* so that recharts stays out of the main bundle.
*
* @module components/charts/TokenTrendChart
*/
import React from 'react';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from 'recharts';
/** A single data point for the token trend line chart. */
export interface TokenTrendDataPoint {
/** ISO 8601 date string (e.g. "2026-03-01"). */
date: string;
/** Number of tokens issued on this date. */
count: number;
}
/** Props for the TokenTrendChart component. */
export interface TokenTrendChartProps {
/** Array of daily token issuance data points, sorted ascending by date. */
data: TokenTrendDataPoint[];
}
/**
* Formats an ISO date string as a short label (e.g. "Mar 1").
*
* @param dateStr - ISO 8601 date string
* @returns Formatted short date label
*/
function formatDateLabel(dateStr: string): string {
const d = new Date(dateStr);
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
/**
* Renders a responsive line chart of daily token issuance counts using recharts.
*
* @param props - TokenTrendChartProps
* @returns JSX element
*/
export default function TokenTrendChart({
data,
}: TokenTrendChartProps): React.ReactElement {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart
data={data}
margin={{ top: 8, right: 16, left: 0, bottom: 8 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis
dataKey="date"
tickFormatter={formatDateLabel}
tick={{ fontSize: 12, fill: '#64748b' }}
tickLine={false}
axisLine={{ stroke: '#cbd5e1' }}
interval="preserveStartEnd"
/>
<YAxis
tick={{ fontSize: 12, fill: '#64748b' }}
tickLine={false}
axisLine={false}
allowDecimals={false}
/>
<Tooltip
formatter={(value: number) => [value.toLocaleString(), 'Tokens issued']}
labelFormatter={(label: string) => formatDateLabel(label)}
contentStyle={{
borderRadius: '8px',
border: '1px solid #e2e8f0',
fontSize: '13px',
}}
/>
<Line
type="monotone"
dataKey="count"
stroke="#6366f1"
strokeWidth={2}
dot={false}
activeDot={{ r: 4, fill: '#6366f1' }}
/>
</LineChart>
</ResponsiveContainer>
);
}