# Admin Products — Web and Mobile

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](./tenant-toolkit.md): 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](./tenant-toolkit.md). Both
> surfaces are thin Next.js rendering layers over the same nine
> `@oshun/tenant-console` modules; 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](../TODOS.md); the
> apps themselves live under `apps/oshun/admin`, `apps/oshun/admin-mobile`, and
> `apps/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](./agentic-registry-jobs-plans.md)):
  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
`externalId`s, 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`
`TenantFederationEdge`s. 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.ts`** generates an **OpenAPI 3.1.0** document
  (`openapi: '3.1.0'`) directly from Zod schemas via `zodToOpenApiSchema`,
  emitting both security schemes — so the public API docs stay in sync with the
  actual request/response contracts instead of drifting.
- **`certification-suite.ts`** is the concrete "certification flow." It defines
  `CERTIFICATION_CHECK_KINDS` — twelve deterministic checks including
  `auth-token-rotation` (must keep authenticating within 60 minutes of a key
  rotation), `webhook-signature-verification` (must validate the
  `X-Oshun-Signature` HMAC-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`, and
  `privacy-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-generator`** and **`sandbox-tenant-policy`** back 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`, and
  `expoSecureKeyValueStorage.ts` (session tokens go to the device secure store,
  and sensitive actions trigger a step-up challenge), plus the React context
  providers `AdminMobileAuthContext.tsx` and
  `AdminMobileSessionRuntimeProvider.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 typed
  `adminMobileEscalationPayload.ts` and a creator/reviewer notification path.
- **`urgent-queue/`**, **`incidents/`**, and **`offline/`** — 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](./tenant-toolkit.md) — the
  strictly-isolated per-tenant counterpart over the same nine-module library.
- [Review, Compliance, and Trust & Safety](./review-trust-safety.md) — the queue
  semantics and high-risk approval gates the console drives.
- [Support, Entitlements, Billing, and Customer Operations](./support-billing.md)
  — the support and entitlement workspaces.
- [Privacy, Consent, Data Portability, and User Controls](./privacy-consent-portability.md)
  — DSAR exports, deletion jobs, and redaction.
- [Agent Registry, Job Orchestration, and Multi-Agent Plans](./agentic-registry-jobs-plans.md)
  — the Agentic AI operations dashboards' backing runtime.
- [Architecture, Platform Foundations, and Security](./platform-foundations-and-security.md)
  — where the admin apps and BFF sit in the platform.
- Hub: [../features.md](../features.md).
