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 --yesThe 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 passwordAmalDemo!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/accessWhen 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.
Related guides
- Provision Users: creating real users outside the demo path
- Audit Log: auditing operator actions across all organizations