Platform Admin Layer
Who this guide is for: Embay Consulting internal staff with a platform user account. The platform is not accessible to tenant admins or learners.
Overview
app.embaylms.com is the Embay staff platform administration domain. It provides cross-tenant operations — tenant provisioning, status management, migration monitoring, billing oversight, and support tooling — against the global schema. Platform users are a separate identity from tenant users; they are stored in embaylms_global.platform_users.
The platform does not surface tenant-scoped features (courses, enrollments, catalog, SCORM, reports, learning paths, etc.). Those are always accessed at the tenant’s own subdomain (e.g. acme.embaylms.com).
Platform User Roles
| Role | Who | What they can do |
|---|---|---|
super_admin | Engineering leads, ops | Everything: create/delete/suspend tenants, manage platform users and roles, override tenant config, export audit logs, impersonate any tenant user |
support_agent | Customer support staff | Read all tenant records (status, config, migration state). Impersonate a tenant user for debugging (1-hour session, audit-logged). Cannot create/delete tenants or manage platform users. |
billing_admin | Finance / accounts | Read billing and subscription data across all tenants. Export invoices. No access to tenant user data or migration tools. |
Getting Access
Platform accounts are created by a super_admin via /platform/users. There is no self-signup.
- Ask a
super_adminto invite your Embay email address - You will receive an email with a one-time setup link
- Set your password and enable TOTP MFA (required for all platform users)
- Navigate to
https://app.embaylms.comand sign in
Bootstrap note: The very first
super_adminmust be created via the seed script:pnpm tsx infra/scripts/seed-platform-admin.tsThis is a one-time operation. See the runbook in
docs/enablement/runbooks/for production bootstrap steps.
Forgot Your Password? (LMS-366)
Platform accounts have self-service password reset:
- On the sign-in page (
https://app.embaylms.com/platform/login), click Forgot password? - Enter your platform account email and click Send reset link
- Check your inbox for an email titled “Password reset — EmbayLMS platform” and click Reset password
- Choose a new password (minimum 8 characters) and confirm it
- Sign in with the new password — the old one stops working immediately
| Behaviour | Detail |
|---|---|
| Link lifetime | 1 hour — request a new link if it expires |
| Email language | Matches the locale of the page you requested from (EN / FR-CA) |
| Unknown emails | The page always shows the same success message — it never reveals whether an account exists |
| Rate limit | 5 requests per minute per IP address |
| Audit | Both the request and the completed reset are written to the audit log |
| MFA | Resetting the password does not reset TOTP MFA. If you also lost your authenticator, a super_admin must clear your MFA enrollment |
Troubleshooting
- No email arrives: check spam, confirm you used the exact email on your platform account, and verify with a
super_adminthat the account is active — deactivated accounts never receive reset emails. - “This reset link is invalid or has expired”: the link is older than 1 hour, was already used after a newer one was issued, or was generated for a tenant account. Request a fresh link from
/platform/forgot-password.
Dashboard (/platform/dashboard)
Shows aggregate platform health. Visible to all platform roles.
| Metric | Description |
|---|---|
| Total Tenants | All tenants in the global registry |
| Active Tenants | Tenants with status = active |
| Migrations Done | Count of completed migration jobs |
| Migrations Failed | Count of failed jobs (highlighted in red if > 0) |
Tenant Management (/platform/tenants)
Access:
super_adminfor mutations. All roles can view.
Viewing Tenants
Lists all tenants with display name, subdomain, status, region, latest migration state, and creation date.
Tenant Detail (/platform/tenants/{slug})
Access: all platform roles can view.
Opening a tenant shows a read-only health overview: identity (region, created, custom domain, billing), usage metrics (users, courses, enrollments, completions), a configuration snapshot (SSO/SCIM/custom domain/branding), and a recent-activity audit preview.
Administrators (LMS-440). The detail page lists the tenant’s administrators — every
tenant user with the admin role — with their name, email (click to compose), last login,
and active status. Soft-deleted users are excluded. Use this to find the right person to
contact about a tenant without signing in to it. The list is read from the tenant schema
live; if it is briefly unavailable the section shows an empty state rather than failing the
page.
Feature Entitlements Panel (super_admin for changes) — LMS-373
The tenant detail page includes a Feature entitlements panel showing the tenant’s plan tier, seat/course/storage limits, API monthly cap, and each gated feature flag (SSO/SAML, SCIM, API access, custom branding, certificates, advanced automations) with its current On/Off state and whether entitlements are enforced for the tenant.
- Per-flag overrides. A
super_admincan toggle an individual flag on or off outside the tier default — e.g. enable SSO temporarily for a security review. Every override requires a reason, and the change is audit-logged (actor, before/after, reason). - Overrides persist until the tier changes — using Change plan tier resets the flags to the new tier’s defaults.
- Tenants with entitlements not enforced (grandfathered) show all features available regardless of flags.
Data Export (super_admin only) — LMS-379
The Data Export panel on the tenant detail page produces a full export of a tenant’s data (users, courses, enrollment/completion records) — for contract-end offboarding or a support investigation (§6.3.1).
- Click Export tenant data. A job is queued and an audit entry
(
tenant_export_requested) records who requested it. - The worker gathers the data, writes a JSON document to the exports bucket, and emails you when it’s ready.
- Click Refresh to update the list, then Download on a completed job. The link is a
short-lived presigned URL (≤15 minutes, CLAUDE.md §4.4); each download is audit-logged
(
tenant_export_downloaded). Re-click to get a fresh link after it expires.
| State | Meaning |
|---|---|
queued / running | The export is in progress. |
completed | Ready — a Download button appears (shows file size). |
failed | Something went wrong (reason shown), e.g. exports storage not configured. |
Signing in to a Tenant (super_admin only) — LMS-437
A super_admin can sign in to any active tenant as a full administrator
without waiting for a customer access request. On the tenant detail page click
Log in to tenant.
- The action records an audit entry (
platform_tenant_login) tying your platform identity to the target tenant — this is the trace of which super_admin entered which tenant and when. - You are redirected to the tenant (e.g.
acme.embaylms.com) and signed in as a synthetic platform-support administrator. A navy banner across the top reads “You are signed in to {tenant} as Embay platform support”. - You have the full tenant-admin permission set — you can do anything a tenant admin can.
- Click Return to platform in the banner to sign out of the tenant and go back to the platform console (your platform session is preserved).
| Property | Detail |
|---|---|
| Who | super_admin only. Support agents use consent-based access (future) instead. |
| Tenant visibility | You do not appear in the tenant’s user table — the platform-support session has no tenant user record. |
| Attribution | Actions inside the tenant are audit-logged with your platform user id as the actor. |
| Availability | Only active tenants — reactivate a suspended tenant first. |
| Exit | ”Return to platform”, or simply signing out of the tenant. |
This is distinct from user impersonation (logging in as a specific tenant user); platform-support login acts as a generic tenant administrator.
Creating a Tenant (super_admin only)
- Click New Tenant
- Fill in the form:
| Field | Required | Description |
|---|---|---|
| Display Name | Yes | Human-readable name (e.g. “Acme Corp”) |
| Subdomain Slug | Yes | Lowercase, alphanumeric + hyphens. Becomes {slug}.embaylms.com |
| Region | Yes | Default: ca-central-1 |
| Custom Domain | No | e.g. learn.acme.com |
- Click Create Tenant
The system creates the tenant record (status: provisioning) and queues a worker job to:
- Create the
embaylms_tenant_{slug}PostgreSQL schema - Register
{slug}.embaylms.comin Vercel
If Redis is unavailable, run provisioning manually:
pnpm tsx infra/scripts/provision-tenant.ts --tenant {slug}Suspending a Tenant (super_admin only)
Click Suspend. Sets status: suspended — all logins at {slug}.embaylms.com are blocked. No data is deleted.
Activating a Tenant (super_admin only)
Click Activate on a suspended tenant to restore access.
Deleting a Tenant (super_admin only)
Soft-deletes the tenant record (status: deleted). The PostgreSQL schema and S3 data are not immediately purged — a separate manual purge step is required. Deletion is audit-logged and irreversible via the UI.
Support: Tenant Impersonation (/platform/tenants/{slug}/impersonate)
Access:
super_adminandsupport_agent
Allows an Embay staff member to log in to a tenant’s context as a specific user, for debugging purposes.
- Find the tenant in the tenant list
- Click Impersonate
- Select the target tenant user (by email)
- Confirm — you are redirected to
{slug}.embaylms.comwith a 1-hour impersonation session
Audit trail: Every impersonation login is written to audit_log with:
actor_id: platform user IDis_impersonation: trueimpersonated_user_id: target userimpersonated_by_role: your platform role
Constraints:
- Impersonation sessions expire after 1 hour and cannot be extended
- An impersonation session cannot itself perform impersonation
support_agentimpersonation sessions are read-only within the tenant (no mutations)
Platform User Management (/platform/users)
Access:
super_adminonly
- Invite a new platform user by email — sends a one-time set-password link.
- Add a new platform user directly (LMS-438) — generates a temporary password shown once for you to hand off securely. The new user is forced to change it on first sign-in (a full-screen prompt blocks the console until they do). Use this when email delivery is unavailable or you need to bootstrap an account immediately.
- Assign or change roles
- Deactivate a platform user (immediately revokes access)
- View login history per user
Impersonating a Platform User (LMS-439)
Access:
super_adminonly
For debugging another Embay staff member’s experience, hover a row in
/platform/users and click Impersonate. Your session is swapped to that user
and an amber banner appears at the top of the console; click Exit impersonation
to return to your own account. You cannot impersonate yourself or a deactivated
user. Every impersonation start is written to the audit log
(platform_impersonation_started) with your id as impersonatedBy.
This impersonates a platform user on
app.embaylms.com— it is distinct from entering a tenant (see Tenant access above).
Errors Console & Platform Notifications (/platform/errors) — LMS-600/601
Access:
super_adminandsupport_agentcan view; status changes and identity resolution aresuper_adminonly.
The Errors console surfaces the self-healing error pipeline (ADR-018): user-facing tenant errors are captured first-party, deduplicated into error groups, ticketed at runtime in Linear, and triaged by an automated coding agent. The console is the human window into that loop.
The Errors list
/platform/errors lists error groups (not individual occurrences), filterable
by status and tenant slug. Each row shows the tenant slug, environment,
occurrence and affected-user counts, first/last seen, and the linked Linear bug.
Group statuses: New, Triaging, Fix in progress, Awaiting decision
(the agent escalated — a human call is needed), Resolved, Ignored, and
Regressed (a resolved error came back).
Status actions (super_admin, audit-logged): Resolve an open group,
Ignore noise, or Reopen a resolved/ignored group.
Identity resolution (audit-logged)
Error rows are pseudonymous — they carry the user’s UUID, tenant slug, and
browser/OS only, never email, name, or IP. On a group’s detail page a
super_admin can explicitly resolve a captured UUID to a person (name, email,
role) when needed for support follow-up. Every resolution is written to the
audit log.
The notifications bell
The platform console header shows a notifications bell — the first in-app notification path for Embay platform users. It signals pipeline events that need attention: an error group awaiting a decision, a fix deployed, or a regression. Clicking a notification deep-links to the relevant error group.
For pipeline operations (enabling capture, agent caps, the safety envelope,
callback secrets, and incident handling), see the runbook:
docs/runbooks/self-healing-pipeline.md.
What the Platform Does NOT Include
The following are not available at app.embaylms.com. Always access them at the tenant’s subdomain:
- Course catalog and course editor
- Enrollments and completions
- Learning paths and skills
- SCORM content upload and launch
- ILT sessions and quizzes
- Tenant user management (inviting learners, setting tenant roles)
- Reports and certificates
Troubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
Redirected to login page on app.embaylms.com | No platform user account, or session expired | Sign in, or ask a super_admin to create your account |
| Signed in but immediately redirected away | Account exists in a tenant schema, not in platform_users | A super_admin must create you a platform account via /platform/users |
Tenant shows provisioning indefinitely | Redis/BullMQ not running | Run pnpm tsx infra/scripts/provision-tenant.ts --tenant {slug} manually |
app.embaylms.com/admin/courses/... returns redirect | Correct — tenant routes are blocked on the platform domain | Access at {tenant}.embaylms.com instead |
| ”Platform API only accessible from platform domain” error | /api/platform called from a non-platform domain | Security guard — do not call the platform API from tenant subdomains |
| Impersonation session expired | 1-hour limit reached | Start a new impersonation session from /platform/tenants |