Skip to main content
Kralis Classroom is a standalone Django application and Next.js product area. It owns its domain model and integrates with Kralis core school, user, class, section, subject, year, and term records through explicit foreign keys. It does not depend on LMS. A future LMS course integration should reference or request a Classroom event; it must not move room or attendance ownership into the LMS domain.

System boundaries

Kralis owns

  • identity, school tenancy, authorization, and role resolution
  • events, settings, grants, admissions, participants, and sessions
  • expected rosters, provider presence evidence, and attendance results
  • chat persistence and presentation state
  • recording requests, publication state, and playback authorization
  • whiteboard ownership, grants, editor leases, and immutable revisions
  • operational limits, notices, audits, incidents, and retention metadata

LiveKit owns

  • WebRTC media transport
  • room and participant provider state
  • camera, microphone, and screen-share tracks
  • restricted data transport for trusted notifications and whiteboard scenes
  • room-composite Egress

R2 owns

  • private MP4 recording objects
  • private Excalidraw JSON revision objects
Provider identifiers are implementation details. Product URLs and access decisions use Kralis UUIDs.

Backend organization

Business transitions belong in classroom/services, not GraphQL resolvers, REST serializers, webhooks, or Celery tasks. Those surfaces resolve inputs and delegate to the same services so manual, scheduled, and provider-driven paths converge.

Frontend organization

The browser uses generated GraphQL types. After changing Classroom GraphQL documents or schema, run the frontend generation command and formatting before type checking.

Data model relationships

Every domain row inherits school scoping through the Kralis base model. A session is an execution of an event, not the event itself. A recording belongs to both the event and, when available, the session that produced it.

API split

GraphQL

GraphQL is the application control plane. It covers:
  • event CRUD-like lifecycle mutations and cursor-paginated queries;
  • access grants, collaboration groups, and membership;
  • lobby requests and decisions;
  • join credentials and recording-notice acknowledgement;
  • participant moderation, live settings, chat, and presentation;
  • attendance, sessions, recordings, playback, and publication;
  • whiteboard creation, grants, attachments, revisions, and realtime access;
  • school operational policy, usage, audit events, and issue resolution.
Root queries and sensitive nested relationships are school- and role-scoped. Clients must not infer authorization from a previously fetched object; each mutation rechecks current access.

REST

Classroom REST resources are read-only and school-acronym scoped:
The provider webhook is REST because LiveKit posts signed event payloads:
It intentionally has no Kralis user authentication. The receiver verifies the LiveKit authorization signature using the API key and secret before processing anything.

Authentication and LiveKit authorization

Kralis users do not receive LiveKit accounts. The flow is:
  1. authenticate the Kralis web request;
  2. load the event inside the user’s school;
  3. resolve the strongest event role;
  4. enforce event state, recording notice, removal state, admission, and capacity;
  5. create or update the Kralis participant;
  6. issue a short-lived LiveKit JWT for one opaque room.
The token contains the Kralis user UUID as identity and school/event/session metadata. It permits subscription and only the media sources authorized by the current role and overrides. It does not grant room administration, arbitrary data publishing, or metadata mutation. Lobby and whiteboard tokens are even narrower data-room credentials. The backend remains authoritative when an old token is replayed: signed join webhooks recheck access and admission before creating attendance evidence and queue disconnection when authorization is no longer valid.

Webhook inbox and presence

Accepted provider events are stored in ProviderWebhookEvent using LiveKit’s event ID as an idempotency key. Duplicate processed events return success without applying the transition again. Supported provider events include:
  • room_started
  • participant_joined
  • participant_left
  • room_finished
  • egress_started
  • egress_updated
  • egress_ended
The handler maps the opaque provider room to a known Kralis session. Unknown rooms are safely ignored. Processing failures remain in the inbox with an attempt count and operational issue. Participant SID, rather than user ID alone, makes presence intervals reconnect safe. Attendance calculation clamps and merges those intervals before deriving duration and status. An early room_finished for a scheduled event ends only that session; the scheduler may create a replacement. An on-time/provider-driven completion uses the shared full event finalizer.

Realtime application data

The browser cannot publish arbitrary application commands in the media room. Chat, hand, settings, moderation, and presentation changes first pass through authenticated GraphQL and persistent services. After commit, the server sends a small trusted LiveKit packet prompting connected clients to refetch or apply the authoritative state. Separate topics exist for chat, participant state, admission, event settings, presentation, whiteboard scenes, and the active whiteboard cursor. Whiteboard editor tokens are the exception: exactly one leased editor may publish scene/cursor data in the board’s restricted data-only room. Durable state still comes from immutable R2 revisions.

Recording architecture

Manual start creates a REQUESTED row synchronously and queues provider work. Celery starts room-composite Egress. Provider webhooks update normal state, and the minute reconciler repairs delayed or missed transitions. Camera-grid recording uses LiveKit’s standard grid layout. Screen-share recording uses the public Kralis egress renderer at LIVEKIT_EGRESS_TEMPLATE_URL, the kralis-classroom custom layout, and the standard LiveKit egress query-token mechanism. The configured base URL must not contain a query or fragment. The renderer reads credentials through the LiveKit Egress SDK, removes them from browser history, and starts capture only after media is available. Egress uploads H.264 720p MP4 directly to private R2 using S3-compatible credentials. Kralis validates the returned filename before marking the record ready.

R2 layout

R2 keys use immutable UUIDs rather than acronyms:
R2_ENDPOINT must be an account endpoint such as:
It must not include /kralis, a bucket, path, query, or fragment. Configure the bucket independently.

Environment configuration

All secrets are server-side. The frontend public domain is required for the screen-share Egress renderer. The API public domain is required for LiveKit webhooks.

Celery and lifecycle convergence

Run an API process, Celery worker, and Celery Beat. The configured periodic work is:
Provider preparation, recording start, chat/participant/settings publication, moderation, disconnection, and room cleanup are also Celery tasks. Mutations commit Kralis state before queuing side effects.

Operational safeguards

  • per-school concurrent room, recording, and storage limits
  • idempotent schedule and webhook transitions
  • capacity reservations for concurrent joins
  • mutation rate limits for admission, chat, hand, joins, whiteboard leases, heartbeats, and revisions
  • recording and webhook retention
  • append-only audit events for sensitive actions
  • coalesced operational issues and support email alerts
  • explicit publication before ordinary recording playback
  • presigned private playback URLs
  • role-aware GraphQL nested resolvers
Default mutation limits are evaluated per authenticated scope and operation: The GraphQL error includes retry information so the frontend can disable the action temporarily and show a countdown instead of encouraging repeated calls.

Provider and staging checks

Verify configured providers with isolated temporary resources:
The R2 check performs create, read, update, and delete. The LiveKit check creates a temporary room and validates the configured signed webhook path. Run the disposable end-to-end command only in staging:

Deployment position

The current product is designed for managed LiveKit Cloud first, with private Cloudflare R2 storage. This avoids operating TURN, SFU, Egress, autoscaling, and regional media capacity while Classroom usage is being established. The provider adapters preserve a path to self-hosted LiveKit later. That change requires capacity planning for signaling, Redis, TURN, regional bandwidth, Egress workers, observability, upgrades, and failure recovery; it is an infrastructure decision, not a frontend rewrite.

LMS integration rule

When LMS requests a live class for a course:
  1. the LMS course explicitly requests creation or attachment;
  2. Classroom creates or owns the event and its settings;
  3. LMS stores the association on the course side;
  4. Classroom remains independently visible and usable;
  5. Classroom remains authoritative for access, sessions, attendance, recordings, chat, and boards.
Do not automatically create a room for every course. Avoid placing course IDs inside LiveKit room names or making Classroom authorization depend on LMS enrollment without an explicit integration contract.