Conversation
- mark FEAT-006, FEAT-007, FEAT-009, FEAT-010 completed (PR #50, 2026-03-23) - note FEAT-005 partial overlap with FEAT-020 hybrid grounding mode - add FEAT-022 suggested questions (quick-start chips) - add FEAT-023 socratic interaction mode (per-course learning style)
Covers widget production-ready items (FEAT-016, FEAT-012, FEAT-004) plus
the instructor configuration backend endpoint. Splits course customization
into two categories: visual (Moodle block config, data-attrs) and behavioral
(namespaces.config JSONB, admin API via Moodle server-side).
Implementation order: WI-3 (PATCH /namespaces/config) -> WI-2 (document name
in citations) -> WI-1 (GET /conversations/{id}/turns JWT-scoped) -> WI-4
(widget data-attrs) -> WI-5 (widget conversation persistence).
…config (WI-3) Partial-update endpoint for the namespaces.config JSONB column, admin-scoped. The FEAT-020 read path (resolve_grounding_mode in vektra-shared) already consumes this column; this adds the missing write path. - Whitelist keys (grounding_mode only for v0.5.0). Unknown keys rejected 400 so upstream clients see typos immediately. - Null value removes the key, falling back to the env default. - Partial merge preserves unrelated existing keys. - New error codes ERR-ADMIN-005/006/007 documented in error-codes.md. - Documented in api.md. - Integration tests cover set, partial merge, null removal, unknown key, invalid value, 404, 403, and end-to-end via resolve_grounding_mode. Part of v0.5.0 widget-and-prof-config plan (see .s2s/plans/20260418-v050-widget-and-prof-config.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Students see chunk UUIDs as citations today. This adds the filename of the source document to each SourceRef so the widget can render "[1] lecture-07.pdf (0.82)" instead of "[1] 4a2f3b... (0.82)". - Extend SourceRef and SourceRefBody with document_name: str | None. - Helper _fetch_document_names(doc_ids) batches a lookup against source_documents.filename and returns an empty map on any failure (DB not initialised in tests, transient error). Pipelines continue returning responses even when enrichment fails; the widget already falls back to chunk_id when document_name is absent. - Both SimpleQueryPipeline and AdvancedQueryPipeline populate the field in execute() and execute_stream(). - vektra-learn CourseQueryResponse forwards the field so the learn widget gets it over SSE and JSON paths. Widget side is already prepared (vektra-learn/widget/src/chat-ui.js:227 uses src.document_name || src.chunk_id), so no JS change needed. Part of v0.5.0 widget-and-prof-config plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-004) Student-facing mirror of the admin turns endpoint. Needed so the widget can restore a conversation after a page refresh (WI-5). - Auth: dashboard JWT (same dependency as /query). Authorization is namespace-scoped: the conversation's namespace must match the namespace resolved from the token (namespace claim > course_id fallback). - 403 on namespace mismatch so the widget can distinguish "wrong course" from "deleted conversation" (404) and reset its local state. - Returns decrypted question/answer/created_at plus an empty sources array for v0.5.0. Source enrichment from query_traces is out of scope here. - Admin-only metadata (model, prompt_tokens, response_id) is intentionally not exposed in the student response. - New error codes ERR-LEARN-005 (404) and ERR-LEARN-006 (403) registered in vektra_shared.errors and docs/reference/error-codes.md. Part of v0.5.0 widget-and-prof-config plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…FEAT-016) Universities need their own title, color, welcome message without forking the widget. Five new optional attributes on the <script> tag: - data-title header text and button aria-label - data-primary-color accent color for buttons, user bubble, links - data-icon emoji or image URL for the floating button - data-welcome-message assistant message shown on first open - data-powered-by "false" hides the Vektra attribution footer Safety: - Title and welcome-message are rendered via textContent, never innerHTML. - Primary color is matched against a conservative whitelist (hex, rgb(), hsl(), named) before being injected as a CSS custom property; anything with quotes/semicolons/whitespace is silently dropped. - Icon URLs use <img src> (browser-sanitized); emoji/text uses textContent. - Hover states use filter: brightness() so a custom primary color still feels interactive without needing a second data-attr. No backend or API contract changes. Missing attributes keep the current v0.4.0 defaults, so existing deploys upgrade without visible changes other than the new "Powered by Vektra" footer (which can be turned off). Part of v0.5.0 widget-and-prof-config plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…004)
A page refresh currently loses the whole conversation. Students asking
follow-up questions about course materials can't afford that. This adds
tab-scoped persistence and a "New chat" button.
- sessionStorage key "vektra-conv:<course_id>" stores { conversation_id,
stored_at }. Tab-scoped avoids leaking conversations across students on
shared university lab computers; localStorage would have been wrong.
- Stale cutoff at 24h so tabs left open overnight don't silently resurface
yesterday's chat at the top of the screen.
- On widget init: read storage, call the new WI-1 endpoint, replay turns
into the chat panel before enabling input. 404/403/empty turns silently
abandon the stored id (widget recovers; no error shown to the user).
- Each successful query persists the server-assigned conversation_id so
that the next reload picks up where we left off.
- "New chat" button in the panel header clears storage and resets the
client conversation id so the next question opens a fresh thread. Works
as a recovery path if the server reassigns the id.
- ApiClient gains setConversationId(id) and getConversationTurns(id).
Part of v0.5.0 widget-and-prof-config plan. All five work items done;
widget bundle rebuilt and lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Mark FEAT-012 completed, FEAT-016 partial (data-attrs only), FEAT-004 partial (persistence + new chat, sources still empty). - Record all 5 WIs under an Unreleased v0.5.0 section so the release notes are ready when this branch merges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ream (WI-2) Missed in 192b62c. The advanced pipeline is the default in production (Combo D), and students use streaming (stream: true hardcoded in the widget client), so citations served over SSE never got the filename — the widget fell back to chunk UUIDs exactly in the code path that matters most. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three review findings addressed in one commit — all three changes are
cross-cutting and touching any one of them alone would force a stale
code/doc split.
1. WI-2 archived marker (REQ-057, plan gate): _fetch_document_names now
also reads deleted_at and appends "(archived)" to the filename when
the source document is soft-deleted. Citations stay traceable for
students without hiding the link to a removed asset.
2. WI-1 audit log (NFR-007): successful turns reads write a
learn_conversation_turns_read audit row via vektra_shared.audit so
decrypted conversation access is traceable. Uses the learn JWT
sentinel key_id (same one used for ensure_conversation) and captures
namespace, conversation_id, turn count, student_id, course_id.
3. WI-3 route naming: PATCH namespace config moves from
/api/v1/namespaces/{id}/config to /api/v1/admin/namespaces/{id}/config
to match the admin-endpoint convention established by DEBT-011's
/api/v1/admin/conversations/{id}/turns. Tests, api.md, and CHANGELOG
updated accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ype test
Second round of review fixes.
WI-4: two new optional attrs, data-powered-by-text and data-powered-by-url,
so universities can replace the footer text/link without touching the
widget source. Default behaviour unchanged ("Powered by Vektra" → vektralabs).
Custom URLs are whitelisted to http(s) and same-origin paths; javascript:
and data: URLs are rejected.
WI-5: restoreConversation is now awaited in init(), preventing a fast-typing
user from racing the fetch and seeing their first user message wiped out
when replayTurns arrives. Paired with a bounded AbortSignal.timeout(8s) on
getConversationTurns so a stalled backend cannot block widget init
indefinitely.
WI-3: integration test asserts a non-string value (42) is still rejected
with ERR-ADMIN-007. Guards the whitelist contract against future
permissive/type-coercing refactors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…3120130889) The existing `if request_id:` guard silently dropped the NFR-007 audit row whenever the RequestIdMiddleware wasn't wired (tests, early boot, misconfiguration). A successful 200 with no audit row is the worst possible failure mode for a compliance log. Synthesize a UUID fallback so every successful turns read leaves an audit trail unconditionally, and add a regression test covering the empty-state case. Renames the adjacent 501ish test to the accurate ERR-LEARN-001 name while we're in the file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…i dup)
Both .vektra-chat-btn:hover and .vektra-chat-send:hover hardcoded
${t.primaryHover}, overriding the CSS variable so a prof's
data-primary-color snapped back to theme-default blue on hover.
Switch both hover backgrounds to var(--vektra-primary, ${t.primaryHover})
matching the resting-state pattern: custom-color deployments get their
brand color darkened by the existing filter: brightness(0.92), theme
default still gets the handcrafted primaryHover shade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
asyncpg returns uuid.UUID objects for UUID columns and callers pass SearchResult.document_id (UUID) — both sides are UUID, so the lookup happened to work in practice. But the helper's signature left this coincidence implicit, and any path that passed a stringified id (or a future SearchResult field using str) would silently miss. Stringify on both ends: helper returns dict[str, str] with str(row[0]) keys, call sites do name_map.get(str(r.document_id)). Test fake updated to match the documented contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Batch of cosmetic / docs fixes from the PR #66 review. - widget: localize "Powered by" via new I18N.poweredBy key (en/it) (CR-3120130900) - widget: tidy the self-contradicting comment around the powered-by footer branch; behaviour unchanged (CR nitpick chat-ui.js:195-216) - widget: wrap init() with .catch(console.error) so any unexpected async rejection surfaces instead of becoming an unhandled promise rejection (CR nitpick index.js:122-197) - shared/types.py: correct SourceRef.document_name docstring — archived documents carry " (archived)" suffix; None is only for DB failures (CR-3120130909) - docs/reference/api.md: add document_name to the /api/v1/query response example so the contract matches the current payload (CR outside-diff api.md:283-297) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three low-priority items deferred during the PR #66 self-review, now tracked in the backlog so they don't get lost once the PR merges. - DEBT-017: consolidate namespace-resolution logic between `_resolve_namespace_from_token` and `course_query` in vektra-learn - DEBT-018: scope the widget `--vektra-primary` override to widget roots and dedupe the style node across instantiations - DEBT-019: add a unit assertion for `document_name` on the streaming sources payload to complement `test_execute_populates_document_name` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…round) Previous fix awaited restoreConversation() in init() but left the input field live for the 8s-bounded fetch. A fast user could type+send during the await: _handleSend would add the user bubble to the DOM, then when restore resolved, replayTurns would wipe messagesEl and the bubble would disappear with the message half-sent. Lock input with a ChatUI._restoring flag mirroring the existing _sending flag: setRestoring(true) before the fetch, setRestoring(false) in the finally branch. Both _handleSend and _handleNewChat guard on the flag, so user actions can't preempt the replay. Lock is a no-op when there's nothing stored to restore (_readStored returns null). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FEAT-012 helper _fetch_document_names is the only step between retrieval and LLM that touches Postgres, so it was the one place where an unexpected latency spike (slow replica, cold cache) could blame the LLM call instead. Emit a StepTrace with requested/resolved counts around all four call sites — SimpleQueryPipeline.execute and execute_stream, AdvancedQueryPipeline.execute and execute_stream — so citation enrichment time is explicit in QueryTrace. step_names expectation in test_execute_returns_response_and_trace updated to include "document_names" (now 9 steps). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follows the project convention where the per-submodule pyproject.toml carries `X.Y.Z-dev` throughout the cycle; even the `v0.4.0` release tag pins `0.4.0-dev`. uv.lock regenerated to pick up the new workspace versions.
- WI-3: PATCH /api/v1/admin/namespaces/{id}/config for instructor grounding mode
- WI-2: document_name in source citations (incl. "(archived)" marker for soft-deleted)
- WI-1: JWT-scoped GET /learn/conversations/{id}/turns, namespace-enforced, audited
- WI-4: widget white-label data-attrs (title, color, icon, welcome, powered-by ±text/url)
- WI-5: tab-scoped conversation persistence + history replay + "New chat" button
Reviews: 11/11 addressed (CodeRabbit 9 across 2 rounds, Gemini 3 — 1 dup)
Tests: 439 unit + 9/9 integration + backend smoke on Kalypso, browser smoke confirmed
Refs: FEAT-012 (completed), FEAT-016 & FEAT-004 (partial); DEBT-017/018/019 deferred
Introduces the show_sources flag end-to-end in the backend so instructors
can hide the citations block per course without touching the widget.
- vektra-shared: VEKTRA_LEARN_SHOW_SOURCES env (default true) + resolve_show_sources() matching the grounding_mode pattern (namespace JSONB config overrides env).
- vektra-admin: PATCH /admin/namespaces/{id}/config now accepts show_sources alongside grounding_mode. New ALLOWED_CONFIG_TYPES map supports bool validation (ERR-ADMIN-007 rejects non-bool, including int/string coercion attempts). Partial-merge semantics unchanged.
- vektra-app: expose learn_show_sources_default on app.state for the API layer to consume.
- vektra-learn: resolve show_sources per request (namespace config > env > default), return the value in CourseQueryResponse, and tag the SSE "sources" event so the streaming widget sees the same flag as the JSON path.
The API always returns the full sources list regardless of the flag;
visibility is a presentation concern for the widget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (FEAT-014) Resolution chain inside the widget: 1. data-show-sources attribute on the script tag (client-side force) 2. show_sources flag from the learn query response (server-resolved) 3. default true (legacy behaviour when both are absent) api-client.js forwards the server value as a second argument to the onSources callback for both the SSE and the JSON-fallback paths. index.js applies the resolution and only calls ui.addSources() when the effective value is true — keeping ChatUI agnostic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- api.md: document show_sources on PATCH /admin/namespaces/{id}/config, covering the new bool type and the fallback to VEKTRA_LEARN_SHOW_SOURCES on null.
- CHANGELOG: extend the v0.5.0 entry with the resolution chain and the rationale for keeping sources in the API payload.
- BACKLOG: mark FEAT-014 completed (Moodle-side integration deferred to the sibling vektra-moodle plan).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A hand-maintained whitelist alongside the validation maps was a single forgotten edit away from letting a new key bypass validation entirely. Derive it from the union of ALLOWED_CONFIG_VALUES and ALLOWED_CONFIG_TYPES so the relationship cannot drift, and add a module-level disjoint check that surfaces a duplicated registration at import time. Addresses Gemini inline 3127135275 and CodeRabbit nitpick on api.py:53-64. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Whitespace from server-side templating (e.g. " false ") was silently treated as truthy because the comparison ran on the unmodified attribute value. Trim before lowercasing so the explicit override behaves as documented. Addresses CodeRabbit inline 3127144765. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror test_namespace_config_patch_resolves_via_shared_helper for the FEAT-014 resolver: PATCH show_sources=False, then call the shared helper with default_value=True and assert the namespace override wins. Closes the loop between the write path (admin PATCH) and the read path (vektra_shared.namespace.resolve_show_sources) with no cache between. Addresses CodeRabbit nitpick on test_integration.py:778-859. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the read side of the namespace-config contract so upstream plugins (starting with the Moodle block edit form) can render "Use default" vs "Override" without re-implementing the resolver chain client-side. The response separates two views: - config: the raw JSONB stored in namespaces.config (empty when nothing has been PATCHed) - resolved: the value queries actually observe, computed via the same fallback chain (namespace > env > hardcoded default) used at runtime The resolved values are computed inline from the loaded ORM row to keep the endpoint to a single DB session — the standalone resolvers in vektra_shared.namespace remain for callers that hold only a namespace id and need to open their own session. The validation logic (enum membership for grounding_mode, isinstance for show_sources) is the same in both paths so behaviour cannot drift. Auth: admin scope. Errors mirror PATCH (404 ERR-ADMIN-005 on missing). 4 integration tests cover empty/stored/missing/scope. Documented in docs/reference/api.md and CHANGELOG. Motivated by the contract checklist in vektra-internal/moodle/feat14-moodle-contract-checklist.md (G1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous helper hardcoded the grounding_mode whitelist and built the return dict by hand, so adding a new key to ALLOWED_CONFIG_KEYS would have silently bypassed the GET endpoint's "Always includes every key" invariant — the same drift risk the round 1 review flagged for the whitelist itself. Iterate ALLOWED_CONFIG_KEYS, look up each key's validator in ALLOWED_CONFIG_VALUES / ALLOWED_CONFIG_TYPES, and accept a single defaults dict (asserted to cover every key). Adding a new whitelist key now flows through automatically; the assertion fails fast at runtime if the call site forgot to provide its default. Existing GET tests still pass — response shape is unchanged. Addresses CodeRabbit nitpick on api.py:570-595. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ibility
- Add VEKTRA_LEARN_SHOW_SOURCES env + resolve_show_sources() helper in vektra-shared
- Whitelist show_sources in PATCH /admin/namespaces/{id}/config with bool validation
- Attach show_sources to /learn/query JSON + SSE responses (sources list always returned)
- Wire data-show-sources widget override with onSources(sources, show_sources) callback
- Symmetric GET /admin/namespaces/{id}/config exposing stored + resolved view
Reviews: 5/5 addressed (Gemini 1, CodeRabbit 3, self 1)
Tests: 14 new (admin whitelist, response shape, SSE, resolution); full suite green
Refs: FEAT-014, ADR-0025, ARCH-047
- Bump version 0.5.0-dev -> 0.5.0 across 8 components - Promote CHANGELOG entry from [Unreleased] to [0.5.0] - 2026-04-25 - Refresh uv.lock for the version change Prepares the develop -> main release PR for v0.5.0 (widget production-ready + instructor configuration).
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (6)
📒 Files selected for processing (55)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request makes the chatbot widget production-ready by adding white-labeling support, tab-scoped conversation persistence, and configurable source visibility. Key backend additions include endpoints for per-namespace behavioral configuration and student-scoped conversation history retrieval. Feedback points out a NameError in the admin API due to missing imports and suggests hardening audit logging with fallback request IDs. Further improvements were identified regarding the optimization of document name resolution to reduce latency and the proper scoping of widget CSS variables to prevent global style leaks.
Two new DEBT entries from Gemini review on the develop->main release PR (#71). All medium/low severity, deferred from v0.5.0 release per zero-new-commit policy on release PRs: - DEBT-020 (medium): audit log skipped when request_id is missing on admin writes. patch_namespace_config and similar endpoints should synthesize a fallback uuid4 so NFR-007 holds even if the request-id middleware misbehaves. - DEBT-021 (low): _fetch_document_names adds a separate Postgres round-trip per query. The WI-2 plan called for a JOIN with source_documents inside the existing chunk-fetch query; v0.5.0 opted for a clarity-first separate call, consolidation tracked here. Three other Gemini comments on the same review map to existing items (DEBT-018 for :root scope) or were false positives (claimed missing imports already present at line 13). The four code-quality bot LOW nitpicks ("self_inner" in nested test fakes) are style noise on disambiguating closure-bound mocks; no entry created.
fvadicamo
left a comment
There was a problem hiding this comment.
Review feedback addressed:
- 1 HIGH (Gemini): false positive —
from datetime import UTC, datetimealready at line 13 of vektra-admin/api.py - 3 MEDIUM (Gemini): tracked in DEBT-018 (existing) and DEBT-020/021 (new — see PR #72)
- 4 LOW (code-quality bot): style noise on disambiguating closure-bound mocks in test fakes; left as-is per release-PR zero-new-commit policy
No code changes on this release PR. New BACKLOG entries shipped via #72 (develop-targeted), which will auto-update this PR once merged.
Address two Gemini review comments on PR #72: - DEBT-020 (3143979031): title "on admin writes" too narrow per NFR-007 ("audit every sensitive content access"). Sensitive read endpoints like `get_conversation_turns` (vektra-admin/api.py:491-539) share the same `if request_id:` pattern. Renamed to "on sensitive endpoints" and expanded the sweep + acceptance criteria to cover both reads and writes. - DEBT-021 (3143979036): "Single Postgres query" was Pgvector-centric and ignored the multi-provider architecture. Reframed as "provider-aligned filename resolution" with two explicit paths: (A) Postgres-side JOIN — provider-agnostic since chunks live in document_chunks for both pgvector and qdrant deployments, (B) Qdrant-native payload denormalization — follow-up for Qdrant-heavy deployments, separate ingest-side change.
- Add DEBT-020: audit log fallback when request_id missing on sensitive endpoints (NFR-007 coverage on both reads and writes) - Add DEBT-021: provider-aligned filename resolution to replace _fetch_document_names round-trip (Postgres JOIN path A + Qdrant payload denormalization path B) Reviews: 2/2 addressed (Gemini medium x2 on PR #72) + 8/8 deferred from PR #71 review Tests: lint + analyze + integration green; component tests correctly skipped (BACKLOG-only diff) Refs: feedback iteration from PR #71 (v0.5.0 release) Gemini review
--- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:development dependency-group: uv - dependency-name: aiohttp dependency-version: 3.13.4 dependency-type: indirect dependency-group: uv - dependency-name: cryptography dependency-version: 46.0.7 dependency-type: indirect dependency-group: uv - dependency-name: lxml dependency-version: 6.1.0 dependency-type: indirect dependency-group: uv - dependency-name: onnx dependency-version: 1.21.0 dependency-type: indirect dependency-group: uv - dependency-name: pillow dependency-version: 12.2.0 dependency-type: indirect dependency-group: uv - dependency-name: pyasn1 dependency-version: 0.6.3 dependency-type: indirect dependency-group: uv - dependency-name: pypdf dependency-version: 6.10.2 dependency-type: indirect dependency-group: uv - dependency-name: python-dotenv dependency-version: 1.2.2 dependency-type: indirect dependency-group: uv - dependency-name: python-multipart dependency-version: 0.0.26 dependency-type: indirect dependency-group: uv - dependency-name: requests dependency-version: 2.33.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the npm_and_yarn group with 1 update in the /vektra-learn/widget directory: [esbuild](https://github.com/evanw/esbuild). Updates `esbuild` from 0.24.2 to 0.25.0 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md) - [Commits](evanw/esbuild@v0.24.2...v0.25.0) --- updated-dependencies: - dependency-name: esbuild dependency-version: 0.25.0 dependency-type: direct:development dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com>
Three fixes addressing review feedback that was previously deferred to
v0.5.1, now bundled into v0.5.0 to avoid a back-to-back release:
DEBT-018 (widget --vektra-primary scoping):
- Override no longer set on :root (host-page leak); now scoped to
.vektra-chat-btn, .vektra-chat-panel.
- Reuse a single <style id="vektra-primary-override"> node instead of
appending one per construction (multi-instance / hot-reload safety).
DEBT-020 (audit log fallback for sensitive endpoints, NFR-007):
- New helper _resolve_request_id(request) -> UUID in vektra-admin/api.py
and vektra-learn/api.py: returns request.state.request_id or
synthesizes a uuid4(); emits a structlog warning on the fallback path.
- Applied to both writes (POST /api-keys, DELETE /api-keys/{id},
PATCH /admin/namespaces/{id}/config) and reads
(GET /admin/conversations/{id}/turns, GET /learn/.../turns).
- Audit no longer silently skips when the request-id middleware misbehaves.
- Unit tests cover the helper and the fallback path on a sensitive read.
Test fakes (4 LOW from code-quality bot):
- Renamed self_inner -> self in nested _FakeResult / _FakeSession used
by test_fetch_document_names_marks_archived. Pure style; no behaviour
change.
uv.lock:
- Regenerated to match the v0.5.0 pyproject.toml after cherry-picking
the dependency bump from PR #68 (litellm, pytest, etc.).
Updates [0.5.0] entry to reflect the additional fixes bundled into the release instead of deferring to v0.5.1: - Date moved to 2026-04-27 (actual ship date) - Changed: widget --vektra-primary scope to widget roots (DEBT-018) - Fixed: audit log fallback on sensitive admin/learn endpoints (DEBT-020), test fakes self_inner -> self - Security: litellm critical CVE fix, pytest 9, esbuild 0.25
Found during self-review of the v0.5.0-finalize branch: vektra-ingest had two private helpers (_write_audit_log_async, _write_audit_log_direct) with the same `if request_id is None: return` pattern that DEBT-020 was meant to fix everywhere. They were silently skipping audit on every ingest endpoint when the request-id middleware misbehaves. Same treatment as vektra-admin/api.py and vektra-learn/api.py: - Add private _resolve_request_id helper (uuid4 fallback + structlog warning). - Use it in both writers, dropping the early-return. Updated DEBT-020 BACKLOG entry to status=completed and broaden the resolution narrative. Updated CHANGELOG to list the ingest endpoints (POST /api/v1/ingest, batch ingest, deletion) alongside the admin/learn endpoints already covered. Marked DEBT-018 completed too. No new tests for vektra-ingest: the helper is byte-identical to the admin one, which already has unit coverage.
Found during second-pass review: test_resolve_request_id_synthesizes_ uuid_when_missing took a caplog parameter but never asserted on it. Verifying structlog warnings emission is best done with structlog's own testing utilities, not stdlib caplog. Minor cleanup; the test still exercises the synthesis path and asserts UUID type + uniqueness.
Three trivial fixes addressing CodeRabbit nitpicks. Deferred items (Gemini medium #1 multi-instance widget, CodeRabbit nitpick #2 helper hoist) tracked as DEBT-022 / DEBT-023 in BACKLOG. Fix #3: dedupe `is_bootstrap_key(token)` call in vektra-admin/api.py. The check now runs once near the top of `create_api_key` and the cached `is_bootstrap` boolean is reused in the audit metadata. Fix #4: tighten the `_injectStyles` comment in chat-ui.js. The previous wording read as if both the main style block and the primary-color override were deduped; only the override is. Comment now states this explicitly and points to DEBT-022 for the multi-instance follow-up. Fix #5: relax `litellm` pin in vektra-core/pyproject.toml from `>=1.83.10,<1.83.11` to `>=1.83.10`. The exact-version pin came from Dependabot's auto-bump; removing the upper bound lets future patch releases flow without a new PR. Lockfile regenerated; resolved version unchanged (1.83.10). BACKLOG: added DEBT-022 (multi-instance widget primary-color + main-style dedupe) and DEBT-023 (hoist _resolve_request_id to vektra_shared) to track the deferred review items.
Self-review caught a third recompute of `_bootstrap.is_bootstrap_key(token)` at line 351 of `create_api_key`, used to gate the bootstrap-key consumption. CodeRabbit only flagged the line 279 + line 375 pair (now using the cache), but the function had a third independent call to the same function for the same token, which can also reuse the cache. Now `_bootstrap.is_bootstrap_key(token)` is called exactly once per request (at the top of the function), and three downstream branches (auth flow, audit metadata, bootstrap consumption) reuse the cached boolean.
- Cherry-pick deps bumps: litellm 1.83.10 (closes 2 critical CVEs), pytest 9, esbuild 0.25 widget - DEBT-018: widget --vektra-primary scoped to widget roots (no host-page leak), single override style node - DEBT-020: audit log fallback (synth uuid4 + structlog warning) on sensitive endpoints across admin/learn/ingest - 4 LOW nitpicks: self_inner -> self test fakes, comment scope clarification, litellm pin relax, is_bootstrap dedup Reviews: 5/5 addressed on PR #73 (1 Gemini medium -> DEBT-022, 4 CodeRabbit nitpicks: 3 fixed + 1 -> DEBT-023) Tests: 632 passed; CI all green Refs: closes DEBT-018, DEBT-020; spawns DEBT-022, DEBT-023
Release v0.5.0 — Widget production-ready & instructor configuration
This release brings the chatbot widget to production-ready status with white-label customization, conversation persistence, instructor-controlled namespace config, and brand consolidation to "Vektra RAG". Bundles in the deferred-from-v0.5.1 hardening pass (security CVE fixes + DEBT-018/020) so v0.5.0 ships ready, not "release-then-fix".
What's new
Widget production-readiness
data-*attrs, no rebuild)lecture-07.pdfinstead of UUIDs; soft-deleted docs keep citation with(archived)suffixsessionStoragekeyed by course (24h TTL), turn replay on reload, "New chat" resetInstructor configuration
PATCH /api/v1/admin/namespaces/{id}/configand symmetricGETfor per-course settings (grounding_mode, show_sources)GET /api/v1/learn/conversations/{id}/turns(JWT-scoped, decrypted, audit-logged per NFR-007)data-show-sources> namespace config > env > default)Security & hardening
/prompts/test). Bumpedlitellmto>=1.83.10.--vektra-primaryoverride no longer leaks to host-page:root; scoped to widget roots and deduped via single<style id="vektra-primary-override">node.uuid4()request_id with structlog warning when the middleware misbehaves. Closes the silent-skip gap onPOST /api-keys,DELETE /api-keys/{id},PATCH /admin/namespaces/{id}/config, both turn-reading endpoints, and the ingest writers.pytest8 → 9.0.3, widgetesbuild0.24 → 0.25.Branding & docs
Pull requests in this release
Coordinated with
vektralabs/vektra-moodle#14 — parallel "Vektra RAG" rebrand on the Moodle plugin (already merged).
Test plan
Refs
.s2s/plans/20260418-v050-widget-and-prof-config.md_resolve_request_idtovektra_shared).