First Microsoft logins failed with 400 "email: Cannot be blank": Graph's
/oidc/userinfo endpoint omits the email claim for accounts without a
mail attribute, and team_members requires an e-mail (it also drives the
ENTRA_ADMIN_EMAILS role mapping). Reproduced against a mock Entra with
PocketBase v0.30.4; the user-supplied response body matched the
missing-email fingerprint exactly.
- provider config (reconciler + migration fast-path): userInfoURL is now
empty, so PocketBase reads the claims from the Entra id_token, which
always carries preferred_username (UPN) and email when available. The
#18 reconciler flips the already-deployed Labs provider automatically
on the next boot — no migration needed.
- pb_hooks/entra_oidc.pb.js: onRecordAuthWithOAuth2Request hook falls
back to the lowercased UPN when the email claim is absent. Guest UPNs
(ext_user#EXT#@tenant...) are excluded explicitly — "#" is RFC-valid
in an e-mail local part, so both a naive regex AND PocketBase's own
validation would accept them (caught by test V5). The hook also logs
every failed OAuth2 attempt with the underlying error to the container
log, which PocketBase otherwise only writes to its internal logs db.
Verified with a switchable mock-Entra matrix (8/8): no-email→UPN e-mail
+ allow-list role, email claim wins when present, no duplicate on
re-login, guest UPN yields a clean validation error, anonymous REST
create stays rejected, both log lines present. Regression: issue-22
matrix 9/9 (baseline pinned to 1a1351d now that #23 is merged), DoD
harness 15/15, npm test 112/112.
Closes #24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Respellion Learning Platform
An internal AI-powered learning platform that keeps Respellion employees up to date with the company's evolving knowledge base.
Features
- Onboarding — On first login each employee enrolls into the curriculum with one tap. Their 26-week cycle starts then and there.
- Weekly Learning Station — Each employee gets a topic for their current curriculum week, drawn from the active 26-week schedule (with a deterministic hash fallback when no curriculum is active). They engage via micro-learnings (concept explainer, scenario quiz, flashcard set) and can also generate long-form formats (Article, Slides, Infographic) on demand.
- Weekly Test — A 5-question AI-generated quiz for the week's topic. Results feed the leaderboard.
- Leaderboard & Gamification — Points for correct answers, badges for milestones (First Steps, Veteran, Perfectionist).
- R42 Chatbot — An always-available AI assistant (Claude) grounded in the knowledge graph via local TF-IDF retrieval. It can propose graph updates that admins approve or reject.
- Admin Panel — Manage the knowledge graph, upload source files, review/refine generated content, generate the curriculum, and manage the team.
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | React 19 + Vite 8, React Router 7 |
| Styling | Vanilla CSS (custom properties) + Tailwind v4 utilities |
| Animations | Framer Motion |
| Icons | Lucide React |
| Graph viz | D3.js (admin knowledge graph only) |
| Backend / DB / auth | PocketBase (self-hosted, SQLite) |
| Retrieval | Local TF-IDF (no Qdrant, no embeddings API) |
| AI | Anthropic Claude (via Caddy reverse proxy) |
| Infra | Docker + Caddy; Ansible playbooks under infra/ |
Claude models (by tier): fast = claude-haiku-4-5-20251001, standard = claude-sonnet-4-6, reasoning = claude-opus-4-7.
Getting Started (local dev)
# 1. Install dependencies
npm install
# 2. Start PocketBase
./pocketbase.exe serve
# 3. Bootstrap collections (first run only)
node scripts/setup-pb-collections.mjs
# 4. Start the dev server
npm run dev
Set ANTHROPIC_API_KEY in your environment — the Vite dev server proxies /api/anthropic and injects the key server-side (see vite.config.js). PocketBase migrations in pb_migrations/ apply automatically when the binary starts.
Deployment (Docker)
docker compose up -d
The Caddyfile handles:
- SPA fallback routing
/api/anthropic/*→ Anthropic API (server-side API key injection)/api/*and/_/*→ the PocketBase sidecar
Key Files
| File | Purpose |
|---|---|
src/lib/llm.js |
Core Anthropic wrapper (callLLM): tiers, retry, schema validation, telemetry |
src/lib/db.js |
All PocketBase data access |
src/lib/extractionPipeline.js |
Uploaded file → knowledge-graph extraction |
src/lib/learningService.js |
On-demand content generation (article / slides / infographic) |
src/lib/microLearningService.js |
Micro-learning generation (3 types) |
src/lib/curriculumService.js |
26-week per-user curriculum engine |
src/lib/retrieval.js |
TF-IDF retrieval for R42 |
src/store/AppContext.jsx |
Global state; derives the user's curriculum week from their start date |
AI_AGENT.md |
Detailed context guide for AI coding agents |
Content Types
On-demand long-form content (the content collection), generated per type and merged into the cached object:
| Type | Key in DB | Description |
|---|---|---|
| Article | content.article |
Long-form reading |
| Slides | content.slides |
Slide deck with speaker notes |
| Infographic | content.infographic |
Visual summary with stats and steps |
The podcast type was removed. Do not re-add it.
Micro-learnings (the micro_learnings collection): concept_explainer, scenario_quiz, flashcard_set.
Documentation
CLAUDE.md— project overview and constraints for AI coding agents.AI_AGENT.md— full architectural guide (patterns, gotchas, decisions).docs/— subsystem specs (architecture, data model, ingestion, generation, curriculum, R42, frontend, gamification).
Description
Languages
JavaScript
95.6%
TypeScript
3.6%
CSS
0.8%