Skip to main content
Nova is Tuturuuu’s prompt engineering platform designed to help users learn, practice, and compete in AI prompt engineering challenges.

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.

Session integrity

Creating a Nova participant submission requires the caller’s own IN_PROGRESS session and an associated problem in that session’s challenge. The server rejects submissions at or after min(start_time + duration, close_at), including before the expiry cleanup runs. Only an enabled manager of the problem’s own challenge (resolved server-side from the problem) may submit without a session; managing a different challenge, or a disabled role, grants no bypass. A supplied session must satisfy the same ownership, challenge and expiry checks. After creation, a submission’s problem_id and session_id are immutable for every caller. Participants cannot edit, delete or grade their own submissions. Only an enabled manager of the submission’s challenge may write or clear its grading (/api/v1/submissions/{id}/criteria and /test-cases), and only on a submission they own may that manager edit the prompt or delete it. A graded criterion must belong to the submission’s challenge, and a graded test case must belong to its problem. The grading tables live in private with no authenticated grants, so the service-role routes are the only write path. The admin submission detail page applies the same scope: an enabled manager of the submission’s challenge, or the owner when the submission is bound to their own session in that challenge. Challenge password verifier fields are never sent to the client. Server actions apply their own authorization before using an admin client; rendering their caller under a protected layout is not sufficient. Whitelist creation requires an enabled global role manager. Submission detail reads require the bound session owner or an enabled manager of that challenge; only that manager may include hidden test cases. Aggregated results require the caller’s own challenge session, or an enabled challenge manager when selecting another user or reading a challenge without a session. Disabled manager roles grant no bypass. These rules cover the retained Next actions and the native Start action suppliers, without changing database grants or central sign-in. Regression coverage: src/start-bridge/action-security.test.ts. Native payload validation is covered by src/start-bridge/action-contracts.test.ts; emitted transport and browser acceptance remain separate gates. Active-session reads select the newest start. Session inserts serialize on the challenge row and reject an overlapping active session with HTTP 409. Expired rows remain as attempt history and do not prevent a new attempt. Daily attempt limits use UTC in both the API and database. Criteria retain catalog/password eligibility before open_at; starting a session still requires the opening window. Regression coverage: submissions/route.test.ts, submissions/[submissionId]/route.test.ts, submissions/[submissionId]/grading-access.test.ts, the admin submissions/[submissionId]/page.test.tsx, packages/utils/src/nova/submissions/check-permission.test.ts, challenge-catalog-access.test.ts, sessions/route.test.ts, and supabase/tests/private-schema-nova-session-limits.sql. The grant boundary is covered by private-schema-nova-runtime.sql and private-schema-nova-challenge-catalog.sql.

Architecture / Satellite Auth

Nova is a Next.js App Router app under apps/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.ts runs a centralized auth proxy created with createCentralizedAuthProxy({ targetApp: 'nova', sessionMode: 'supabase-first', mfa: { enabled: false } }). MFA is disabled in the satellite because apps/web already enforces aal2 before issuing a cross-app token.
  • For browser navigation, the proxy first calls consumeVerifyTokenRequest, which handles the /verify-token handoff so a local Portless login can set cookies on nova.tuturuuu.localhost. It then runs the auth proxy and locale handling.
  • POST /api/auth/verify-app-token is generated by createPOST('nova', { verificationBaseUrl: TTR_URL }) from @tuturuuu/auth/cross-app/server. It exchanges a one-time cross-app token from apps/web for 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 via refreshAppSessionForRequest and 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 a Request inside a route handler (targetApp: 'nova').
  • getNovaAppSessionUserFromHeaders() / requireNovaAppSessionUser() resolve the user in Server Components and redirect to /login when absent.
  • getNovaPlatformRole(userId, sbAdmin?) reads the caller’s row from the shared platform_user_roles table (enabled, allow_challenge_management, allow_manage_all_challenges, allow_role_management).
  • requireNovaEnabledRole(user) redirects to /not-whitelisted when the role is not enabled.
There is no Nova tRPC router and no Nova server-action data layer in apps/web. Nova route handlers authenticate the caller, then read and write the private.nova_* tables directly with the service-role admin client. Use packages/internal-api helpers and the Nova /api/v1/* routes for client data access; do not add direct client-side Supabase reads of Nova tables.

Database Schema

All Nova tables live in the private 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

Challenge passwords use the shared scrypt KDF from @tuturuuu/utils/crypto; new hashes embed their salt, so password_salt is null for them. Any other non-empty stored hash, including legacy salted SHA-256, is a reset marker: it never verifies, verify-password returns HTTP 409 PASSWORD_RESET_REQUIRED without a grant, and a challenge manager must set a new password. Scrypt hashes with older parameters still verify and are rehashed with an update guarded by the previous hash. See shared-secret password reset. The optional Durable Object password store remains default-off. Once assigned, each verified user and each trusted source network may spend at most 5% of the password family’s daily command share, across challenges. Denied probes and successful-login refunds consume that allowance; rotating challenges or source addresses cannot reset the user’s quota. See durable command fairness. Real-store concurrency and restart coverage lives in apps/abuse-guard/src/fairness.test.ts. After the password verifies, /api/auth/challenges/verify-password sets an httpOnly nova_challenge_access_<challengeId> cookie holding a signed grant bound to that challenge and the signed-in user, valid for the challenge duration (at most seven days). Challenge pages accept only that grant; the stored hash is never sent to the browser, and the retired token cookie is cleared and no longer grants access. Users with an open challenge from before this change re-enter the password once. Each password attempt reserves capacity atomically before verification. Verified success refunds exactly that target reservation. Budgets allow 10 outstanding or failed attempts per challenge and user, plus 60 admitted checks per user across challenges in 15 minutes. The aggregate budget uses the authenticated user id, so an office does not share one IP budget. A spent budget answers 429 with Retry-After (see shared-secret password budgets). Starting a session (POST /api/v1/sessions) applies the same participant catalog rules as the problem APIs and, for a password-protected challenge, requires that grant for the caller; challenge managers are exempt. Because the problem APIs only serve participants with an in-progress session, this is what keeps protected problems behind the password. Sessions and submissions by id are scoped to their owner, and a session can only be ended, not moved to another challenge, restarted or reopened. Coverage: apps/nova/src/app/api/auth/challenges/verify-password/route.test.ts, apps/nova/src/app/api/v1/sessions/route.test.ts, apps/nova/src/app/api/v1/sessions/[sessionId]/route.test.ts and apps/nova/src/app/api/v1/submissions/[submissionId]/route.test.ts.

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:
Platform-wide Nova permissions are not stored in a dedicated 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 the private 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 has allow_challenge_management and is listed in nova_challenge_manager_emails for that challenge.
  • canManageNovaChallengesGlobally(user, sbAdmin?) — true when the caller can manage all challenges (allow_manage_all_challenges or allow_role_management).
  • canManageNovaRolesGlobally(user, sbAdmin?) — true when the caller has allow_role_management.
  • Synchronous predicates canManageNovaChallenges, canManageAllNovaChallenges, and canManageNovaRoles evaluate an already-fetched NovaPlatformRole.
  • Resolver helpers getNovaProblemChallengeId, getNovaTestCaseChallengeId, and getNovaCriterionChallengeId walk child rows back to their owning challenge so a single canManageNovaChallenge check can guard nested mutations.

Best Practices

✅ DO

  1. Authorize service-role writes explicitly. getNovaAppSessionUserFromRequest() only proves a valid Nova app-session. Any route that mutates Nova management data through createAdminClient({ noCookie: true }) must also check the relevant platform_user_roles permission before the write, using the shared helpers in apps/nova/src/lib/challenge-management-auth.ts (canManageNovaChallenge(), canManageNovaChallengesGlobally(), canManageNovaRolesGlobally()). For nested resources (problems, test cases, criteria), resolve the owning challenge_id first via the resolver helpers and then authorize against that challenge.
  2. Keep hidden test cases hidden. Never return nova_problem_test_cases rows where hidden = true to participants. Filter on the server with .eq('hidden', false) for participant-facing reads.
  3. Validate challenge timing and attempt limits. Respect open_at, close_at, previewable_at, max_attempts, and max_daily_attempts before accepting a submission or starting a session.
  4. Honor whitelisting. When whitelisted_only is set, only admit emails present in nova_challenge_whitelisted_emails.
  5. Read scores from the aggregate view. Use nova_submissions_with_scores instead of recomputing from nova_submission_criteria / nova_submission_test_cases in application code.

❌ DON’T

  1. Don’t add a Nova tRPC router or @/lib/nova server-action layer. Nova has neither; product data flows through the Nova /api/v1/* routes and packages/internal-api helpers.
  2. Don’t read Nova tables directly from the client. All private.nova_* access goes through service-role route handlers after authorization.
  3. Don’t invent score columns on nova_submissions. Scores are derived from the per-criterion and per-test-case child tables.

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

Management page scope

The global sessions overview requires an enabled global challenge manager. Session and problem details require management access to their owning challenge; assigned managers retain access to their own challenge. Manager membership changes require an enabled platform role. The page and route boundaries are covered by apps/nova/src/test/management-page-access.test.tsx and the challenge manager route tests.