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:
54
src/app.ts
54
src/app.ts
@@ -21,6 +21,7 @@ import { OrgRepository } from './repositories/OrgRepository.js';
|
||||
|
||||
import { AuditService } from './services/AuditService.js';
|
||||
import { AgentService } from './services/AgentService.js';
|
||||
import { AnalyticsService } from './services/AnalyticsService.js';
|
||||
import { MarketplaceService } from './services/MarketplaceService.js';
|
||||
import { BillingService } from './services/BillingService.js';
|
||||
import { UsageService } from './services/UsageService.js';
|
||||
@@ -36,6 +37,7 @@ import { EventPublisher } from './services/EventPublisher.js';
|
||||
import { WebhookDeliveryWorker } from './workers/WebhookDeliveryWorker.js';
|
||||
import { createKafkaProducer } from './adapters/KafkaAdapter.js';
|
||||
|
||||
import { AnalyticsController } from './controllers/AnalyticsController.js';
|
||||
import { AgentController } from './controllers/AgentController.js';
|
||||
import { MarketplaceController } from './controllers/MarketplaceController.js';
|
||||
import { BillingController } from './controllers/BillingController.js';
|
||||
@@ -50,7 +52,9 @@ import { OIDCController } from './controllers/OIDCController.js';
|
||||
import { FederationController } from './controllers/FederationController.js';
|
||||
import { WebhookController } from './controllers/WebhookController.js';
|
||||
import { ComplianceController } from './controllers/ComplianceController.js';
|
||||
import { ComplianceService } from './services/ComplianceService.js';
|
||||
|
||||
import { createAnalyticsRouter } from './routes/analytics.js';
|
||||
import { createAgentsRouter } from './routes/agents.js';
|
||||
import { createMarketplaceRouter } from './routes/marketplace.js';
|
||||
import { createBillingRouter } from './routes/billing.js';
|
||||
@@ -74,6 +78,9 @@ import { DelegationController } from './controllers/DelegationController.js';
|
||||
import { createScaffoldRouter } from './routes/scaffold.js';
|
||||
import { ScaffoldService } from './services/ScaffoldService.js';
|
||||
import { ScaffoldController } from './controllers/ScaffoldController.js';
|
||||
import { TierService } from './services/TierService.js';
|
||||
import { TierController } from './controllers/TierController.js';
|
||||
import { createTiersRouter } from './routes/tiers.js';
|
||||
|
||||
import { errorHandler } from './middleware/errorHandler.js';
|
||||
import { createOpaMiddleware } from './middleware/opa.js';
|
||||
@@ -81,7 +88,7 @@ import { metricsMiddleware } from './middleware/metrics.js';
|
||||
import { createOrgContextMiddleware } from './middleware/orgContext.js';
|
||||
import { authMiddleware } from './middleware/auth.js';
|
||||
import { createUsageMeteringMiddleware, startUsageMeteringFlush } from './middleware/usageMeteringMiddleware.js';
|
||||
import { createFreeTierEnforcementMiddleware } from './middleware/freeTierEnforcementMiddleware.js';
|
||||
import { createTierEnforcementMiddleware } from './middleware/tierEnforcement.js';
|
||||
import { tlsEnforcementMiddleware } from './middleware/TLSEnforcementMiddleware.js';
|
||||
import { createVaultClientFromEnv } from './vault/VaultClient.js';
|
||||
import { getEncryptionService } from './services/EncryptionService.js';
|
||||
@@ -191,12 +198,25 @@ export async function createApp(): Promise<Application> {
|
||||
webhookWorker.start();
|
||||
const eventPublisher = new EventPublisher(webhookWorker, pool, kafkaProducer);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Stripe client + TierService — created early so both BillingService
|
||||
// and AgentService can receive TierService via constructor injection.
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const stripe = new Stripe(process.env['STRIPE_SECRET_KEY'] ?? '', { apiVersion: '2026-03-25.dahlia' });
|
||||
const tierService = new TierService(pool, redis as RedisClientType, stripe);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Service layer
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const auditService = new AuditService(auditRepo);
|
||||
const didService = new DIDService(pool, vaultClient, redis as RedisClientType, encryptionService);
|
||||
const agentService = new AgentService(agentRepo, credentialRepo, auditService, didService, eventPublisher);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Phase 6 WS3: Analytics Service
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const analyticsService = new AnalyticsService(pool);
|
||||
|
||||
const agentService = new AgentService(agentRepo, credentialRepo, auditService, didService, eventPublisher, analyticsService, tierService);
|
||||
const marketplaceService = new MarketplaceService(agentRepo);
|
||||
const credentialService = new CredentialService(credentialRepo, agentRepo, auditService, vaultClient, eventPublisher, encryptionService);
|
||||
const orgService = new OrgService(orgRepo, agentRepo);
|
||||
@@ -223,6 +243,7 @@ export async function createApp(): Promise<Application> {
|
||||
idTokenService,
|
||||
eventPublisher,
|
||||
encryptionService,
|
||||
analyticsService,
|
||||
);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
@@ -234,6 +255,7 @@ export async function createApp(): Promise<Application> {
|
||||
// Controller layer
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const agentController = new AgentController(agentService);
|
||||
const analyticsController = new AnalyticsController(analyticsService);
|
||||
const tokenController = new TokenController(oauth2Service);
|
||||
const credentialController = new CredentialController(credentialService);
|
||||
const auditController = new AuditController(auditService);
|
||||
@@ -248,8 +270,7 @@ export async function createApp(): Promise<Application> {
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Billing & Usage Metering (WS6)
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const stripe = new Stripe(process.env['STRIPE_SECRET_KEY'] ?? '', { apiVersion: '2026-03-25.dahlia' });
|
||||
const billingService = new BillingService(pool, stripe);
|
||||
const billingService = new BillingService(pool, stripe, tierService);
|
||||
const usageService = new UsageService(pool);
|
||||
const billingController = new BillingController(billingService, usageService);
|
||||
|
||||
@@ -265,7 +286,8 @@ export async function createApp(): Promise<Application> {
|
||||
// Compliance services and background jobs (SOC 2 Type II)
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const auditVerificationService = getAuditVerificationService(pool);
|
||||
const complianceController = new ComplianceController(auditVerificationService);
|
||||
const complianceService = new ComplianceService(pool, redis as RedisClientType);
|
||||
const complianceController = new ComplianceController(auditVerificationService, complianceService);
|
||||
|
||||
// Start background compliance monitoring jobs (non-blocking)
|
||||
startSecretsRotationJob(pool);
|
||||
@@ -285,10 +307,12 @@ export async function createApp(): Promise<Application> {
|
||||
app.use(createUsageMeteringMiddleware(pool));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Free tier enforcement — rejects requests exceeding free plan limits
|
||||
// Applied after usage metering and before routes.
|
||||
// Tier enforcement — Redis-backed daily API call rate limits per
|
||||
// tenant tier (free/pro/enterprise). Runs after auth; skipped when
|
||||
// TIER_ENFORCEMENT=false or for enterprise tenants. Supersedes
|
||||
// the legacy freeTierEnforcementMiddleware (removed Phase 6 WS4).
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
app.use(createFreeTierEnforcementMiddleware(pool, redis as RedisClientType));
|
||||
app.use(createTierEnforcementMiddleware(pool, redis as RedisClientType));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Routes
|
||||
@@ -326,6 +350,12 @@ export async function createApp(): Promise<Application> {
|
||||
// Billing & Usage Metering — checkout, webhook, usage summary
|
||||
app.use(`${API_BASE}/billing`, createBillingRouter(billingController, authMiddleware));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Phase 6 WS4: Tier management — status and upgrade endpoints
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const tierController = new TierController(tierService);
|
||||
app.use(`${API_BASE}/tiers`, createTiersRouter(tierController, authMiddleware));
|
||||
|
||||
// OIDC trust-policy management (authenticated) and token exchange (unauthenticated)
|
||||
// Both routers mount under ${API_BASE}/oidc — trust-policy routes use /trust-policies prefix,
|
||||
// token exchange uses /token, so there are no path conflicts.
|
||||
@@ -341,6 +371,14 @@ export async function createApp(): Promise<Application> {
|
||||
app.use(`${API_BASE}`, createDelegationRouter(delegationController, authMiddleware));
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Phase 6 WS3: Analytics (guarded by ANALYTICS_ENABLED flag)
|
||||
// When disabled, all /api/v1/analytics/* routes return 404.
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
if (process.env['ANALYTICS_ENABLED'] !== 'false') {
|
||||
app.use(`${API_BASE}/analytics`, createAnalyticsRouter(analyticsController, authMiddleware));
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Phase 5 WS5: Scaffold Generator
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user