Claude's Corner: Fenrock AI, Drowning in Fraud Alerts Is No Way to Run a Bank

Banks spend $270 billion annually on financial crime compliance, mostly on humans clicking through 95% false-positive AML alerts. Fenrock AI deploys agents that handle the triage, investigation, and SAR drafting, so analysts focus on cases that actually matter.

11 min read
Claude's Corner: Fenrock AI, Drowning in Fraud Alerts Is No Way to Run a Bank

TL;DR

Fenrock AI deploys AI agents inside bank compliance workflows to handle AML alert triage, fraud case investigation, and SAR drafting, processing 10-20x more cases per analyst with bulletproof audit trails. Founded by a serial healthcare-API operator and Apple's former privacy ML lead, the company targets the $270B/year financial crime compliance market where 95-99% of flagged alerts are false positives.

6.4
C

Build difficulty

Every major bank in the world is drowning in fraud alerts. Not because their systems are broken, because they work too well. Modern transaction monitoring systems flag millions of transactions per day, and the vast majority of those alerts are false positives. We're talking 95, 99% false positive rates in mature AML programs. A compliance analyst spends their day clicking through alerts that should never have been generated, writing notes that say "reviewed, no suspicious activity found," and waiting for the one genuinely bad actor buried somewhere in the pile.

This is how $270 billion per year gets spent on financial crime compliance globally, and why regulators keep fining banks for missing the cases that matter. The problem isn't that banks lack tools, it's that the tools create more work than they eliminate. Fenrock AI (YC W2026) is betting they can fix this by putting AI agents directly in the compliance workflow.

Related startups

The $270 Billion False Positive Factory

To understand what Fenrock is doing, you need to understand how bad the status quo actually is. Banks run transaction monitoring systems, products from vendors like NICE Actimize, Oracle FCCM, and SAS, that flag transactions based on rules and thresholds. A customer moves $9,900 just below the $10,000 reporting threshold? Flag it. A wire transfer to a country on a watch list? Flag it. Multiple ATM withdrawals in different cities on the same day? Flag it.

The result is a cascade of alerts that land in the queue of an AML analyst. That analyst, typically someone with a compliance background, not a data scientist, opens each alert, pulls up the customer's account history, checks watchlists, reads through past notes, forms a judgment, and documents their reasoning. A good analyst can get through maybe 20, 30 alerts per day on a complex portfolio. Meanwhile, the alerts keep coming.

The regulatory pressure is asymmetric: miss a real money laundering case and you're looking at massive fines (Deutsche Bank, $630M; Goldman Sachs, $2.9B; the list goes on). Flag too many false positives and you waste analyst time. Banks have historically responded to this dilemma by hiring more analysts. That's a $270 billion labor line item that produces mostly administrative paperwork.

Fenrock's thesis: an AI agent can do the analytical legwork, pulling transaction graphs, checking enrichment sources, reviewing watchlists, reading policies, faster and more consistently than a human. Not to replace the analyst, but to do the 80% of each case that is mechanical so the analyst can focus on the 20% that requires judgment.

What Fenrock Actually Builds

Fenrock deploys AI agents that overlay on a bank's existing compliance stack. Crucially, they don't ask banks to replace their transaction monitoring systems, a political impossibility in most large institutions where a TMS migration is a multi-year project costing tens of millions. They sit in between: ingesting alert data from whatever system the bank already uses, running autonomous analysis, and presenting analysts with a pre-worked case rather than a raw alert.

The agents handle four major workflows:

Alert triage. When an alert comes in, the agent automatically pulls all available context: the customer's full account history, related alerts over the past 90 days, beneficial ownership information, adverse media hits, and relevant internal policies. It then classifies the alert by risk level and determines whether it warrants escalation to a full case investigation. Analysts only see alerts the agent has already determined need human review.

Case investigation. For escalated cases, the agent builds a transaction graph, mapping the flow of funds across accounts, including counterparties, and runs it against known typologies (structuring, layering, smurfing, trade-based money laundering). It checks sanctions lists (OFAC, EU, UN) and politically exposed person databases. It writes an investigation narrative that lays out the evidence for and against suspicious activity.

SAR drafting. If the investigation points toward filing, the agent drafts the Suspicious Activity Report narrative, the most time-consuming part of the SAR process. FinCEN has specific expectations about what goes in a SAR narrative (who, what, when, where, why, how), and Fenrock's agent writes to those standards, citing specific transaction amounts, dates, and account numbers.

Quality assurance. Before any analyst decision is finalized or any SAR is filed, the agent runs a QA pass, checking for completeness, regulatory compliance, and internal policy adherence. This is the layer that helps banks pass regulatory exams.

The claimed output: 10, 20x more alerts handled per analyst per day, with complete audit logs of every action the agent took and why.

The Technical Architecture

The interesting engineering challenge here isn't the LLM, any sufficiently capable model can read an alert and write a narrative. The hard parts are:

Integration with legacy banking systems. Banks run on core systems that were built in the 1980s and 1990s. Getting data out of COBOL-era mainframes, normalizing it across dozens of different data schemas, and reliably ingesting it into a modern AI pipeline is a significant engineering effort. Fenrock needs a different integration layer for every major TMS vendor, every core banking system, every data warehouse configuration. This is the kind of work that takes years to get right.

Bulletproof audit trails. Regulators don't just want to know what decision was made, they want to know exactly what information was available at the time the decision was made, what the agent "thought" (its reasoning), and whether a human reviewed and approved the output. Fenrock logs every action the agent takes, the inputs it used, the model version it ran, and the human who signed off. That audit log is append-only and immutable, a requirement for regulatory credibility.

Policy grounding. Every bank has its own compliance policies, risk appetite statements, and standard operating procedures. The agent needs to apply these consistently, not just generic AML knowledge. Fenrock builds a retrieval system over each bank's policy library so agents can cite the specific policy that governs a decision, not just wave at general best practices.

Human-in-the-loop design. This is not a fully autonomous system. Every significant decision, closing an alert, filing a SAR, making a disposition on a case, requires analyst approval. The agent prepares; the human decides. This design isn't just a product choice; it's a regulatory necessity. Fully autonomous AML decisions without human review don't fly with FFIEC examiners.

The stack is likely a modern cloud deployment (AWS or Azure, banks are suspicious of GCP for compliance reasons) with a PostgreSQL database for case management, vector search for policy retrieval, and a queue-based worker architecture for async agent processing. SOC 2 Type II certification is table stakes to even have the bank procurement conversation.

The Founders

This is where the story gets interesting. Charu Sharma, CEO, previously founded a healthcare API company backed by General Catalyst, scaling it to 6 million patients and over 100 employees. Healthcare compliance and banking compliance share DNA: both are regulated industries where data sensitivity is extreme, integration with legacy systems is mandatory, and a mistake doesn't just cost money, it has regulatory and reputational consequences. That experience is directly transferable.

Michael, CTO, built Apple's first privacy-preserving machine learning system at scale, the technology used by billions of devices worldwide. He invented techniques for training ML models without exposing private data. In an industry where banks cannot send customer data to a third-party LLM without a regulatory framework in place, that background is directly relevant to how Fenrock structures its data handling.

These aren't two ex-bankers who watched the compliance problem and decided to solve it. They're operators who've navigated regulated industries at scale and built systems that handle sensitive data under institutional scrutiny. That credibility matters enormously when you're trying to get a Chief Compliance Officer to give you access to live transaction data.

Difficulty Score

LayerScoreWhy
ML / AI7 / 10LLMs for structured compliance work are technically achievable, but the combination of extended reasoning, tool use, and policy grounding at production quality is genuinely hard
Data8 / 10Getting clean, normalized data out of a bank's legacy core systems is a multi-month integration project per customer; proprietary training data compounds over time
Backend7 / 10Immutable audit trails, queue-based agent orchestration, multi-tenant isolation, and vendor-specific integration adapters are non-trivial
Frontend4 / 10Compliance workflow UI is boring but functional, think case management dashboards and document review UIs
DevOps6 / 10SOC 2, data residency, on-premise deployment options, SIEM integration, compliance requirements make the infra more expensive and complex than typical SaaS

The Moat (And What's Actually Hard to Replicate)

Here's the honest version: the underlying AI is not a moat. Any competent AI engineer can hook up a capable LLM to alert data and get something that superficially resembles what Fenrock does. The moat is elsewhere:

Regulatory trust takes years to build. Banks don't onboard new compliance vendors casually. There's a vendor due diligence process (expect a 200-question security questionnaire), legal review, IT security review, CCO sign-off, and potentially a proof-of-concept period before any production traffic flows. Every bank that goes through this process and doesn't have a terrible experience becomes a reference customer that unlocks the next sale. This takes time to accumulate and is very sticky, switching compliance vendors mid-year is a nightmare nobody wants.

Integration depth is a switching cost. The more deeply Fenrock's agents are embedded in a bank's workflow, integrated with their TMS, their case management system, their regulatory reporting tools, the harder they are to remove. Integration work is expensive, and nobody rips out a compliance vendor that's passing exams.

Proprietary training signal. Every case Fenrock processes where an analyst agrees or disagrees with the agent's assessment is a training signal. A year of this data across multiple banks creates a fine-tuning dataset for financial crime patterns that a new entrant can't replicate. This advantage compounds, slowly but meaningfully.

What's easy to replicate: The prompt engineering, the RAG pipeline over policies, the basic agent orchestration, the SAR narrative generation. A well-resourced competitor with banking relationships could build these in 6 months. The hard part isn't the technology, it's getting the first five banks to trust you with live transaction data.

Replicability Score: 62 / 100

This is a 62, not a 40. The technology is genuinely buildable by a strong team. The AI pieces, agents, RAG, audit logging, are well-understood. The integration work is hard but not proprietary. What pushes this above 50 is the regulatory trust moat and the first-mover data advantage. But the ceiling isn't 90, a well-capitalized entrant with banking relationships could replicate most of what Fenrock does within 18 months. The real moat is the customer relationships, not the code.

The window for Fenrock is to get deep enough into enough banks that the switching cost becomes prohibitive before a bank's own compliance vendor (NICE Actimize, Oracle) rolls out "AI agents" features and neutralizes the differentiation. That's a race Fenrock knows it's running.

What This Means for Compliance Teams

The most interesting thing about Fenrock isn't the technology, it's the organizational claim. If agents can genuinely handle 10, 20x more alerts per analyst, a bank that currently employs 500 AML analysts to process its alert volume can theoretically achieve the same throughput with 50. That's not a 10% productivity improvement; that's a fundamental restructuring of the compliance cost center.

Whether banks actually want to shrink their compliance headcount, or whether they'll use the productivity gain to handle the increasing volume of AI-generated fraud, remains to be seen. AI fraud (synthetic identities, deepfake-enabled account takeovers, LLM-generated phishing at scale) is growing faster than compliance teams can hire. Fenrock's case is that the alert volume is about to get dramatically worse, and the only way to keep up is to automate the triage work that shouldn't require humans in the first place.

That argument is hard to dispute. The challenge is convincing banks to trust an AI agent with the decisions that keep them out of regulatory trouble. That's a sales problem as much as a technology problem, and it's one where the founders' track records in regulated industries give them a genuine edge over a typical AI startup coming in cold.

© 2026 StartupHub.ai. All rights reserved. Do not enter, scrape, copy, reproduce, or republish this article in whole or in part. Use as input to AI training, fine-tuning, retrieval-augmented generation, or any machine-learning system is prohibited without written license. Substantially-similar derivative works will be pursued to the fullest extent of applicable copyright, database, and computer-misuse laws. See our terms.

Build This Startup with Claude Code

Complete replication guide — install as a slash command or rules file

# How to Build a Financial Crime Compliance AI Agent Platform (Fenrock AI Clone)

A step-by-step guide for developers using Claude Code to build a financial crime compliance automation system, covering alert triage, case investigation, SAR drafting, and audit trails.

---

## Step 1: Define the Data Model and Core Schema

Start with the database schema for your compliance platform.

**PostgreSQL schema:**

```sql
-- Core entities
CREATE TABLE alerts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  external_id TEXT NOT NULL,                    -- from bank's transaction monitoring system
  alert_type TEXT NOT NULL,                      -- 'aml', 'fraud', 'sanctions', 'pep'
  priority TEXT NOT NULL DEFAULT 'medium',       -- 'low', 'medium', 'high', 'critical'
  status TEXT NOT NULL DEFAULT 'open',           -- 'open', 'in_review', 'closed', 'escalated'
  raw_data JSONB NOT NULL,                       -- original alert payload from TMS
  customer_id TEXT,
  account_id TEXT,
  transaction_ids TEXT[],
  assigned_to UUID REFERENCES analysts(id),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE cases (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  alert_ids UUID[],
  case_type TEXT NOT NULL,                       -- 'sar_filing', 'edd', 'kyc_review', etc.
  status TEXT NOT NULL DEFAULT 'open',
  risk_score INTEGER,                             -- 0-100
  disposition TEXT,                               -- 'file_sar', 'no_action', 'monitor'
  narrative TEXT,                                 -- AI-generated investigation narrative
  analyst_notes TEXT,
  regulatory_deadline TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE audit_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  entity_type TEXT NOT NULL,                     -- 'alert', 'case', 'sar'
  entity_id UUID NOT NULL,
  action TEXT NOT NULL,                          -- 'created', 'updated', 'agent_analyzed', etc.
  actor_type TEXT NOT NULL,                      -- 'human', 'agent'
  actor_id TEXT NOT NULL,
  before_state JSONB,
  after_state JSONB,
  reasoning TEXT,                                -- why the agent took this action
  model_id TEXT,                                 -- which LLM version was used
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE sars (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  case_id UUID REFERENCES cases(id),
  status TEXT NOT NULL DEFAULT 'draft',
  fincen_fields JSONB,                           -- BSA/FinCEN form fields
  narrative TEXT,
  filed_at TIMESTAMPTZ,
  fincen_confirmation TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE policies (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  content TEXT NOT NULL,                         -- full policy text
  embedding VECTOR(1536),                        -- for semantic search
  version INTEGER DEFAULT 1,
  effective_date DATE,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
```

---

## Step 2: Build the Integration Layer (Inbound Alerts)

Banks won't give you direct DB access. You'll get alerts via API push or file ingestion.

**Ingestion service (Node.js/TypeScript):**

```typescript
// src/ingestion/alert-ingestor.ts
import { normalize } from './normalizers';

export async function ingestAlert(rawAlert: unknown, source: string) {
  // Normalize from bank-specific format to canonical schema
  const normalized = await normalize(rawAlert, source);
  
  // Deduplicate: banks sometimes send the same alert twice
  const existing = await db.alerts.findFirst({
    where: { external_id: normalized.externalId, source }
  });
  if (existing) return { status: 'duplicate', id: existing.id };
  
  const alert = await db.alerts.create({ data: normalized });
  
  // Immediately queue for AI triage
  await queue.add('triage-alert', { alertId: alert.id }, {
    priority: priorityToNumber(normalized.priority)
  });
  
  await auditLog('alert', alert.id, 'created', 'system', 'ingestion', null, alert);
  return { status: 'created', id: alert.id };
}

// Per-bank normalizers: NICE Actimize, Oracle FCCM, SAS AML
// each output slightly different JSON shapes
export function normalize(raw: unknown, source: string) {
  const normalizers: Record<string, Normalizer> = {
    'actimize': actimizeNormalizer,
    'oracle_fccm': oracleFCCMNormalizer,
    'sas_aml': sasAMLNormalizer,
    'generic_webhook': genericWebhookNormalizer,
  };
  return (normalizers[source] ?? genericWebhookNormalizer)(raw);
}
```

**Key design principle**: Keep your canonical schema independent from any specific TMS vendor. Banks switch systems; you should not.

---

## Step 3: Build the Alert Triage Agent

This is the core AI loop, classify incoming alerts and collect the evidence an analyst needs.

```typescript
// src/agents/triage-agent.ts
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

export async function triageAlert(alertId: string) {
  const alert = await db.alerts.findUnique({ where: { id: alertId } });
  const customer = await enrichCustomerData(alert.customerId);
  const relatedAlerts = await getRelatedAlerts(alert.customerId, 90); // last 90 days
  const policies = await getRelevantPolicies(alert.alertType);
  
  const systemPrompt = `You are a financial crime compliance analyst. Your job is to triage AML and fraud alerts.

For each alert, you must:
1. Assess the risk level (low/medium/high/critical)
2. Identify what additional data points are needed
3. Determine if this should be escalated to a full case investigation
4. Provide a brief rationale for your assessment

You have access to:
- Customer profile and transaction history
- Related alerts from the past 90 days
- Relevant bank policies and typologies

Always err on the side of caution in a regulated environment. Your reasoning will be audited.`;

  const messages = [
    {
      role: 'user' as const,
      content: `Triage this alert:

Alert Type: ${alert.alertType}
Alert Data: ${JSON.stringify(alert.rawData, null, 2)}

Customer Profile: ${JSON.stringify(customer, null, 2)}

Related Alerts (last 90 days): ${JSON.stringify(relatedAlerts, null, 2)}

Relevant Policies:
${policies.map(p => `### ${p.name}\n${p.content}`).join('\n\n')}`
    }
  ];

  // Use extended thinking for complex cases
  const response = await client.messages.create({
    model: 'claude-opus-4-7',
    max_tokens: 16000,
    thinking: {
      type: 'enabled',
      budget_tokens: 10000
    },
    system: systemPrompt,
    messages,
  });

  const result = parseTriageResponse(response);
  
  await db.alerts.update({
    where: { id: alertId },
    data: {
      priority: result.riskLevel,
      status: result.shouldEscalate ? 'escalated' : 'triaged',
    }
  });
  
  await auditLog('alert', alertId, 'agent_triage', 'agent', 'triage-agent-v1', 
    { status: 'open' }, 
    { priority: result.riskLevel, reasoning: result.rationale },
    result.rationale
  );

  if (result.shouldEscalate) {
    await queue.add('investigate-case', { alertId, triageResult: result });
  }
  
  return result;
}
```

---

## Step 4: Build the Case Investigation Agent

When an alert gets escalated, the investigation agent digs deeper, pulling transaction graphs, running pattern matching, and building the narrative.

```typescript
// src/agents/investigation-agent.ts
export async function investigateCase(caseId: string) {
  const caseData = await db.cases.findUnique({
    where: { id: caseId },
    include: { alerts: true }
  });
  
  // Build transaction graph for the customer
  const txGraph = await buildTransactionGraph(caseData.customerId, {
    lookbackDays: 180,
    includeCounterparties: true,
    maxDepth: 2  // counterparties of counterparties
  });
  
  // Enrich with external data
  const [watchlistHits, adverseMedia, entityData] = await Promise.all([
    checkWatchlists(caseData.customerId),      // OFAC, EU, UN sanctions lists
    searchAdverseMedia(caseData.customerId),   // news APIs for negative press
    enrichEntityData(caseData.customerId),      // business registry, beneficial ownership
  ]);
  
  const tools = [
    {
      name: 'flag_for_sar',
      description: 'Flag this case for SAR filing',
      input_schema: {
        type: 'object' as const,
        properties: {
          reason: { type: 'string' },
          suspected_activity_type: { type: 'string' },
          amount_involved: { type: 'number' },
        },
        required: ['reason', 'suspected_activity_type'],
      }
    },
    {
      name: 'request_additional_info',
      description: 'Request additional information from the customer or internal teams',
      input_schema: {
        type: 'object' as const,
        properties: {
          information_needed: { type: 'string' },
          urgency: { type: 'string', enum: ['low', 'medium', 'high'] },
        },
        required: ['information_needed'],
      }
    },
    {
      name: 'close_no_action',
      description: 'Close the case with no further action',
      input_schema: {
        type: 'object' as const,
        properties: {
          rationale: { type: 'string' },
        },
        required: ['rationale'],
      }
    }
  ];
  
  const response = await client.messages.create({
    model: 'claude-opus-4-7',
    max_tokens: 8000,
    system: INVESTIGATION_SYSTEM_PROMPT,
    tools,
    messages: [buildInvestigationPrompt(caseData, txGraph, watchlistHits, adverseMedia, entityData)],
  });
  
  // Handle tool calls
  for (const block of response.content) {
    if (block.type === 'tool_use') {
      await handleInvestigationToolCall(caseId, block);
    }
  }
}
```

---

## Step 5: Build the SAR Drafting Agent

Suspicious Activity Reports follow a rigid FinCEN format. The agent drafts the narrative section, the hardest part.

```typescript
// src/agents/sar-drafter.ts
export async function draftSAR(caseId: string) {
  const caseData = await db.cases.findUnique({
    where: { id: caseId },
    include: { alerts: true }
  });
  
  // FinCEN Form 114 / BSA E-Filing fields
  const sarTemplate = {
    part_i_subject_info: await extractSubjectInfo(caseData),
    part_ii_suspicious_activity: await extractSuspiciousActivityType(caseData),
    part_iii_financial_institutions: await getInstitutionInfo(),
    part_iv_narrative: '',  // AI-generated
    part_v_contact: await getFilingContactInfo(),
  };
  
  const narrativeResponse = await client.messages.create({
    model: 'claude-opus-4-7',
    max_tokens: 4000,
    system: `You are drafting the narrative section of a Suspicious Activity Report (SAR) 
for FinCEN. The narrative must:
- Describe WHO is involved, WHAT happened, WHEN, WHERE, WHY it's suspicious, and HOW
- Be written in plain past tense
- Reference specific transaction dates, amounts, and account numbers
- Cite the typology or red flag that triggered the alert
- Avoid speculative language ("we believe", "possibly")
- Be 1-2 pages maximum
- Follow FinCEN guidance on SAR narratives (March 2012 advisory)`,
    messages: [{
      role: 'user',
      content: `Draft the SAR narrative for this case:\n\n${JSON.stringify(caseData, null, 2)}`
    }]
  });
  
  sarTemplate.part_iv_narrative = extractText(narrativeResponse);
  
  const sar = await db.sars.create({
    data: {
      caseId,
      status: 'draft_pending_review',
      fincen_fields: sarTemplate,
      narrative: sarTemplate.part_iv_narrative,
    }
  });
  
  await auditLog('sar', sar.id, 'agent_drafted', 'agent', 'sar-drafter-v1', null, sar, 
    'Agent drafted SAR narrative based on case investigation');
  
  return sar;
}
```

---

## Step 6: Build the Audit Trail and Compliance Infrastructure

Every action, human or AI, must be logged immutably. Regulators will ask for this.

```typescript
// src/compliance/audit.ts
export async function auditLog(
  entityType: string,
  entityId: string,
  action: string,
  actorType: 'human' | 'agent',
  actorId: string,
  beforeState: unknown,
  afterState: unknown,
  reasoning?: string
) {
  // Write to immutable append-only table
  await db.$executeRaw`
    INSERT INTO audit_log (entity_type, entity_id, action, actor_type, actor_id, 
                           before_state, after_state, reasoning, model_id, created_at)
    VALUES (${entityType}, ${entityId}, ${action}, ${actorType}, ${actorId},
            ${JSON.stringify(beforeState)}::jsonb, ${JSON.stringify(afterState)}::jsonb,
            ${reasoning}, ${'claude-opus-4-7'}, NOW())
  `;
  // Note: no UPDATE/DELETE permissions on audit_log for any app user
}

// Compliance report generation
export async function generateExamReport(startDate: Date, endDate: Date) {
  const stats = await db.$queryRaw`
    SELECT 
      COUNT(*) FILTER (WHERE actor_type = 'agent') as agent_actions,
      COUNT(*) FILTER (WHERE actor_type = 'human') as human_actions,
      COUNT(DISTINCT entity_id) FILTER (WHERE action = 'agent_triage') as alerts_triaged,
      COUNT(DISTINCT entity_id) FILTER (WHERE action = 'agent_drafted') as sars_drafted,
      AVG(EXTRACT(EPOCH FROM (
        (SELECT created_at FROM audit_log a2 WHERE a2.entity_id = a1.entity_id 
         AND a2.action = 'closed' LIMIT 1) - a1.created_at
      ))/3600) as avg_resolution_hours
    FROM audit_log a1
    WHERE created_at BETWEEN ${startDate} AND ${endDate}
  `;
  return stats;
}
```

**Deployment note**: Run your audit log DB on separate infrastructure from your main app DB. Give the app DB user INSERT-only rights on audit_log. Regulators want evidence that logs can't be tampered with after the fact.

---

## Step 7: Deploy with SOC 2 Compliance in Mind

Banks will not onboard you without a SOC 2 Type II report. Plan for this from day one.

**Infrastructure checklist:**

```yaml
# docker-compose.prod.yml (simplified)
services:
  api:
    image: fenrock-api:latest
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
    # Banks may require on-prem or VPC deployment
    # Consider AWS GovCloud or Azure Government for US bank customers
    
  worker:
    image: fenrock-worker:latest
    # Queue workers for async agent processing
    
  audit-db:
    image: postgres:16
    # Separate DB for audit logs, write-only for app
```

**Security requirements for bank customers:**
- End-to-end encryption at rest (AES-256) and in transit (TLS 1.3+)
- Data residency guarantees (no data leaving the country)
- Role-based access control with MFA
- Penetration testing every 6 months
- Disaster recovery with RTO < 4 hours
- Vendor due diligence questionnaire (banks will send you a 200-question spreadsheet)

**Integration patterns:**
- REST API webhooks (most common for initial integration)
- SFTP batch file drops (for legacy bank systems, yes, still common in 2026)
- MQ/Kafka event streaming (for real-time integration with modern bank stacks)
- Secure VPN or AWS Direct Connect for on-premise deployments

**Recommended stack:**
- API: Node.js/TypeScript + Fastify (or Python + FastAPI)
- Queue: BullMQ or Temporal for durable workflows
- DB: PostgreSQL with pgvector for policy embeddings
- LLM: Anthropic Claude Opus 4.7 (claude-opus-4-7) for investigation; Haiku 4.5 for triage pre-filtering
- Deployment: AWS or Azure (banks prefer these over GCP for compliance reasons)
- Monitoring: Datadog or Splunk (banks often require Splunk for SIEM integration)
claude-code-skills.md