The Admin products are the operator-facing control surfaces of Oshun V1: the
Oshun Admin web console that platform operators use to run review, trust &
safety, support, privacy, model ops, incidents, and readiness; and Oshun Admin
mobile, an Expo app that puts urgent triage and step-up-protected approvals in
an on-call operator's pocket. This page serves the operators who live in these
surfaces, the engineers wiring the BFF route handlers, and reviewers who need to
know which claims are backed by shipped code versus partially-covered UI. It
sits beside the strictly-isolated
Tenant, Institution, and Operator Toolkit: the
operator-only Admin products described here are the platform-wide counterpart to
the per-tenant console, and both draw on a shared domain library,
libs/oshun/tenant-console.
Scope note. "Admin Products" is the operator console. The capabilities that a tenant admin sees — SSO/SCIM config, roster sync, per-tenant auth policy, custom roles — are detailed on Tenant, Institution, and Operator Toolkit. Both surfaces are thin Next.js rendering layers over the same nine
@oshun/tenant-consolemodules; this page focuses on the platform-operator web and mobile apps and enumerates their concrete route surface. Backlog tracking for the console substrate is §20 in ../TODOS.md; the apps themselves live underapps/oshun/admin,apps/oshun/admin-mobile, andapps/oshun/tenant-admin.
Where this sits, and what is real#
The Admin web app (apps/oshun/admin) is a substantial Next.js application:
274 files under src/components/ and a deep server route surface under
src/app/api/admin/. It is not a wireframe. Every operator workflow described
below is backed by a real BFF route handler that, in turn, calls real domain
logic — much of it the pure-function substrate in @oshun/tenant-console.
The realism caveat is the same one that governs the whole admin-tenant area and
is stated honestly here: the identity primitives (processSsoLogin,
applyScimSync, evaluateAuthChallenge) are deterministic pure functions,
not live SAML/OIDC runtimes. There is no embedded SAML response parser or OIDC
token validator in the library. The OIDC live-login path is wired to fail
closed with a 503 when an SsoConnection lacks oidcClientId /
oidcJwksUrl, exactly as the comment in identity/sso.ts documents, "so a
config-only connection never pretends to be loginable." This is a deliberate
fail-loud seam, not a stub: the function refuses to fabricate a logged-in
session it cannot actually authenticate. The one area the completeness audit
flags as only partially covered end-to-end is the tenant-admin SSO config UI
(metadata-XML / OIDC-discovery parsing, the full claim-transform matrix editor,
the auth-policy panel, and sandbox-probe verdicts). Nothing in this area reads
as fabricated.
Oshun Admin Web#
Oshun Admin web V1 includes a single operator shell with auth, RBAC, a dashboard, a unified inbox, and navigation across trust/safety, review, research-integrity, support, privacy, policy, model, persona, rights, incident, and readiness areas. Across that shell, it provides saved views, filters, a universal admin search, cross-workspace links, collaboration comments and annotations, keyboard-first triage, a notification center, assignment signals, and exportable investigation bundles.
The operator workspaces#
The console is organized into named workspaces, each with list and detail views, its own queue semantics, and an audit linkage:
- Review. Package lists and details, stage graphs, status history, approve / reject / request-changes actions, rationale capture, delegation, escalation, SLA timers, review templates, blockers, immutable history, and audit linkage.
- Trust and safety. Queues for moderation, user reports, crisis escalation, cloned-voice and avatar-deception review, image moderation, repeat-offender tracking, appeals, rule-hit explanations, and metrics.
- Content & curriculum. Content calendar, publishing states, content QA, curriculum and teacher publishing, research integrity, and citation validation.
- Support, billing & privacy. Support cases, subscriptions, entitlements, refunds, privacy requests, DSAR exports, deletion, retention exceptions, legal holds, policy attestations, and compliance reviews.
- Persona, model & rights. Persona registry, voice-profile review, avatar-pack review, model registry, workflow templates, rights/provenance inspection, watermark verification, incidents, a policy/config center, and executive and readiness dashboards.
- Editorial. Editorial calendar, content scheduling, embargo, takedown, hotfix, and emergency-correction surfaces with cross-domain visibility and assignment.
- Agentic AI operations. Dashboards bound to the Agentic AI Studio (see Agent Registry, Job Orchestration, and Multi-Agent Plans): cross-tenant run inventory, plan/DAG inspection, evidence-trail review, cost ledger, kill-switch activation, gold-set promotion, champion-challenger rollout, and rollback.
- Audit log explorer. Filters, saved investigations, exportable bundles, cross-event correlation, and tenant-scoped views (mechanics below).
- Bulk-operation console. Users, rosters, content, taxonomy, persona assignments, and entitlements with dry-run, validation, and staged commit.
- Integrations registry. API keys, webhooks, LMS/calendar/identity/payment connectors, OpenAPI/SDK docs, sandbox tenants, and webhook simulators.
- Communications. Notification, email, SMS, and in-app message template authoring with localized variants, variable validation, accessibility lint, A/B testing, and approval workflow; help center and changelog authoring under the same lifecycle, review, localization, and versioning machinery as content domains; and public status-page authoring, banner targeting, incident communications, and a postmortem catalog.
Admin copilots, with a hard line between suggestion and decision#
Each operator area carries a copilot — for review, moderation, research
integrity, model ops, compliance, and support — with an explicit distinction
between suggestions, evidence, and human decisions. High-risk
decisions require mandatory human approval; the console captures feedback, logs
overrides, ships audit tests, and is built to prevent bypass. This is visible in
the route surface: review decisions go through
api/admin/review/[reviewId]/confirm-high-risk-approval and a paired
.../cancel-high-risk-approval. Every copilot area exposes a
.../copilot-feedback endpoint so a human's accept/reject of a suggestion is
itself recorded.
One inbox, safe bulk triage, the full policy surface#
A single canonical inbox-and-queue model spans review, moderation, support,
privacy, incident, model, persona, policy, and research integrity. Safe bulk
triage and action workflows enforce rationale, selection limits, previews, and
audit trails (the bulk inbox action is api/admin/inbox/bulk-action). The
policy surface is correspondingly broad: policy packs, disclosure state,
tone/style calibration, prompt policy, retrieval policy, grounding policy,
moderation thresholds, appeals, voice/avatar policy, experiment guardrails,
model lineage, provider health, sampled output review, incident blast-radius,
customer impact, runbook launch, postmortems, release scorecards, and go/no-go
decisions.
Designed for long shifts#
The console favors a dense-data redesign, calmer operator surfaces, evidence comparison, diff inspection, severity and escalation visuals, reduced modal overuse, and extended-session ergonomics. Coverage spans Playwright, accessibility, visual regression, audit, copilot, admin-queue, policy, incident, export, rights, privacy, and research-integrity tests.
The Admin web server route surface#
The architecture docs describe the admin app but stop short of enumerating its
server routes; in practice the BFF surface under
apps/oshun/admin/src/app/api/admin/ is the most concrete evidence that the
console is wired through to the domain libraries rather than mocked. A
representative slice (the full tree is deeper, with per-resource [id] and
action sub-routes):
| Area | Representative routes |
|---|---|
| Session | signin, signout |
| Discovery | search |
| Inbox & notifications | inbox/bulk-action, notifications, notifications/[notificationId]/{seen,dismiss}, notifications/mark-all-seen |
| Review | review/[reviewId]/{decision,delegate,escalate,confirm-high-risk-approval,cancel-high-risk-approval,copilot-feedback} |
| Moderation | moderation/items/[contentItemId], .../copilot-feedback |
| Bulk operations | bulk-operations/[operationId]/transition, bulk-exports/[opId]/verify |
| Integrations | integrations/api-keys, integrations/api-keys/revoke, integrations/webhooks/simulator, integrations/connectors/upgrade, integrations/snapshot |
| Developer portal | developer-portal/articles, developer-portal/certification/checks, developer-portal/sandbox-policies |
| Agentic operations | agentic-operations/kill-switches, .../gold-sets/promote, .../champion-challengers/rollout, agentic-operations/snapshot |
| Communications | communications/{banners,help-articles,incidents/update,status-components,snapshot} |
| Editorial | editorial/release-streams, .../[streamId] |
| Audit | audit-log/events, audit-log/investigations/[investigationId]/export |
| Privacy | privacy/{requests,deletion-jobs,consent-records,disclosure-entries,memory-scopes,retention-exceptions,audit-exports/generate} |
| Models & providers | models/[modelId]/{governance/promote,governance/rollback,providers/failover/trigger,review/decision,spot-check/...} |
| Readiness & rehearsals | readiness/reports/[reportId]/releases/[releaseId]/decision, rehearsals/[rehearsalId]/{start,complete,rollback} |
These routes map directly onto the @oshun/tenant-console exports — e.g.,
bulk-exports/[opId]/verify calls verifyManifest, integrations/api-keys
operates on ApiKey records, communications/incidents/update appends to an
IncidentRecord.publicUpdates thread, and audit-log/investigations/.../export
materializes an AuditExportBundle with chain-of-custody metadata.
The shared substrate: @oshun/tenant-console#
Both the operator console and the tenant console import the same library.
libs/oshun/tenant-console/src/index.ts re-exports nine modules:
tenant-model, identity, roles, audit-explorer, bulk-ops,
integrations, notifications, help-center, and status-page. The apps are
thin Next.js rendering layers over these.
apps/oshun/tenant-admin/src/app/identity/page.tsx imports applyScimSync,
canFederateAuth, computeRosterDiff, evaluateAuthChallenge, and
processSsoLogin directly from @oshun/tenant-console, so the app genuinely
consumes the lib rather than reimplementing identity logic in the UI.
(ARCHITECTURE.md correctly names the app at apps/oshun/tenant-admin but
understates the library's role: essentially all domain logic lives in the
library; the Next app is the rendering layer.)
The subsections below surface the concrete primitives that the operator console drives — useful both as a reference for the route handlers and as evidence that the logic is domain-specific, not generic CRUD.
Identity — SSO, SCIM, OneRoster, federation, auth policy#
identity/sso.ts models SSO_PROTOCOLS = ['saml2','oidc']. A claim mapping
(SsoClaimMapping) maps an IdP externalClaim to one of a fixed set of
internal attributes — email, displayName, familyName, givenName,
groups, tenant-role, preferred-locale — under one of four transforms:
identity, lowercase, csv-split, first-only. processSsoLogin handles
both idp-initiated and sp-initiated flows, validates the assertion
signature, enforces session/refresh lifetime sanity, applies the transforms, and
performs just-in-time provisioning when jitProvisioning is on and the email
claim is present. As noted above, OIDC live login fails closed (503) when the
runtime fields are absent.
identity/scim.ts defines
SCIM_OPERATIONS = ['create','replace','patch','delete'] and a conflict policy
ScimConflictResolution ∈ {reject, merge, idp-wins, local-wins}.
applyScimSync returns a verdict — applied, rejected-conflict, merged, or
noop — so the operator sees exactly how a provisioning event resolved. The
module also carries OneRosterClassResource and OneRosterEnrollmentResource
(each with status ∈ {active, tobedeleted, inactive}) and computeRosterDiff,
which produces an add/update/remove diff plus conflict reports (duplicate
externalIds, enrollments referencing unknown classes or users) for
OneRoster-grade Metis rostering.
identity/federation-and-auth-policy.ts provides cross-tenant federation via
canFederateAuth, supporting both one-way and mutual
TenantFederationEdges. The per-tenant TenantAuthPolicy carries
mfaMethodsAllowed ∈ {totp, webauthn, sms, push}, an ipAllowlist
(CIDR-style), a device-posture requirement, and inactivity bounds.
evaluateAuthChallenge returns allow, step-up-required (reason mfa /
device-posture / inactivity / trigger), or deny (reason ip-blocked).
The step-up triggers are fixed:
AUTH_STEP_UP_TRIGGERS = ['high-risk-action', 'export-sensitive-data', 'admin-override', 'incident-response', 'after-inactivity'].
Roles — diff, risk scoring, dry-run harness, recertification#
roles/roles.ts is the most algorithmically dense identity module. A tenant
role template is diffed against the canonical baseline by
diffTemplateAgainstCanonical, which classifies each capability's
PermissionDiffEntry.delta as unchanged, granted-by-tenant,
revoked-by-tenant, scope-narrowed, or scope-widened, and assigns a risk
weight per the constants:
| Delta | Risk weight constant |
|---|---|
granted-by-tenant |
RISK_WEIGHT_GRANT = 3 |
scope-widened |
RISK_WEIGHT_SCOPE_WIDEN = 4 |
revoked-by-tenant |
RISK_WEIGHT_REVOKE = 1 |
scope-narrowed |
RISK_WEIGHT_SCOPE_NARROW = 1 |
The summed riskScore drives requiresReviewerSignoff, which trips when the
underlying canonical role isElevated or when the score reaches
REVIEWER_SIGNOFF_THRESHOLD = 6. For example, two unapproved scope-widenings
(4 + 4 = 8) force a reviewer, while a single grant (3) does not. The weighting
is deliberately asymmetric: widening scope is the riskiest single act, and
loosening permissions costs more than tightening them.
The module also ships the dry-run role test harness the feature spec
mentions only generically. runDryRun takes a role's effective capabilities and
a list of DryRunAction { capabilityId, scope, expected }, evaluates each
against the allow/deny and scope-restriction logic, and returns
DryRunResult { action, actual, matched } so an operator can assert
allowed/denied actions and surface unexpected escalations before deploying a
role. Alongside it is a real recertification state machine —
ASSIGNMENT_STATES = ['pending','approved','active','expired','recertified','revoked']
with transitionAssignment enforcing legal transitions, an approver-required
guard (the requester cannot approve their own assignment), and a past-expiry
guard.
Audit explorer — a per-tenant hash chain#
audit-explorer/audit-explorer.ts does more than "tamper-evident storage." Each
AuditLogRecord carries a contentDigest and a priorDigest — the digest of
the previous record in the same tenant's log — forming a per-tenant hash
chain. verifyAuditChain walks a segment and reports the first record where a
priorDigest fails to match the prior contentDigest, so tampering or deletion
is detectable, not merely discouraged. Records carry
severity ∈ {info, warning, critical}. applyAuditFilter filters by actorId,
actorRole, tenantId, action, severity, time window, targetObjectClass,
policyBinding, and correlationId, and is viewer-scoped through
ExplorerViewer { role: 'platform-operator' | 'tenant-admin', tenantId }: a
tenant-admin viewer can never see another tenant's records. Export bundles
(AuditExportBundle) carry chain-of-custody metadata and a record of redactions
applied; applyRedactions supports remove / mask / pseudonymize modes for
privacy-sensitive fields.
Bulk operations — dry-run, residency counting, per-kind export matrix#
bulk-ops/bulk-ops.ts defines
BULK_RESOURCE_KINDS = ['users','rosters', 'classes','courses','content','sources','taxonomy','persona-assignments', 'entitlements']
and BULK_OP_KINDS = ['import','export','action']. dryRunImport produces a
BulkDryRunReport that tracks per-row validation errors, conflict/add/update
counts, a residencyViolationCount, and a readyToCommit flag that is true
only when there are zero errors and zero residency violations. Staged commit
(planStagedCommit) records committed, failed, and skipped row IDs plus a
resume index; bulk actions are gated by gateBulkAction, which blocks on a
missing rationale or an over-safetyLimit selection.
Export is its own module, bulk-ops/bulk-export.ts. The supported formats are
BULK_EXPORT_FORMATS = ['json','csv','oneroster','xapi','caliper'] and the
exportable resource kinds are
BULK_EXPORT_RESOURCE_KINDS = ['content','rosters','users','audit','rights','metis'].
Crucially, the docs flatten what is actually a per-resource-kind support
matrix (BULK_EXPORT_FORMAT_SUPPORT) — not every format is valid for every
kind:
| Resource kind | Allowed formats |
|---|---|
content |
json, csv, oneroster |
rosters |
json, csv, oneroster |
users |
json, csv, oneroster |
audit |
json, csv, xapi, caliper |
rights |
json, csv |
metis |
json, csv |
executeBulkExport enforces this (format-not-supported), checks residency
(residency-violation), chunks records (default 5000/file), and emits a signed
BulkExportManifest with per-file SHA-256 hashes and a manifest hash. The hash
function is dependency-injected (hashSha256) so the module is
deterministically unit-testable and can be backed by node:crypto at runtime
without coupling — a documented seam, not a fake. verifyManifest recomputes
file and manifest hashes to confirm integrity.
Integrations — API keys, webhooks, connectors#
integrations/api-keys.ts stores only a key prefix and a tokenHash — the
full key is never persisted, per the in-code comment. authenticateApiKey
returns one of four explicit failure reasons: unknown-key, revoked,
expired, ip-not-allowed. The module also evaluates a per-minute sliding
rate-limit window, rotates keys with a grace period (rotateApiKey), and
revokes them (revokeApiKey).
integrations/webhooks.ts drives outbound delivery.
WebhookDelivery.status ∈ {pending, in-flight, success, failed, dead-letter},
and matchesTopicPattern supports three forms — an exact match, a prefix.*
subtree match, and a bare * wildcard. Transient failures retry with
exponential backoff plus jitter until maxRetries, then move to dead-letter;
replayDelivery resets a delivery for operator-initiated replay from the
webhook simulator route.
integrations/connector-registry.ts defines
CONNECTOR_KINDS = ['lms', 'calendar','identity','payment','telemetry','byom-ingest','slack','teams'].
This is the authoritative, coarser taxonomy: the prose elsewhere over-enumerates
LMS sub-flavors (LTI 1.3, LTI Advantage, SCORM) and named calendar providers
(Google, Apple, Outlook) as if they were distinct connector kinds. At the
registry level, they are not. SCORM and the LTI variants are configuration
sub-flavors of the single lms kind; named providers are configuration of the
calendar kind. Each ConnectorRegistration carries an hourly error budget, a
circuit breaker (evaluateCircuitBreaker opens on budget-exhausted), version
pinning, and an upgradeConnector path that refuses unknown or deprecated
versions.
Notifications, help center, status page#
notifications/notifications.ts defines
NOTIFICATION_CHANNELS = ['in-app', 'push','email','sms','voice','webhook'] —
and yes, voice is a real enum member, not aspirational. Severities are
NOTIFICATION_SEVERITIES = ['info', 'standard','important','critical'].
decideDelivery resolves per-channel preferences (with per-domain and
per-persona overrides), quiet hours (overridden only by critical),
bounce/suppression state, minimum-severity gates, and
digestMode ∈ {off, hourly, daily, weekly} — returning an explicit per-channel
verdict (deliver, skip-disabled, skip-quiet-hours, skip-below-severity,
skip-bounced, skip-suppressed, defer-to-digest). Templates carry localized
perLocale subject/body, required-variable validation, accessibility lint tags,
and an approval state machine.
help-center/help-center.ts defines
HELP_AUDIENCES = ['customer','admin', 'creator','partner'] and
HELP_KINDS = ['article','walkthrough','video-script', 'faq','changelog'].
searchHelp is role-aware: it filters by viewer audience and applicable
roles, scores contextual matches against the current domain and surface path,
and falls back across locales. A feedback loop (aggregateFeedback) rolls up
helpful/unhelpful counts and missing-subject tallies to feed the editorial
backlog.
status-page/status-page.ts defines
COMPONENT_STATES = ['operational', 'degraded','partial-outage','major-outage','maintenance']
and rolls up component health to an overall and per-region state. An
IncidentRecord carries a thread of publicUpdates with
status ∈ {investigating, identified, monitoring, resolved}; banners are
targeted by severity, audience tenant/role, and locale; and postmortems carry a
visibility ∈ {public, tenant-scoped, internal} enforced by
postmortemVisibleTo.
Developer portal — what "integration certification flows" actually means#
The integrations area's "developer documentation, OpenAPI specs, code examples,
sandbox tenants, and integration certification flows" are backed by a real
library, libs/oshun/developer-portal, whose src/index.ts exports five
modules: openapi-builder, code-example-generator, sandbox-tenant-policy,
certification-suite, and developer-docs-registry.
openapi-builder.tsgenerates an OpenAPI 3.1.0 document (openapi: '3.1.0') directly from Zod schemas viazodToOpenApiSchema, emitting both security schemes — so the public API docs stay in sync with the actual request/response contracts instead of drifting.certification-suite.tsis the concrete "certification flow." It definesCERTIFICATION_CHECK_KINDS— twelve deterministic checks includingauth-token-rotation(must keep authenticating within 60 minutes of a key rotation),webhook-signature-verification(must validate theX-Oshun-SignatureHMAC-SHA256 header),webhook-replay-rejection,rate-limit-respect,idempotency-key-honor,pagination-cursor-correctness,partial-failure-envelope-parse,residency-region-honor,event-deduplication,oauth-pkce-on-public-client,scopes-least-privilege, andprivacy-deletion-fanout. Each check has a gate level (required/recommended/optional) and a remediation URL; the suite reports a pass-rate and an overall gate verdict before an integration is listed as "Oshun-certified."code-example-generatorandsandbox-tenant-policyback the code examples and sandbox tenants surfaced in the docs.
The admin console exposes these at api/admin/developer-portal/articles,
api/admin/developer-portal/certification/checks, and
api/admin/developer-portal/sandbox-policies.
Oshun Admin Mobile#
Oshun Admin mobile V1 is a real Expo app (apps/oshun/admin-mobile, with
app.json and eas.json). It is not a thin web wrapper — it has its own module
tree under src/: urgent-queue, review, auth, incidents, offline,
notifications, shell, support, and supporting accessibility, design,
navigation, telemetry, and theme modules.
It includes a secure mobile admin shell, login, step-up auth, an urgent-queue home, an assignment overview, push-driven escalation, and low-connectivity-safe queue behavior. The home surface uses compact review cards with severity and SLA state. Where mobile action is safe, an operator can approve, reject, request changes, escalate, reassign, acknowledge an incident, handle high-priority support cases, and process creator/reviewer notifications. The experience is tuned for the constraints of a phone on call: compact evidence previews, incident summaries, degraded-connectivity and retry states, handoff states, and deliberate haptics, motion, and confirmation behavior. Coverage spans mobile E2E, accessibility, telemetry, device-specific visual QA, and urgent-queue signoff.
The security posture is visible in the module structure:
auth/—adminMobileSessionController.ts,adminMobileStepUp.ts, andexpoSecureKeyValueStorage.ts(session tokens go to the device secure store, and sensitive actions trigger a step-up challenge), plus the React context providersAdminMobileAuthContext.tsxandAdminMobileSessionRuntimeProvider.tsx.review/— separate queue, routing, decision, and evidence clients (adminMobileReviewQueueClient.ts,adminMobileReviewRoutingClient.ts,adminMobileReviewDecisionClient.ts,adminMobileReviewEvidence.ts).notifications/— an Expo escalation router (adminMobileEscalationRouter.ts) with a typedadminMobileEscalationPayload.tsand a creator/reviewer notification path.urgent-queue/,incidents/, andoffline/— the urgent-triage home, incident acknowledgement, and low-connectivity behavior.
Honest status#
| Capability | Status |
|---|---|
| Admin web console (274 components, deep BFF route surface) | Implemented |
@oshun/tenant-console 9-module substrate (identity/roles/audit/bulk/integrations/notifications/help/status/tenant-model) |
Implemented (deterministic pure functions) |
| Hash-chained tamper-evident audit, per-tenant viewer scoping | Implemented |
| Role diff + risk scoring + dry-run harness + recertification state machine | Implemented |
| Bulk dry-run, residency counting, per-kind export matrix, signed manifests | Implemented |
| API keys (prefix+hash), webhooks (topic-pattern/retry/dead-letter), connectors | Implemented |
| Developer portal: OpenAPI 3.1 from Zod, 12-check certification suite | Implemented |
| Admin mobile (Expo): urgent queue, step-up auth, escalation router, offline | Implemented |
| SSO/SCIM as deterministic pure functions; OIDC live login | Fail-closed (503) when oidcClientId/oidcJwksUrl absent — no live SAML/OIDC parser in lib |
| Tenant-admin SSO config UI (metadata-XML/OIDC-discovery parse, transform matrix, auth-policy panel, sandbox-probe verdicts) | Partially covered end-to-end |
No part of this area reads as fabricated. The two honest gaps are an intentional fail-loud OIDC seam and a partially-covered config UI — both stated above, neither a hidden stub.
Related#
- Tenant, Institution, and Operator Toolkit — the strictly-isolated per-tenant counterpart over the same nine-module library.
- Review, Compliance, and Trust & Safety — the queue semantics and high-risk approval gates the console drives.
- Support, Entitlements, Billing, and Customer Operations — the support and entitlement workspaces.
- Privacy, Consent, Data Portability, and User Controls — DSAR exports, deletion jobs, and redaction.
- Agent Registry, Job Orchestration, and Multi-Agent Plans — the Agentic AI operations dashboards' backing runtime.
- Architecture, Platform Foundations, and Security — where the admin apps and BFF sit in the platform.
- Hub: ../features.md.