The deployed migration crash-looped PocketBase (502 on every /api call)
for two reasons, both verified against a production-like DB locally:
1. team_members is referenced by relation fields (team_member_id) in
micro_learning_completions and theme_session_completions, so
app.delete(team_members) was blocked — and the swallowed error left
the create to fail with "Collection name must be unique". Fix: before
dropping team_members, convert those relation fields to plain text
(consistent with how user_id is stored elsewhere) and rebuild the
theme_session_completions unique index on the text column.
2. An enabled OAuth2 provider with empty clientId/clientSecret fails
validation and aborts the migration. Fix: attach the OIDC provider
only when ENTRA_CLIENT_ID and ENTRA_CLIENT_SECRET are present;
otherwise create the auth collection without a provider.
Verified locally with the muchobien/pocketbase image: boots clean both
without Entra env (provider-less) and with env (auth-methods returns the
Microsoft OIDC provider).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the client-side PIN login with real authentication against Azure
Entra ID via PocketBase's built-in OAuth2 (OIDC) flow. Every Azure user is
auto-provisioned as a team_member on first login.
- pb_migrations: team_members becomes a PocketBase auth collection (OIDC
provider configured from env); all collections require @request.auth.id
- pb_hooks: provisioning of role (ENTRA_ADMIN_EMAILS allow-list) and
enrollment_status, with admin-role re-sync on every login
- frontend: "Sign in with Microsoft" login, pb.authStore-based session,
TeamManager manages roster/roles (no PIN/manual create)
- infra: Entra env wiring + pb_hooks mount for dev (Labs) and prod
- src/lib/azureAuth.js + tests for the canonical role/allow-list logic
- docs/auth-spec.md
Knowledge base, generated tests and micro-learnings are left untouched.
Existing PIN users are dropped (no migration required, per sign-off).
Closes#16
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a /topic-test route where learners can take a 5-question test on any
eligible topic at any time. Questions come from a shared, admin-curated
question_bank — no LLM calls on the user path. Points feed the existing
leaderboard with a 10pt/topic/week cap (per ISO week) so the bank can't be
farmed, and repeats from a thin bank yield 0 points.
- New PB collections: question_bank, on_demand_attempts (+ migrations and
setup-pb-collections.mjs entries).
- db.js: un-deprecates getQuizBank/setQuizBank against question_bank so the
existing admin TestManager panel becomes functional again; adds
saveOnDemandAttempt, getOnDemandPointsThisWeek, getUserSeenQuestionIds,
getAllOnDemandAttempts, isoWeekKey.
- testService.js: getEligibleTopicsForOnDemand, startOnDemandTest (unseen-
first draw), finishOnDemandTest (cap enforcement + leaderboard upsert).
- New TopicTest page, route, and nav entry; weekly test flow untouched.
- Leaderboard folds on-demand 100%s into perfect-score count and merges
on-demand attempts into the recent activity feed.
- Spec: docs/on-demand-tests-spec.md with ADRs and extension points.
- Tests: 5 new vitest cases covering eligibility, draw fallback, scoring,
partial cap, and exhausted cap (85/85 total).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New theme_sessions + theme_session_completions PocketBase collections
- Generate a detailed per-week theme summary covering every topic in the
week's curriculum slot via EMIT_THEME_SESSION_TOOL; cache per
(curriculum_version, week_number)
- Replace Leren.jsx "This Week's Topic" card with "This Week's Theme
Session" that opens the new ThemeSessionView; per-topic micro-learnings
remain reachable as optional practice from inside the session
- Group the Knowledge Library by topic.theme, pin the current week's
theme on top, and badge topics already covered by the active session
- Mark week complete when the user finishes reading the theme session
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add dependency-free TF-IDF retrieval (src/lib/retrieval.js) with NL+EN
stopwords and a WeakMap-cached index.
- Rewrite buildKbContext to ship the top-K relevant topics + verbatim-
mentioned ids only, filter relations to the included set, and append a
[kb_hash: <8 hex>] suffix so the ephemeral prompt cache busts when the
graph changes. Returns { context, retrievedTopics, allTopics }.
- Add LOOKUP_TOPIC_TOOL and drive useChat through callLLM directly with a
multi-hop tool_result loop capped at 3 hops; preserve Anthropic-provided
tool_use ids through callLLM so the loop can echo correct tool_use_id.
- Truncate R42 history to the last 12 turns and prepend a single
"(earlier conversation truncated)" assistant message.
- Set R42 chat defaults: temperature 0.3, maxTokens 2048.
- Add pb_migrations/1780500002_created_llm_calls.js (the best-effort
logger in callLLM was already wired) and a new Admin → Diagnostics
view showing the last 100 calls with token usage, cache-hit rate, and
USD cost from a local Anthropic price table.
- Finalize AI_PIPELINE_HARDENING_PLAN.md: mark Phases 1–5 shipped and
Phase 6 (eval harness) explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace stateless one-shot extraction with a stateful, paced, cancellable
pipeline. Six subtasks:
- 3.1 Sentence-aware chunking with 800-char overlap (was paragraph-only
at 4000 chars). Hard-split fallback for runaway sentences.
- 3.2 Stateful extraction: chunks 2+ receive an "already-extracted topic
IDs" hint capped at 200 IDs, so the model reuses IDs instead of
inventing variants like software-developer vs software-engineer.
- 3.3 Token-bucket limiter in llmRetry.js (extractionLimiter, 5 req/min).
callLLM awaits the limiter before fetch; 429+Retry-After calls
pauseUntil. Replaces hard setTimeout(12000) and setTimeout(15000).
- 3.4 relevance_locked column on topics — admin edits to relevance are
sticky across re-extraction. Migration + merge respects the flag +
unlock checkbox in KnowledgeGraph edit form.
- 3.5 Unify relation vocabulary — handbook prompt no longer mentions
legacy "executes"; one-shot migration rewrites existing executes rows
to executed_by with source/target swapped.
- 3.6 Cancellation — Cancel button on UploadZone wired to an
AbortController threaded into callLLM; aborted runs persist status =
"cancelled" rather than "failed".
Tests: 16 new unit tests for chunkText, buildKnownIdsHint, and
createLimiter. All 61 tests pass, 0 lint errors, build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>