The
apps/metis/area: the TypeScript/Expo client-and-edge tier for Metis, the Oshun learning platform — a learner web PWA, an operator admin console, a mobile app, an HTTP API gateway, and a background-job worker, all sitting in front of a separate Python content backend.
What this area is#
Metis is Oshun's education product: a "core-six" learning platform centred on
philosophy, religion, psychology, neuroscience, anthropology, and astronomy,
with grounded (source-cited) tutoring, assessments, and course authoring. The
five Nx projects under apps/metis/ are the application tier — the
user-facing surfaces and the edge services — written in TypeScript and Expo.
They are not where course content, retrieval, or the tutoring models live: those
sit in a separate Python backend that every one of these projects ultimately
talks to. The gateway's default upstream is http://localhost:8000
(METIS_PYTHON_BACKEND_URL in apps/metis/api-gateway/src/config.ts), and the
web/admin Next.js apps proxy through their own server routes to that same
backend.
The split is along surface and responsibility. @metis/web is the learner PWA
(Next.js, port 3020). metis-admin is the operator/moderation/observability
console (Next.js, port 3021). @metis/mobile is the Expo / React Native app.
metis-api-gateway is a dependency-free Node http reverse proxy that fronts
the Python backend with auth, rate-limiting, caching, transforms, a circuit
breaker, and a WebSocket relay. metis-worker is the background-job runtime
(export, notifications, analytics, cleanup, reports, content
transcode/thumbnail, search reindex) with its own queue, cron-style scheduler,
and health server.
These projects relate as a fan-in onto the Python backend rather than as a deep
internal dependency chain. The two Next.js apps each carry a thin server-side
proxy (src/lib/server/metis-proxy.ts, admin-proxy.ts) that forwards browser
requests to the backend; the standalone metis-api-gateway is the
infrastructure-grade equivalent for non-browser callers. The worker is an
independent process that the backend (or the gateway's job-queue view) feeds.
The web and mobile apps additionally compose sibling Oshun libraries —
@aja/domain-motion-pipelines (embodied/movement instruction hooks),
@kalika/core and @kalika/sdk (math exploration), and @metis/models /
@metis/integrations — so a Metis lesson can host a Kalika math explorer or an
Aja movement practice path.
How it fits the wider system#
The consumers are end users (learners on web and mobile, operators in admin) and
infrastructure (the gateway is consumed by clients/services that need a hardened
entry point to the Python backend). The hard boundary is the TypeScript-app-tier
vs. Python-backend line: none of these projects own the domain data model — they
validate, shape, cache, and present it. That boundary shows up concretely in the
data libraries, whose API response types are snake_case mirrors of the backend
(e.g. MetisCourseLessonApiResponse in apps/metis/web/src/lib/course-data.ts,
the Api* interfaces in apps/metis/admin/src/lib/admin-client.ts) that are
mapped into the apps' camelCase view models. Cross-domain integration is via the
proxy's forwarded control headers (x-metis-isis-*, x-metis-lilith-* in
metis-proxy.ts), which thread Isis course-generation control and Lilith
pedagogical-tone policy through to the backend. Walk the "used by" / "depends
on" edges on any node below to see the exact wiring.
Entity catalog (5)#
The 5 tracked Nx projects in metis, each a code-linked entity node — package, type, source path, declared targets, and its internal dependency graph (depends-on / used-by, resolved from the package manifests, §6/§8), read from the project graph. Grouped by architectural layer; walk the dependency links to travel the system. 5 of these carry an authored deep-dive (what / why / how it fits); the rest are generated scaffolds awaiting one.
app (2)#
A dependency-free reverse proxy for the Python backend, built on Node's http
module (apps/metis/api-gateway, default port 3000). MetisApiGateway in
src/app.ts composes an onion-model middleware chain — logging, rate-limiting,
JWT auth-gating with per-method least-privilege permissions, response cache, and
camel/snake transform — then matches against domain route tables
(DEFAULT_ROUTES in src/config.ts: courses, assessments, tutoring, content,
users, analytics, admin, search, recommendations, notifications). It forwards
upstream through a BackendProxy with a real circuit breaker
(src/proxy/backend-proxy.ts, CLOSED/OPEN/HALF_OPEN states) and exposes
/health, /ready, and /api/v1/gateway/status plus a WebSocket relay
(src/realtime/websocket-proxy.ts). It handles CORS, graceful shutdown, and
body-size limits. Each middleware and route has a co-located *.test.ts. Fully
implemented edge service.
LogLevel11HttpMethod11DEFAULT_ROUTES11createDefaultConfig11resolveUpstreamUrl11findRouteForPath11isLogLevelEnabled11MetisApiGateway26createMetisApiGateway26MetisPermission33MetisRole33ROLE_PERMISSIONS33createAuthMiddleware33requirePermission33 +64 moreThe background-job runtime (apps/metis/worker). MetisWorkerService in
src/main.ts orchestrates a priority JobQueue (src/queues/job-queue.ts), a
cron-style JobScheduler (src/queues/scheduler.ts with DEFAULT_SCHEDULES),
a processor registry, a concurrency-bounded polling loop, heartbeat and
memory-pressure monitoring, graceful shutdown, and an HTTP health server
(/health, /ready, /stats). It ships processors for course export, single
and batch notifications, analytics aggregate/snapshot, cleanup, reports, and
content transcode/thumbnail/search-reindex/assessment-generation (src/jobs/*).
A deliberate honesty seam runs through it: processors that need a real data
source fail closed rather than fabricate — e.g. course-export.ts refuses
to export without an injected MetisCourseLoader ("refusing to fabricate course
content"), and the analytics/notification processors report failure rather than
fake metrics or delivery until a real backend transport is wired (see the
comments in registerDefaultProcessors). The orchestration is fully implemented
and tested; the default-wired processors are intentionally inert until their
backing stores are injected.
JobType40JobPriority40JobStatus40WorkerStatus40ExportFormat40ImportMergeStrategy40NotificationChannel40ReportFormat40ThumbnailFormat40SearchEntityType40DigestType40Granularity40CleanupTargetType40createJobId40 +35 moreunclassified (3)#
The Expo / React Native learner app (apps/metis/mobile). app/_layout.tsx and
app/index.tsx are the Expo Router entry; src/MetisMobileHome.tsx renders the
learner home against a typed learning model in src/learning-model.ts. That
model defines 19 required mobile surfaces (browse, course detail/play, study,
tutor, assessment, authoring, saved, queue, resume, profile, goals, pace,
preferences, reminders, exports, downloads, sharing) and five run-states, with
resolver functions (resolveMetisMobileResumeState, …SyncState,
buildMetisMobileSurfaceInventory, coverage/gap finders) and tests. It composes
@aja/domain-motion-pipelines for embodied-instruction hooks. Honest scope
note: the screen is driven by a hand-authored demo learner state
(metisMobileLearnerState, learner "Maya") rather than live backend data — the
surface inventory, navigation actions, and offline/grounding semantics are real
and tested, but the data is a fixture, and there is a single home screen plus a
Maestro smoke flow (e2e/flows/smoke.yaml) rather than the full navigable app.
The learner-facing PWA (apps/metis/web, Next.js App Router, port 3020). It has
the full learner journey as real routes — courses, course play, assessments,
tutoring, create/authoring, progress, study, saved, notifications, settings,
login (src/app/*) — backed by ~30 domain libraries under src/lib
(course-data, assessment-data, profile-data, progress-data,
tutoring-data, grounded-sources, plus genuine client logic like the
kalika-exploration math solver and iris-study-continuity resume planner),
each with co-located tests. Server routes (src/app/api/metis/[...path],
src/lib/server/metis-proxy.ts) proxy to the Python backend and forward Isis
course-generation and Lilith pedagogical-tone control headers; the data libs
parse the backend's snake_case responses into client types. It is a real PWA
(public/sw.js, manifest.json, PwaBootstrap), has Lighthouse budgets and a
high-value-route performance test, an extensive Playwright suite, and composes
@kalika/*, @aja/*, @metis/models, and @metis/integrations. Fully
implemented.
The operator console (apps/metis/admin, Next.js App Router, port 3021). It is
a deep moderation / oversight / observability surface: routes for analytics,
audit trail, complaints, content moderation, courses, incidents, monitoring,
operations, oversight, review queues, source-rights, and user management
(src/app/*/page.tsx). All backend access flows through AdminApiClient
(src/lib/admin-client.ts, ~2,400 lines) which calls a /api/admin route that
server-side-proxies to the Python backend (src/lib/server/admin-proxy.ts), and
maps richly-typed snake_case API payloads (concept-graph validation, runtime
rollout decisions, retrieval inspections, cohort mastery heatmaps, source-rights
postures) into camelCase view models. This is a fully implemented app with a
large Playwright e2e suite (e2e/ — moderation, incident command, audit trail,
WCAG AA signoff, keyboard-focus paths) and per-page vitest tests, not a
scaffold.