Overview
Nova provides an interactive environment where users can:- Learn prompt engineering through structured problems
- Practice against problem test cases (some public, some hidden)
- Compete in timed, attempt-limited challenges
- Submit prompts for automated, criteria-based evaluation
- Track progress through sessions and leaderboards (individual and team)
Nova is a standalone satellite app that lives at
apps/nova, not inside
apps/web. It runs on port 7805 locally (nova.tuturuuu via Portless) and
delegates authentication to apps/web through the shared cross-app login
handoff, the same satellite pattern used by
mail, learn,
teach, mind,
hive, and tasks.Architecture / Satellite Auth
Nova is a Next.js App Router app underapps/nova that owns the prompt
engineering UI and its own /api/v1/* route handlers. Authentication is
delegated to apps/web, and Nova-specific data lives in the shared Supabase
project under the private schema, accessed with the service-role admin client.
Cross-app login handoff
apps/nova/src/proxy.tsruns a centralized auth proxy created withcreateCentralizedAuthProxy({ targetApp: 'nova', sessionMode: 'supabase-first', mfa: { enabled: false } }). MFA is disabled in the satellite becauseapps/webalready enforcesaal2before issuing a cross-app token.- For browser navigation, the proxy first calls
consumeVerifyTokenRequest, which handles the/verify-tokenhandoff so a local Portless login can set cookies onnova.tuturuuu.localhost. It then runs the auth proxy and locale handling. POST /api/auth/verify-app-tokenis generated bycreatePOST('nova', { verificationBaseUrl: TTR_URL })from@tuturuuu/auth/cross-app/server. It exchanges a one-time cross-app token fromapps/webfor Nova’s local app-session.- Token refresh stays same-origin through
POST /api/auth/refresh-app-session; the proxy refreshes the app session for/api/*requests viarefreshAppSessionForRequestand clears Supabase auth cookies on failure.
App-session helpers
apps/nova/src/lib/app-session.ts is the single source of truth for resolving
the current Nova user and their platform role:
getNovaAppSessionUserFromRequest(request)resolves the app-session user from aRequestinside a route handler (targetApp: 'nova').getNovaAppSessionUserFromHeaders()/requireNovaAppSessionUser()resolve the user in Server Components and redirect to/loginwhen absent.getNovaPlatformRole(userId, sbAdmin?)reads the caller’s row from the sharedplatform_user_rolestable (enabled,allow_challenge_management,allow_manage_all_challenges,allow_role_management).requireNovaEnabledRole(user)redirects to/not-whitelistedwhen the role is not enabled.
Database Schema
All Nova tables live in theprivate schema of the shared Supabase project and
are reachable only through the service-role admin client
(createAdminClient({ noCookie: true })). The shapes below are derived from the
generated types in packages/types/src/supabase.ts — treat that file as the
source of truth and never hand-edit generated DB types.
Challenges and problems
A challenge is the top-level unit. Problems belong to a challenge (nova_problems.challenge_id), and test cases belong to a problem
(nova_problem_test_cases.problem_id). Note the relationship direction:
problems reference their parent challenge, not the other way around.
nova_challenges
nova_problems
nova_problem_test_cases
nova_challenge_criteria
Free-form scoring criteria attached to a challenge. Submissions are graded
against these criteria.
Sessions and submissions
nova_sessions
A timed attempt at a challenge.
nova_submissions
A submitted prompt for a problem. The aggregate score is not stored on this
row; per-criterion and per-test-case results live in child tables (see below)
and are aggregated by the nova_submissions_with_scores view.
nova_submission_criteria
Per-criterion grading results for a submission.
nova_submission_test_cases
Per-test-case results for a submission, including whether the model output
matched and the grader’s confidence/reasoning.
Teams
Nova supports team-based participation through three tables (not a single “team management” table):Access control tables
Per-challenge whitelisting and manager assignment are keyed by email:nova_* roles
table. They come from the shared public.platform_user_roles table (enabled,
allow_challenge_management, allow_manage_all_challenges,
allow_role_management), exposed to Nova through
getNovaPlatformRole(). The is_nova_role_manager() RPC reflects the
allow_role_management flag.
Data Access Patterns
Nova route handlers follow a consistent shape: resolve the app-session user, authorize against the caller’s platform role and (for management writes) the challenge they are touching, then read or write theprivate schema with the
admin client.
Listing problems for a challenge
Creating a problem (authorized write)
A problem belongs to a challenge, so the caller must be allowed to manage that specific challenge before the service-role insert runs.Supabase client constructors from
@tuturuuu/supabase/next/server differ:
createClient() and createDynamicClient() are async (await them), while
createAdminClient() is the sync admin client. Nova’s { noCookie: true }
admin entrypoint is awaited as shown above.Authorization Helpers
apps/nova/src/lib/challenge-management-auth.ts centralizes management checks so
service-role writes are never gated by app-session presence alone:
canManageNovaChallenge(user, challengeId, sbAdmin?)— true when the caller can manage all challenges, or hasallow_challenge_managementand is listed innova_challenge_manager_emailsfor that challenge.canManageNovaChallengesGlobally(user, sbAdmin?)— true when the caller can manage all challenges (allow_manage_all_challengesorallow_role_management).canManageNovaRolesGlobally(user, sbAdmin?)— true when the caller hasallow_role_management.- Synchronous predicates
canManageNovaChallenges,canManageAllNovaChallenges, andcanManageNovaRolesevaluate an already-fetchedNovaPlatformRole. - Resolver helpers
getNovaProblemChallengeId,getNovaTestCaseChallengeId, andgetNovaCriterionChallengeIdwalk child rows back to their owning challenge so a singlecanManageNovaChallengecheck can guard nested mutations.
Best Practices
✅ DO
-
Authorize service-role writes explicitly.
getNovaAppSessionUserFromRequest()only proves a valid Nova app-session. Any route that mutates Nova management data throughcreateAdminClient({ noCookie: true })must also check the relevantplatform_user_rolespermission before the write, using the shared helpers inapps/nova/src/lib/challenge-management-auth.ts(canManageNovaChallenge(),canManageNovaChallengesGlobally(),canManageNovaRolesGlobally()). For nested resources (problems, test cases, criteria), resolve the owningchallenge_idfirst via the resolver helpers and then authorize against that challenge. -
Keep hidden test cases hidden. Never return
nova_problem_test_casesrows wherehidden = trueto participants. Filter on the server with.eq('hidden', false)for participant-facing reads. -
Validate challenge timing and attempt limits. Respect
open_at,close_at,previewable_at,max_attempts, andmax_daily_attemptsbefore accepting a submission or starting a session. -
Honor whitelisting. When
whitelisted_onlyis set, only admit emails present innova_challenge_whitelisted_emails. -
Read scores from the aggregate view. Use
nova_submissions_with_scoresinstead of recomputing fromnova_submission_criteria/nova_submission_test_casesin application code.
❌ DON’T
-
Don’t add a Nova tRPC router or
@/lib/novaserver-action layer. Nova has neither; product data flows through the Nova/api/v1/*routes andpackages/internal-apihelpers. -
Don’t read Nova tables directly from the client. All
private.nova_*access goes through service-role route handlers after authorization. -
Don’t invent score columns on
nova_submissions. Scores are derived from the per-criterion and per-test-case child tables.
Related Documentation
- TanStack + Rust migration - the broader move away from
apps/web - Mail satellite app - the canonical satellite auth pattern
- Authentication - cross-app login and sessions
- Database overview - shared schema conventions
Future Enhancements
- Richer team challenges - deeper collaborative prompt engineering flows
- Live leaderboards - real-time ranking surfaced from the leaderboard views
- Prompt history - version control for participant submissions
- Advanced metrics - token usage and latency analysis per submission
- Custom evaluation - configurable, criterion-weighted scoring functions