Lucas Pun

Independent full-stack AI / AWS platform

Clovor

An AI interview and career platform with voice sessions, asynchronous analysis on AWS, and tenant isolation enforced in IAM.

Independent project · Built end to end

Stack

  • Next.js
  • React
  • TypeScript
  • Clerk
  • AWS DynamoDB
  • AWS Lambda
  • AWS Step Functions
  • AWS IAM / STS
  • AWS Secrets Manager
  • Docker
  • Fly.io
  • GitHub Actions
  • OpenAI
  • Gemini
  • ElevenLabs
  • Vitest

Overview

Clovor is an independent full-stack AI interview and career platform: a spoken mock-interview companion that offers warm, usable suggestions instead of scores, plus a chat for talking a job search through.

Users take real-time voice interviews with personalized question generation, and the platform analyses their answers asynchronously and produces a report. It also includes AI-assisted career tools.

It combines a Next.js application, authenticated server APIs and several AWS services, with multiple AI providers behind structured, validated outputs.

Clovor demo A mock interview in which the AI interviewer greets the candidate, asks about their background, listens to the answer, and decides when to move on to the next question.

Product and problem

Job-seekers, especially newcomers to a country, often find interview practice stressful, and one pre-launch tester said a low score from another app left them down for days. I built Clovor to offer warm, usable suggestions instead of scores.

The engineering problems are the ones any multi-user AI product meets: keeping one user's data strictly separate from another's, running slow AI analysis without blocking the request, and turning free-form model output into data the rest of the system can trust.

My role

Clovor is an independent project. I built it end to end, from the interface to the AWS infrastructure and delivery pipeline.

What I worked on

  • Next.js, React and TypeScript application and the authenticated server APIs behind it, including real-time voice interviews, personalized question generation and AI-assisted career tools.
  • Authentication with Clerk and authorization enforced down to the data layer.
  • Defense-in-depth multi-tenant isolation on DynamoDB, using short-lived AWS credentials and IAM-enforced data access.
  • Asynchronous interview analysis with AWS Step Functions and Lambda.
  • Structured LLM outputs with schema validation, retries and fallbacks across OpenAI and Gemini.
  • Voice interview sessions using ElevenLabs.
  • Docker and GitHub Actions CI/CD with AWS deployments authenticated through OIDC, and automated tests with Vitest.

Key engineering challenges

  1. Isolating tenants in the database, not just in application code

    Problem
    In a multi-tenant app, one missing filter in application code can expose one user's data to another. Relying on every query being written correctly is a fragile guarantee.
    Approach
    Defense-in-depth tenant isolation. The server verifies the caller's identity, then accesses data with short-lived AWS credentials scoped to that tenant, so authorization is enforced by IAM below the application layer as well as in code.
  2. Analysing interviews without blocking the request

    Problem
    AI analysis of an interview is slow and made of several parts. Running it inside a web request would tie up the request and make failures hard to handle.
    Approach
    Analysis runs asynchronously: AWS Step Functions coordinates parallel Lambda analysis steps and then persists the report. The application does not wait on the analysis inside the request.
  3. Making LLM output safe to build on

    Problem
    Language models return free-form text, and providers can fail or return output that does not fit the expected shape.
    Approach
    Model calls request structured output and validate it against a schema. Failures are handled with retries and fallbacks, across more than one provider (OpenAI and Gemini).

Architecture

Three views: the overall request path, the layers of tenant isolation, and the asynchronous analysis pipeline. All are simplified and sanitized: no account identifiers, resource names or policy documents.

System overview
  1. BrowserReact interface
  2. ApplicationNext.js appContainerized with DockerAuthenticated server APIsClerk identity
  3. AWSDynamoDBTenant dataStep Functions → LambdaAsynchronous analysis
  4. External servicesElevenLabsVoiceOpenAI · GeminiStructured LLM outputs

Which component calls which provider is intentionally omitted.

Tenant isolation layers
  1. IdentityClerkAuthenticated user
  2. ApplicationServer-side authorizationChecked in code
  3. CredentialsSTSShort-lived, tenant-scoped
  4. DataIAM → DynamoDBEnforced below the application

Simplified: enforcement details are intentionally omitted.

Asynchronous analysis pipeline
  1. TriggerServer APIStarts the workflow
  2. OrchestrationStep Functions
  3. AnalysisLambdaParallel analysis steps
  4. ResultReport persistence

Selected technical decisions

  1. Enforce tenant isolation in IAM as well as in code

    Decision
    Requests use short-lived, tenant-scoped credentials, so IAM and the database layer enforce isolation independently of the application code.
    Trade-off
    Every request path needs the credential-scoping step, and access-denied errors are harder to debug than an application-level filter. In exchange, a bug in query code cannot on its own expose another tenant's data.
  2. Use Step Functions for analysis orchestration

    Decision
    A state machine coordinates parallel Lambda analysis steps and report persistence instead of running analysis inside the request.
    Trade-off
    It adds another AWS service and a workflow definition to maintain. It also gives built-in support for parallel branches, retries and failure handling.
  3. Validate structured LLM output and fall back

    Decision
    Model responses must match a schema before the rest of the system uses them, with retries and provider fallback when they do not.
    Trade-off
    Retries and fallbacks can add latency and cost on failure. The rest of the codebase gets typed, trustworthy data.
  4. Authenticate CI to AWS with OIDC

    Decision
    GitHub Actions obtains AWS access through OIDC rather than stored access keys.
    Trade-off
    The IAM trust setup takes more effort, but there is no long-lived AWS credential to leak or rotate.

Production and delivery

  • Containerized application deployed through GitHub Actions, with AWS deployments authenticated through OIDC and secrets managed outside source control. No long-lived AWS keys are stored.
  • CI runs install, lint, tests and build on every pull request and push to main, and deployment happens only from main after CI passes.
  • Automated tests run with Vitest: 348 passing tests at the audited point in time.
  • The analysis workflow uses retries with backoff and fallback handling, and model prompt logging is kept out of production.
  • Development used AI coding assistance (Claude Code).

Results

passing automated testsAt the audited point in time; not updated automatically
348
  • No user or usage metrics are claimed for Clovor. The evidence is the architecture and the test suite.

What I would improve

  • Simplify the voice integration into one supported path, with the documentation kept in step with the code.
  • Expand API and UI integration-test coverage, starting with the flows users depend on most.
  • Add privacy-safe production observability around AI latency and fallback rates, without recording message content.