From 051a31f41d609280342e98b5bfee9d46def1a9b7 Mon Sep 17 00:00:00 2001 From: Shashank Date: Sun, 29 Mar 2026 04:40:18 +0530 Subject: [PATCH] feat: The Chaos Engineering features are actively being implemented - AI's backend logic to the frontend to trigger these mid-interview disruptions --- app/api/interview/[id]/hint/route.ts | 133 +++++++++++++---------- app/interview/[id]/page.tsx | 2 + components/interview/InterviewHeader.tsx | 23 +++- 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/app/api/interview/[id]/hint/route.ts b/app/api/interview/[id]/hint/route.ts index bff1ed9..14b8b97 100644 --- a/app/api/interview/[id]/hint/route.ts +++ b/app/api/interview/[id]/hint/route.ts @@ -24,67 +24,83 @@ interface ConstraintTriggerSession { timeLimit: number; } +interface ConstraintCandidateContext { + prompt: string; + difficulty: 'easy' | 'medium' | 'hard'; + introducedAtMinute: number; + nodes: ICanvasNode[]; + connections: IConnection[]; + failedRuleMessages: string[]; +} + const LIVE_CHANGE_MIN_PROGRESS = 0.25; const LIVE_CHANGE_MAX_PROGRESS = 0.75; const LIVE_CHANGE_MIN_NODES = 3; function generateConstraintChange( - prompt: string, - difficulty: 'easy' | 'medium' | 'hard', - introducedAtMinute: number + { + prompt, + difficulty, + introducedAtMinute, + nodes, + connections, + failedRuleMessages + }: ConstraintCandidateContext ): IConstraintChange { const lowerPrompt = prompt.toLowerCase(); - - const templates: Array> = []; - - if (lowerPrompt.includes('chat') || lowerPrompt.includes('notification') || lowerPrompt.includes('feed') || lowerPrompt.includes('stream')) { - templates.push({ + const nodeTypes = new Set(nodes.map((node) => node.type)); + const connectionCount = connections.length; + const hasCache = nodeTypes.has('Cache') || nodeTypes.has('CDN'); + const hasQueue = nodeTypes.has('Queue') || nodeTypes.has('Kafka'); + const databaseCount = nodes.filter((node) => node.type === 'SQL' || node.type === 'Blob').length; + const hasRealtimePrompt = ['chat', 'notification', 'feed', 'stream', 'collaborative'].some((keyword) => lowerPrompt.includes(keyword)); + const hasFinancialPrompt = ['payment', 'trade', 'order'].some((keyword) => lowerPrompt.includes(keyword)); + const mentionsDisconnected = failedRuleMessages.some((message) => message.toLowerCase().includes('not connected') || message.toLowerCase().includes('floating')); + + let selected: Omit; + + if (!hasCache && (hasRealtimePrompt || nodeTypes.has('LB') || connectionCount >= 3)) { + selected = { type: 'traffic', title: 'Traffic Spike', description: 'Peak traffic is now expected to spike to roughly 10x the original estimate during major live events.', severity: 'high', impactAreas: ['scalability', 'caching', 'load balancing'], - }); - } - - if (lowerPrompt.includes('payment') || lowerPrompt.includes('trade') || lowerPrompt.includes('order')) { - templates.push({ + }; + } else if ((databaseCount <= 1 || mentionsDisconnected) && difficulty !== 'easy') { + selected = { + type: 'reliability', + title: 'Regional Failover', + description: 'The system must continue serving users during a full regional outage with minimal disruption.', + severity: difficulty === 'hard' ? 'high' : 'moderate', + impactAreas: ['availability', 'replication', 'disaster recovery'], + }; + } else if (!hasQueue && (hasRealtimePrompt || nodeTypes.has('Server') || nodeTypes.has('Function'))) { + selected = { + type: 'product', + title: 'Real-Time Updates', + description: 'Users now expect live updates in the product instead of relying on manual refreshes or long polling.', + severity: 'moderate', + impactAreas: ['realtime', 'messaging', 'fan-out'], + }; + } else if (hasFinancialPrompt) { + selected = { type: 'latency', title: 'Stricter Write Latency', description: 'Critical write operations now need to complete within 150ms at p95 while preserving correctness.', severity: 'high', impactAreas: ['latency', 'consistency', 'storage'], - }); + }; + } else { + selected = { + type: 'compliance', + title: 'Regional Data Residency', + description: 'Data for EU users must remain in-region and cannot be freely replicated across all geographies.', + severity: 'moderate', + impactAreas: ['compliance', 'storage', 'multi-region'], + }; } - templates.push({ - type: 'reliability', - title: 'Regional Failover', - description: 'The system must continue serving users during a full regional outage with minimal disruption.', - severity: difficulty === 'hard' ? 'high' : 'moderate', - impactAreas: ['availability', 'replication', 'disaster recovery'], - }); - - templates.push({ - type: 'compliance', - title: 'Regional Data Residency', - description: 'Data for EU users must remain in-region and cannot be freely replicated across all geographies.', - severity: 'moderate', - impactAreas: ['compliance', 'storage', 'multi-region'], - }); - - templates.push({ - type: 'product', - title: 'Real-Time Updates', - description: 'Users now expect live updates in the product instead of relying on manual refreshes or long polling.', - severity: 'moderate', - impactAreas: ['realtime', 'messaging', 'fan-out'], - }); - - const selected = difficulty === 'hard' - ? templates[0] - : templates.find((template) => template.type !== 'compliance') || templates[0]; - const interviewerMessage = `Let's add a new constraint: ${selected.description} How would you adjust your design to handle that?`; return { @@ -186,14 +202,28 @@ export async function POST(request: NextRequest, { params }: RouteParams) { ? sanitizeMessage(candidateReply.trim()) : null; + const structuralResults = evaluateStructure( + nodes, + connections, + session.question.requirements || [], + session.question.constraints || [] + ); + + const failedRuleMessages = structuralResults.details + .filter((detail) => detail.status === 'fail') + .map((detail) => detail.message); + const liveChangeDecision = shouldTriggerConstraintChange(session, nodes.length); if (liveChangeDecision.shouldTrigger) { - const constraintChange = generateConstraintChange( - session.question.prompt, - session.difficulty, - liveChangeDecision.introducedAtMinute - ); + const constraintChange = generateConstraintChange({ + prompt: session.question.prompt, + difficulty: session.difficulty, + introducedAtMinute: liveChangeDecision.introducedAtMinute, + nodes, + connections, + failedRuleMessages + }); const newMessage = { role: 'interviewer' as const, @@ -224,13 +254,6 @@ export async function POST(request: NextRequest, { params }: RouteParams) { }); } - const structuralResults = evaluateStructure( - nodes, - connections, - session.question.requirements || [], - session.question.constraints || [] - ); - const formatNode = (n: ICanvasNode) => ({ type: n.type, label: n.label }); const formatConn = (c: IConnection) => { const from = nodes.find((n: ICanvasNode) => n.id === c.from); @@ -238,7 +261,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) { return { fromType: from?.type, fromLabel: from?.label, toType: to?.type, toLabel: to?.label, label: c.label }; }; - const failedRules = structuralResults.details.filter(d => d.status === 'fail').map(d => d.message); + const failedRules = failedRuleMessages; const prompt = `You are an expert systems design interviewer evaluating a candidate in a real-time system design interview. diff --git a/app/interview/[id]/page.tsx b/app/interview/[id]/page.tsx index 5933853..65abbf7 100644 --- a/app/interview/[id]/page.tsx +++ b/app/interview/[id]/page.tsx @@ -329,6 +329,8 @@ export default function InterviewCanvasPage({ params }: PageProps) {
= { export function InterviewHeader({ difficulty, + constraintChangeCount = 0, + latestConstraintTitle, saveStatus, timer, status, @@ -42,7 +46,6 @@ export function InterviewHeader({ const diffConfig = DIFFICULTY_LABELS[difficulty] || DIFFICULTY_LABELS.medium; const isInProgress = status === 'in_progress'; - const renderSaveStatus = () => { switch (saveStatus) { case 'saving': @@ -73,7 +76,6 @@ export function InterviewHeader({ return (
- {/* Left: Logo & Interview badge */}
@@ -92,15 +94,28 @@ export function InterviewHeader({
+ {constraintChangeCount > 0 && ( +
+ bolt + + Updated Requirements + + + {constraintChangeCount} + +
+ )} + {renderSaveStatus()}
- {/* Center: Timer */}
- {/* Right: Actions */}
{isInProgress && (