Skip to content

release: v0.5.0 - Widget production-ready & instructor configuration#71

Merged
fvadicamo merged 60 commits into
mainfrom
develop
May 5, 2026
Merged

release: v0.5.0 - Widget production-ready & instructor configuration#71
fvadicamo merged 60 commits into
mainfrom
develop

Conversation

@fvadicamo
Copy link
Copy Markdown
Contributor

@fvadicamo fvadicamo commented Apr 26, 2026

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

  • FEAT-016 White-label — title, primary color, icon, welcome message, powered-by per course (all via data-* attrs, no rebuild)
  • FEAT-012 Document filenames — source citations show lecture-07.pdf instead of UUIDs; soft-deleted docs keep citation with (archived) suffix
  • FEAT-004 Conversation persistencesessionStorage keyed by course (24h TTL), turn replay on reload, "New chat" reset

Instructor configuration

  • WI-3PATCH /api/v1/admin/namespaces/{id}/config and symmetric GET for per-course settings (grounding_mode, show_sources)
  • WI-1GET /api/v1/learn/conversations/{id}/turns (JWT-scoped, decrypted, audit-logged per NFR-007)
  • FEAT-014 — configurable source citation visibility (resolution chain: data-show-sources > namespace config > env > default)

Security & hardening

  • 2 critical CVEs in litellm closed — auth bypass via OIDC userinfo cache key collision, plus several high-severity findings (cmd exec via MCP stdio, SSTI in /prompts/test). Bumped litellm to >=1.83.10.
  • DEBT-018 widget primary-color scope--vektra-primary override no longer leaks to host-page :root; scoped to widget roots and deduped via single <style id="vektra-primary-override"> node.
  • DEBT-020 audit log fallback (NFR-007) — sensitive admin/learn/ingest endpoints now always write an audit row, synthesizing a uuid4() request_id with structlog warning when the middleware misbehaves. Closes the silent-skip gap on POST /api-keys, DELETE /api-keys/{id}, PATCH /admin/namespaces/{id}/config, both turn-reading endpoints, and the ingest writers.
  • deps-dev: pytest 8 → 9.0.3, widget esbuild 0.24 → 0.25.

Branding & docs

  • Rebrand user-facing surfaces to Vektra RAG (README, docs, ROADMAP, GOVERNANCE, CONTRIBUTING, bug report, gemini styleguide)
  • Widget footer label "Vektra" → "VektraLabs"
  • New consolidated docs/guides/widget-integration.md (LMS-agnostic, ~250 lines)
  • New org landing at https://vektralabs.github.io/ (Cayman theme, separate repo)
  • Repo description + 14 GitHub topics for discoverability

Pull requests in this release

Coordinated with

vektralabs/vektra-moodle#14 — parallel "Vektra RAG" rebrand on the Moodle plugin (already merged).

Test plan

  • CI green (lint + tests in clean env, 632 tests pass)
  • Smoke test: query a course via the widget, verify branding shown, conversation restored on reload
  • Verify https://vektralabs.github.io/ live (HTTP 200)

Refs

  • Plan: .s2s/plans/20260418-v050-widget-and-prof-config.md
  • Requirements: REQ-049, REQ-053, REQ-066
  • Architecture: ARCH-047, ARCH-054, ARCH-063
  • Decisions: ADR-0010, ADR-0020, ADR-0025
  • CHANGELOG: [0.5.0] - 2026-04-27
  • Closed BACKLOG items: DEBT-018, DEBT-020. New entries spawned: DEBT-022 (multi-instance widget), DEBT-023 (hoist _resolve_request_id to vektra_shared).

fvadicamo and others added 30 commits April 18, 2026 12:28
- 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).
@fvadicamo fvadicamo added documentation Improvements or additions to documentation enhancement New feature or request component:shared vektra-shared component component:core vektra-core component component:admin vektra-admin component component:learn vektra-learn component labels Apr 26, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Warning

Rate limit exceeded

@fvadicamo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 57 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 08420df1-2a24-474e-a223-a93c595999b0

📥 Commits

Reviewing files that changed from the base of the PR and between 6c61d62 and 25428d3.

⛔ Files ignored due to path filters (6)
  • .s2s/BACKLOG.md is excluded by !.s2s/**
  • .s2s/CONTEXT.md is excluded by !.s2s/**
  • .s2s/architecture.md is excluded by !.s2s/**
  • .s2s/plans/20260418-v050-widget-and-prof-config.md is excluded by !.s2s/**
  • uv.lock is excluded by !**/*.lock, !**/*.lock
  • vektra-learn/widget/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (55)
  • .env.example
  • .gemini/styleguide.md
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
  • GOVERNANCE.md
  • README.md
  • ROADMAP.md
  • docs/README.md
  • docs/architecture/index.md
  • docs/getting-started/first-query.md
  • docs/guides/contributors/index.md
  • docs/guides/widget-integration.md
  • docs/reference/api.md
  • docs/reference/configuration.md
  • docs/reference/error-codes.md
  • pyproject.toml
  • vektra-admin/README.md
  • vektra-admin/pyproject.toml
  • vektra-admin/src/vektra_admin/api.py
  • vektra-admin/tests/test_admin_turns.py
  • vektra-admin/tests/test_integration.py
  • vektra-analytics/README.md
  • vektra-analytics/pyproject.toml
  • vektra-app/README.md
  • vektra-app/pyproject.toml
  • vektra-app/src/vektra_app/main.py
  • vektra-core/README.md
  • vektra-core/pyproject.toml
  • vektra-core/src/vektra_core/advanced_pipeline.py
  • vektra-core/src/vektra_core/api.py
  • vektra-core/src/vektra_core/pipeline.py
  • vektra-core/tests/test_pipeline.py
  • vektra-index/README.md
  • vektra-index/pyproject.toml
  • vektra-ingest/README.md
  • vektra-ingest/pyproject.toml
  • vektra-ingest/src/vektra_ingest/api.py
  • vektra-learn/README.md
  • vektra-learn/pyproject.toml
  • vektra-learn/src/vektra_learn/api.py
  • vektra-learn/src/vektra_learn/query.py
  • vektra-learn/tests/test_api.py
  • vektra-learn/tests/test_query.py
  • vektra-learn/widget/package.json
  • vektra-learn/widget/src/api-client.js
  • vektra-learn/widget/src/chat-ui.js
  • vektra-learn/widget/src/index.js
  • vektra-learn/widget/src/styles.js
  • vektra-shared/README.md
  • vektra-shared/pyproject.toml
  • vektra-shared/src/vektra_shared/config.py
  • vektra-shared/src/vektra_shared/errors.py
  • vektra-shared/src/vektra_shared/namespace.py
  • vektra-shared/src/vektra_shared/types.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added component:ingest vektra-ingest component component:index vektra-index component ci CI/CD and toolchain changes labels Apr 26, 2026
Comment thread vektra-core/tests/test_pipeline.py Fixed
Comment thread vektra-core/tests/test_pipeline.py Fixed
Comment thread vektra-core/tests/test_pipeline.py Fixed
Comment thread vektra-core/tests/test_pipeline.py Fixed
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread vektra-admin/src/vektra_admin/api.py
Comment thread vektra-admin/src/vektra_admin/api.py Outdated
Comment thread vektra-core/src/vektra_core/pipeline.py
Comment thread vektra-learn/widget/src/chat-ui.js Outdated
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.
Copy link
Copy Markdown
Contributor Author

@fvadicamo fvadicamo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review feedback addressed:

  • 1 HIGH (Gemini): false positive — from datetime import UTC, datetime already 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
@fvadicamo fvadicamo self-assigned this Apr 27, 2026
dependabot Bot and others added 6 commits April 27, 2026 08:54
---
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
@fvadicamo fvadicamo merged commit 6c37194 into main May 5, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD and toolchain changes component:admin vektra-admin component component:core vektra-core component component:index vektra-index component component:ingest vektra-ingest component component:learn vektra-learn component component:shared vektra-shared component documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant