Details of the Go platform that the README only summarises: the security and database model, the judge boundary, what works end to end, the first run, and key rotation. Decisions are in the ADR index; current status in the roadmap.
The User service is the canonical Casbin-backed authorization decision service.
Its private mTLS authz/v1.Authorize API issues a fresh, five-second,
database-audience-bound HMAC capability for each allowed request. A target
database validates that capability in authz.set_context, binds it to the
current PostgreSQL backend and transaction, and checks the local
authorization-revision projection under FORCE ROW LEVEL SECURITY. A failed
decision, expired capability, or projection lag denies access. The complete
contract is in docs/database/authorization-context.md.
Authorization recovery is fail closed as well. Each RLS-protected service starts with its local authorization projection unavailable and, after an outbox or authorization-consumer failure, writes a target-specific resync request through its local outbox. The User service returns a manifest-verified grant snapshot; the target reopens only after every item has been applied. This prevents a stream-retention gap from silently retaining access after a revocation.
Every stateful platform service owns one logical database in the three-node
platform PostgreSQL topology. Database owners are non-login roles; migrations,
applications, and authorization-projection workers use separate least-privilege
identities. Run migrations only as the service migrator after the role/database
provisioner has run. Authorization HMAC material is supplied by the approved
KMS/secret controller after bootstrap with
backend/scripts/provision-authz-context-key;
the script neither generates nor stores a secret.
The platform HA chart is deliberately render-gated on client certificates and India-resident encrypted backup inputs. A successful render or install is not HA acceptance: the node-failure and PITR exercises in the platform PostgreSQL runbook are required before promotion.
Judge control-plane state is isolated in aether_judge_wrapper with its own
PostgreSQL HA deployment and RabbitMQ quorum cluster. It does not own or share
Redis with the platform; Redis is an internal dependency of the separately
operated Judge0 engine after approval. The wrapper accepts durable, encrypted
references and leases completions through private mTLS gRPC.
Implemented on the judge side:
- a real Judge0 HTTP client (
backend/services/judge/internal/adapters/judge0), selected withJUDGE_ENGINEand only enabled whenJUDGE_ENGINE_COMPATIBILITY_APPROVEDis set; - fan-out of an evaluation bundle into one execution unit per test case (ADR-0014);
- per-unit results surfaced to submission, with a candidate-versus-faculty visibility boundary (ADR-0015).
Submission dispatches each queued evaluation request and code run to Judge over mTLS; Judge decrypts the bundle and source, runs one execution unit per test case on the engine, and Submission scores the attempt by test weight (ADR-0014, ADR-0015, ADR-0021). The single-server stack runs code on the AetherCode Execution Engine (ADR-0028).
The Judge0 engine chart is disabled by default. It must remain blocked until the gVisor, no-network, non-privileged compatibility gate has approved an immutable image and recorded queue-replay, node-failure, and 10,000-candidate / five-minute load evidence, including the 60-second final-verdict P95 target. See the compatibility-gate runbook.
Working end to end through the gateway (verified by
deploy/single-server/smoke.py):
- identity: login by username or roll number, MFA and recovery; administrator-provisioned accounts and CSV student import (ADR-0019);
- colleges, departments, batches, roles and placement affiliations;
- staff authoring into a global question bank with server-built, encrypted, weighted test bundles (ADR-0020); immutable question and exam versions; batch assignment;
- attempts with per-candidate deadlines, autosaved answers, Run against sample tests with full output (ADR-0021), submit, judging, weighted scoring and time-up auto-submission;
- Safe Exam Browser lockdown with per-URL key checks and
.seblaunch files (ADR-0022); - results and CSV export, result release, a live monitor with extra time and announcements, regrading, a similarity report and staff operations views (ADR-0027, 0031, 0039, 0043);
- question pools, multiple choice, model-solution checks that gate publishing, question review, practice problems and exam previews (ADR-0033 to 0035, 0041, 0044 to 0047);
- community content for students: interview experiences, blogs, contest, hackathon and open-source listings, linked coding accounts and tasks (ADR-0048);
- single sign-on through the college's ZITADEL (ADR-0042) and college branding from a file (ADR-0045);
- in-app notifications, event-fed analytics projections, cursor-paginated lists and soft delete (ADR-0013).
The local MinIO and KMS adapters (backend/libs/pkg/storage/minio,
backend/libs/pkg/kms/local) are what the single-server stack uses. A
multi-college production deployment should use managed object storage and
KMS; see the production gates in the roadmap.
A fresh deployment has no principals and no role assignments, so there is no way to call any authenticated endpoint. Run the one-time bootstrap command to create the first platform administrator after migrations are applied:
export IDENTITY_DATABASE_URL="postgres://..."
export USER_DATABASE_URL="postgres://..."
make bootstrap EMAIL=admin@college.edu NAME="Platform Admin"The command creates the principal in the identity database and a self-granted
super_admin role assignment in the user database. The created account has no
password. Activate it by triggering the password-reset flow for the supplied
email address. No password is ever written, printed, or accepted by this
command.
The command is safe to re-run. A crash between the two database writes is
repaired by running it again — each half independently no-ops if its row already
exists. Once the platform has any principal or any super_admin, both functions
permanently refuse further calls.
authz.context_keys (present in the analytics, assessment, identity,
notification, question-bank, seb, submission, tenant, and user
databases — not gateway or judge, which have no such table) supports
zero-downtime rotation through overlapping not_before/not_after/retired_at
validity windows, but publishing and retiring a key still requires an operator
action. make rotate-authz-key targets exactly one database per invocation:
export DATABASE_URL="postgres://..."
make rotate-authz-key ACTION=publish AUDIENCE=aether_submission NOT_AFTER=2026-09-24T00:00:00ZThe command generates a new key ID (a UUIDv7, unless KEY_ID is supplied) and
32 bytes of random HMAC key material, inserts the row, and prints the key ID
and base64-encoded secret to stderr exactly once, with a clear warning that
it is the only time the secret is shown. The secret is never written to a file,
a log, or stdout. Copy it out-of-band into the target's operational secret
store before it is lost.
Full rotation procedure:
- Publish a new key against all nine target databases, one invocation per
database, with a
not-beforea few minutes in the future (the default) so already-deployed services have time to pick up the new configuration before the key becomes valid, and anot-afterfar enough out to cover the rotation window. - Wait until
not-beforehas passed on every database, then confirm the new key works before relying on it. - Update
AUTHZ_CAPABILITY_KEYSin the User service's configuration (the canonical signing service, perbackend/libs/pkg/authz) to the new key. - Once confident no capability signed with the old key is still in flight —
capabilities have a five-second TTL (
capabilityTTLinbackend/libs/pkg/authz/capability.go), so the safe window is generous — retire the old key on all nine databases:
export DATABASE_URL="postgres://..."
make rotate-authz-key ACTION=retire AUDIENCE=aether_submission KEY_ID=<old-key-id>retire fails if the key is already retired or does not exist for that
audience. This CLI is for operational key rotation; the KMS-provisioned
first key for a freshly bootstrapped database still comes from
backend/scripts/provision-authz-context-key,
whose secret is supplied externally rather than generated locally.