Skip to Content

Demo Tenant

The demo tenant is a pre-seeded, sales-ready Amal environment that covers all six personas without the risk of permanently breaking real data. A single public endpoint returns login credentials for any visitor; a read-only guard blocks destructive writes while leaving the full reading-support loop open.

Seeding the demo org

Run the idempotent seeder from the repo root:

pnpm --filter server seed:demo --yes

The seeder provisions:

  • One organization (isDemo = true) in Jordan.
  • One school, three classes, and 60 students spread across Grades 1 to 4.
  • Six adult personas with deterministic credentials (emails at *@amal-demo.jo + shared password AmalDemo!2026).
  • A hero teacher assigned to two classes to demonstrate the multi-class picker.
  • Real RTI pipeline state for a representative student subset (diagnostic finish, skill-status snapshots, profile resolution, bundle recommendations).

Re-running the seeder wipes accumulated visitor state (sessions, snapshots, profiles, bundles, monitoring rows, context flags, notifications) and re-derives the baseline. People rows are upserted on natural keys so login credentials remain stable across resets.

A DEMO-GUIDE.md file is emitted to docs/demo/ on every seed run with the credential table, a per-persona page tour, and the sync-class join code.

The seeder requires AMAL_SEED_DEMO_CONFIRM=1 in the environment (or pass --yes) to guard against an accidental run against a non-demo database.

GET /api/demo/access

The single public endpoint returns whatever the seeded demo org contains. No Authorization header is required or accepted (security: []).

curl https://localhost:3000/api/demo/access

When no demo org is configured

{ "available": false }

When the demo org exists

{ "available": true, "password": "AmalDemo!2026", "adults": [ { "persona": "ADMIN", "email": "admin@amal-demo.jo" }, { "persona": "MANAGER", "email": "manager@amal-demo.jo" }, { "persona": "PARENT", "email": "parent@amal-demo.jo" }, { "persona": "PRINCIPAL", "email": "principal@amal-demo.jo" }, { "persona": "TEACHER", "email": "teacher@amal-demo.jo" } ], "students": [ { "grade": 1, "classId": "...", "quickCode": "..." }, { "grade": 2, "classId": "...", "quickCode": "..." }, { "grade": 3, "classId": "...", "quickCode": "..." }, { "grade": 4, "classId": "...", "quickCode": "..." } ] }

The handler queries only the isDemo = true organization. It never reads a non-demo org, and it never discloses that any other org exists. Adults are ordered alphabetically by email; students carry one sample per grade (the first active enrollment from each demo class, ordered by grade).

The DEMO_READ_ONLY guard

Approximately 29 destructive and provisioning route groups carry a requireNotDemo middleware check. When a request comes from a demo org session, these routes return:

403 { "code": "DEMO_READ_ONLY" }

Blocked actions include user CRUD, tenancy CRUD, LMS connect / sync, quickCode regeneration, password-reset mail, and parent-notification dispatch.

The following are not blocked and remain fully interactive:

  • All diagnostic sessions (start, answer, finish).
  • All practice queues and item serving.
  • Cognitive warm-up sessions.
  • Teacher decisions (bundle activation, plan adjustments, context flags).
  • Monitoring evidence and progress reads.
  • All read endpoints for every persona.

A visitor can walk the complete reading-support loop end to end.

The /demo entry page

The public /demo page calls GET /api/demo/access at render time and shows:

  • One card per adult persona with an “Enter as X” button.
  • A student card with a per-grade quickCode picker.
  • A collapsible credentials panel (shared password, per-persona emails, per-grade quickCodes) with copy-to-clipboard, so a presenter can log in from a second device.

Clicking “Enter as X” sets the amal_demo=1 cookie alongside the session and redirects to that persona’s home screen. A DemoBanner in both app shells (Adult AppShell and Student World) shows a persistent “you are in demo mode” strip for the duration of the session.

When available: false the page renders a calm “unavailable” panel with a booking call-to-action instead of persona cards.

  • Provision Users: creating real users outside the demo path
  • Audit Log: auditing operator actions across all organizations