From 69a50873823ec946194e5f9110e5e44deda61a77 Mon Sep 17 00:00:00 2001 From: Francesco Vadicamo Date: Sat, 25 Apr 2026 14:13:46 +0000 Subject: [PATCH 01/18] chore(release): finalize v0.5.0 - 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). --- CHANGELOG.md | 4 +- uv.lock | 88 ++++++++++++++++----------------- vektra-admin/pyproject.toml | 2 +- vektra-analytics/pyproject.toml | 2 +- vektra-app/pyproject.toml | 2 +- vektra-core/pyproject.toml | 2 +- vektra-index/pyproject.toml | 2 +- vektra-ingest/pyproject.toml | 2 +- vektra-learn/pyproject.toml | 2 +- vektra-shared/pyproject.toml | 2 +- 10 files changed, 55 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b75ed22..ce41f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). -## [Unreleased] — v0.5.0 "Widget production-ready + instructor configuration" +## [0.5.0] - 2026-04-25 + +Widget production-ready + instructor configuration. ### Added diff --git a/uv.lock b/uv.lock index 652f43f..2ce1136 100644 --- a/uv.lock +++ b/uv.lock @@ -4598,15 +4598,15 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:7fbbf409143a4fe0812a40c0b46a436030a7e1d14fe8c5234dfbe44df47f617e" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:b39cafff7229699f9d6e172cac74d85fd71b568268e439e08d9c540e54732a3e" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:358bd7125cbec6e692d60618a5eec7f55a51b29e3652a849fd42af021d818023" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:470de4176007c2700735e003a830828a88d27129032a3add07291da07e2a94e8" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:45a1c5057629444aeb1c452c18298fa7f30f2f7aeadd4dc41f9d340980294407" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:339e05502b6c839db40e88720cb700f5a3b50cda332284873e851772d41b2c1e" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:840351da59cedb7bcbc51981880050813c19ef6b898a7fecf73a3afc71aff3fe" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:c88b1129fd4e14f0f882963c6728315caae35d2f47374d17edeed1edc7697497" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f4bea7dc451267c028593751612ad559299589304e68df54ae7672427893ff2c" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:7fbbf409143a4fe0812a40c0b46a436030a7e1d14fe8c5234dfbe44df47f617e", upload-time = "2026-02-06T16:27:14Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:b39cafff7229699f9d6e172cac74d85fd71b568268e439e08d9c540e54732a3e", upload-time = "2026-02-06T16:27:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:358bd7125cbec6e692d60618a5eec7f55a51b29e3652a849fd42af021d818023", upload-time = "2026-02-10T19:55:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:470de4176007c2700735e003a830828a88d27129032a3add07291da07e2a94e8", upload-time = "2026-02-10T19:55:43Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:45a1c5057629444aeb1c452c18298fa7f30f2f7aeadd4dc41f9d340980294407", upload-time = "2026-01-23T15:09:55Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:339e05502b6c839db40e88720cb700f5a3b50cda332284873e851772d41b2c1e", upload-time = "2026-01-23T15:09:57Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:840351da59cedb7bcbc51981880050813c19ef6b898a7fecf73a3afc71aff3fe", upload-time = "2026-01-23T15:09:59Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:c88b1129fd4e14f0f882963c6728315caae35d2f47374d17edeed1edc7697497", upload-time = "2026-01-23T15:09:59Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f4bea7dc451267c028593751612ad559299589304e68df54ae7672427893ff2c", upload-time = "2026-01-23T15:10:01Z" }, ] [[package]] @@ -4633,33 +4633,33 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-linux_aarch64.whl", hash = "sha256:8de5a36371b775e2d4881ed12cc7f2de400b1ad3d728aa74a281f649f87c9b8c" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:9accc30b56cb6756d4a9d04fcb8ebc0bb68c7d55c1ed31a8657397d316d31596" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:179451716487f8cb09b56459667fa1f5c4c0946c1e75fbeae77cfc40a5768d87" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ee40b8a4b4b2cf0670c6fd4f35a7ef23871af956fecb238fbf5da15a72650b1d" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:21cb5436978ef47c823b7a813ff0f8c2892e266cfe0f1d944879b5fba81bf4e1" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:3eaa727e6a73affa61564d86b9d03191df45c8650d0666bd3d57c8597ef61e78" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-linux_aarch64.whl", hash = "sha256:fd215f3d0f681905c5b56b0630a3d666900a37fcc3ca5b937f95275c66f9fd9c" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:170a0623108055be5199370335cf9b41ba6875b3cb6f086db4aee583331a4899" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e51994492cdb76edce29da88de3672a3022f9ef0ffd90345436948d4992be2c7" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8d316e5bf121f1eab1147e49ad0511a9d92e4c45cc357d1ab0bee440da71a095" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:b719da5af01b59126ac13eefd6ba3dd12d002dc0e8e79b8b365e55267a8189d3" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:b67d91326e4ed9eccbd6b7d84ed7ffa43f93103aa3f0b24145f3001f3b11b714" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-linux_aarch64.whl", hash = "sha256:5af75e5f49de21b0bdf7672bc27139bd285f9e8dbcabe2d617a2eb656514ac36" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-linux_s390x.whl", hash = "sha256:ba51ef01a510baf8fff576174f702c47e1aa54389a9f1fba323bb1a5003ff0bf" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0fedcb1a77e8f2aaf7bfd21591bf6d1e0b207473268c9be16b17cb7783253969" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:106dd1930cb30a4a337366ba3f9b25318ebf940f51fd46f789281dd9e736bdc4" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:eb1bde1ce198f05c8770017de27e001d404499cf552aaaa014569eff56ca25c0" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-linux_aarch64.whl", hash = "sha256:ea2bcc9d1fca66974a71d4bf9a502539283f35d61fcab5a799b4e120846f1e02" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:f8294fd2fc6dd8f4435a891a0122307a043b14b21f0dac1bca63c85bfb59e586" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a28fdbcfa2fbacffec81300f24dd1bed2b0ccfdbed107a823cff12bc1db070f6" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:aada8afc068add586464b2a55adb7cc9091eec55caf5320447204741cb6a0604" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:2adc71fe471e98a608723bfc837f7e1929885ebb912c693597711e139c1cda41" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-linux_aarch64.whl", hash = "sha256:9412bd37b70f5ebd1205242c4ba4cabae35a605947f2b30806d5c9b467936db9" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:e71c476517c33e7db69825a9ff46c7f47a723ec4dac5b2481cff4246d1c632be" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:23882f8d882460aca809882fc42f5e343bf07585274f929ced00177d1be1eb67" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4fcd8b4cc2ae20f2b7749fb275349c55432393868778c2d50a08e81d5ee5591e" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:ffc8da9a1341092d6a90cb5b1c1a33cd61abf0fb43f0cd88443c27fa372c26ae" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-linux_aarch64.whl", hash = "sha256:8de5a36371b775e2d4881ed12cc7f2de400b1ad3d728aa74a281f649f87c9b8c", upload-time = "2026-01-23T15:10:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:9accc30b56cb6756d4a9d04fcb8ebc0bb68c7d55c1ed31a8657397d316d31596", upload-time = "2026-01-23T15:10:24Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:179451716487f8cb09b56459667fa1f5c4c0946c1e75fbeae77cfc40a5768d87", upload-time = "2026-01-23T15:10:25Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ee40b8a4b4b2cf0670c6fd4f35a7ef23871af956fecb238fbf5da15a72650b1d", upload-time = "2026-01-23T15:10:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:21cb5436978ef47c823b7a813ff0f8c2892e266cfe0f1d944879b5fba81bf4e1", upload-time = "2026-01-23T15:10:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:3eaa727e6a73affa61564d86b9d03191df45c8650d0666bd3d57c8597ef61e78", upload-time = "2026-01-23T15:10:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-linux_aarch64.whl", hash = "sha256:fd215f3d0f681905c5b56b0630a3d666900a37fcc3ca5b937f95275c66f9fd9c", upload-time = "2026-01-23T15:10:34Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:170a0623108055be5199370335cf9b41ba6875b3cb6f086db4aee583331a4899", upload-time = "2026-01-23T15:10:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e51994492cdb76edce29da88de3672a3022f9ef0ffd90345436948d4992be2c7", upload-time = "2026-01-23T15:10:37Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8d316e5bf121f1eab1147e49ad0511a9d92e4c45cc357d1ab0bee440da71a095", upload-time = "2026-01-23T15:10:38Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:b719da5af01b59126ac13eefd6ba3dd12d002dc0e8e79b8b365e55267a8189d3", upload-time = "2026-01-23T15:10:41Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:b67d91326e4ed9eccbd6b7d84ed7ffa43f93103aa3f0b24145f3001f3b11b714", upload-time = "2026-01-23T15:10:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-linux_aarch64.whl", hash = "sha256:5af75e5f49de21b0bdf7672bc27139bd285f9e8dbcabe2d617a2eb656514ac36", upload-time = "2026-01-23T15:10:44Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-linux_s390x.whl", hash = "sha256:ba51ef01a510baf8fff576174f702c47e1aa54389a9f1fba323bb1a5003ff0bf", upload-time = "2026-01-23T15:10:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0fedcb1a77e8f2aaf7bfd21591bf6d1e0b207473268c9be16b17cb7783253969", upload-time = "2026-01-23T15:10:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:106dd1930cb30a4a337366ba3f9b25318ebf940f51fd46f789281dd9e736bdc4", upload-time = "2026-01-23T15:10:50Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:eb1bde1ce198f05c8770017de27e001d404499cf552aaaa014569eff56ca25c0", upload-time = "2026-01-23T15:10:50Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-linux_aarch64.whl", hash = "sha256:ea2bcc9d1fca66974a71d4bf9a502539283f35d61fcab5a799b4e120846f1e02", upload-time = "2026-01-23T15:10:53Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:f8294fd2fc6dd8f4435a891a0122307a043b14b21f0dac1bca63c85bfb59e586", upload-time = "2026-01-23T15:10:55Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a28fdbcfa2fbacffec81300f24dd1bed2b0ccfdbed107a823cff12bc1db070f6", upload-time = "2026-01-23T15:10:56Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:aada8afc068add586464b2a55adb7cc9091eec55caf5320447204741cb6a0604", upload-time = "2026-01-23T15:10:58Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:2adc71fe471e98a608723bfc837f7e1929885ebb912c693597711e139c1cda41", upload-time = "2026-01-23T15:11:01Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-linux_aarch64.whl", hash = "sha256:9412bd37b70f5ebd1205242c4ba4cabae35a605947f2b30806d5c9b467936db9", upload-time = "2026-01-23T15:11:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:e71c476517c33e7db69825a9ff46c7f47a723ec4dac5b2481cff4246d1c632be", upload-time = "2026-01-23T15:11:04Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:23882f8d882460aca809882fc42f5e343bf07585274f929ced00177d1be1eb67", upload-time = "2026-01-23T15:11:07Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4fcd8b4cc2ae20f2b7749fb275349c55432393868778c2d50a08e81d5ee5591e", upload-time = "2026-01-23T15:11:07Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:ffc8da9a1341092d6a90cb5b1c1a33cd61abf0fb43f0cd88443c27fa372c26ae", upload-time = "2026-01-23T15:11:10Z" }, ] [[package]] @@ -4955,7 +4955,7 @@ wheels = [ [[package]] name = "vektra-admin" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-admin" } dependencies = [ { name = "argon2-cffi" }, @@ -5004,7 +5004,7 @@ dev = [ [[package]] name = "vektra-analytics" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-analytics" } dependencies = [ { name = "fastapi" }, @@ -5039,7 +5039,7 @@ dev = [ [[package]] name = "vektra-app" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-app" } dependencies = [ { name = "alembic" }, @@ -5088,7 +5088,7 @@ dev = [ [[package]] name = "vektra-core" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-core" } dependencies = [ { name = "fastapi" }, @@ -5131,7 +5131,7 @@ dev = [ [[package]] name = "vektra-index" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-index" } dependencies = [ { name = "asyncpg" }, @@ -5186,7 +5186,7 @@ dev = [ [[package]] name = "vektra-ingest" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-ingest" } dependencies = [ { name = "arq" }, @@ -5248,7 +5248,7 @@ dev = [ [[package]] name = "vektra-learn" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-learn" } dependencies = [ { name = "fastapi" }, @@ -5285,7 +5285,7 @@ dev = [ [[package]] name = "vektra-shared" -version = "0.5.0.dev0" +version = "0.5.0" source = { editable = "vektra-shared" } dependencies = [ { name = "asyncpg" }, diff --git a/vektra-admin/pyproject.toml b/vektra-admin/pyproject.toml index 18c28b9..d5e7288 100644 --- a/vektra-admin/pyproject.toml +++ b/vektra-admin/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-admin" -version = "0.5.0-dev" +version = "0.5.0" description = "System administration interface: health, API key management, audit log" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-analytics/pyproject.toml b/vektra-analytics/pyproject.toml index 3481f01..18afcfe 100644 --- a/vektra-analytics/pyproject.toml +++ b/vektra-analytics/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-analytics" -version = "0.5.0-dev" +version = "0.5.0" description = "QueryTrace storage, metrics aggregation, and reporting API" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-app/pyproject.toml b/vektra-app/pyproject.toml index a5c7635..b81f38e 100644 --- a/vektra-app/pyproject.toml +++ b/vektra-app/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-app" -version = "0.5.0-dev" +version = "0.5.0" description = "FastAPI application assembly and startup validation (ARCH-015, ARCH-057)" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-core/pyproject.toml b/vektra-core/pyproject.toml index 500c77b..55cc616 100644 --- a/vektra-core/pyproject.toml +++ b/vektra-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-core" -version = "0.5.0-dev" +version = "0.5.0" description = "RAG engine, LLM abstraction, and conversation management" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-index/pyproject.toml b/vektra-index/pyproject.toml index 2883ad1..4e155f1 100644 --- a/vektra-index/pyproject.toml +++ b/vektra-index/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-index" -version = "0.5.0-dev" +version = "0.5.0" description = "Vector store abstraction and semantic search for the Vektra platform" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-ingest/pyproject.toml b/vektra-ingest/pyproject.toml index 770e033..44f61ea 100644 --- a/vektra-ingest/pyproject.toml +++ b/vektra-ingest/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-ingest" -version = "0.5.0-dev" +version = "0.5.0" description = "Document processing pipeline for the Vektra platform" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-learn/pyproject.toml b/vektra-learn/pyproject.toml index b8fcb4d..c355ff7 100644 --- a/vektra-learn/pyproject.toml +++ b/vektra-learn/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-learn" -version = "0.5.0-dev" +version = "0.5.0" description = "E-learning vertical: LMS-agnostic API and chatbot widget" readme = "README.md" requires-python = ">=3.12" diff --git a/vektra-shared/pyproject.toml b/vektra-shared/pyproject.toml index 233c124..9e8b749 100644 --- a/vektra-shared/pyproject.toml +++ b/vektra-shared/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vektra-shared" -version = "0.5.0-dev" +version = "0.5.0" description = "Shared protocols and types for the Vektra platform" readme = "README.md" requires-python = ">=3.12" From 254f7ae22e353fa1975143f8ae3ea708432f9e42 Mon Sep 17 00:00:00 2001 From: Francesco Vadicamo Date: Sat, 25 Apr 2026 18:49:39 +0000 Subject: [PATCH 02/18] docs(changelog): adopt always-on [Unreleased] per Keep a Changelog 1.1.0 - CHANGELOG.md: add empty [Unreleased] block above [0.5.0] with a header comment documenting the convention so contributors and AI agents know where to add entries between releases - CONTRIBUTING.md: new "Changelog" section explaining the during-development vs at-release-time process; add an [Unreleased] entry to the PR checklist Addresses Gemini review on PR #70 (CHANGELOG.md:7). --- CHANGELOG.md | 15 +++++++++++++++ CONTRIBUTING.md | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce41f9e..d4744ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + + +## [Unreleased] + + + ## [0.5.0] - 2026-04-25 Widget production-ready + instructor configuration. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index def6de7..f2f9867 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -124,6 +124,31 @@ git log --show-signature -1 # should show "Good ssh signature" **Remote servers without browser**: use `gh auth login --with-token` and add the `admin:ssh_signing_key` scope upfront to avoid needing browser-based re-auth later. +## Changelog + +We follow [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/). The +file must always have an `[Unreleased]` section at the top, even if empty. + +**During development** — every PR with user-visible impact adds an entry under +`[Unreleased]` in `CHANGELOG.md`, using one of the six standard sections: + +- **Added** — new features +- **Changed** — changes in existing functionality +- **Deprecated** — features that will be removed in upcoming releases +- **Removed** — features removed in this release +- **Fixed** — bug fixes +- **Security** — vulnerability fixes + +Internal-only changes (refactors with no behavior change, CI tweaks, test-only +edits) do not require a changelog entry. + +**At release time** — in the release-prep PR (`chore/vX.Y.Z-release`): + +1. Rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD` +2. Add a fresh empty `## [Unreleased]` block above the new release entry +3. Bump versions in all `vektra-*/pyproject.toml` from `X.Y.Z-dev` to `X.Y.Z` +4. Refresh `uv.lock` to match the new versions + ## PR workflow 1. Create a branch: `git checkout -b feat/core-streaming` @@ -148,6 +173,7 @@ Before opening a PR: - [ ] Unit tests pass: `uv run pytest /tests/ -m "not integration"` - [ ] New behavior has test coverage - [ ] Commit messages follow Conventional Commits with `-s` sign-off +- [ ] `CHANGELOG.md` has an `[Unreleased]` entry for any user-visible change - [ ] PR description explains the change and motivation ## CI pipeline From 64a9b042882d9b05f7360bc40cfe91de59ca4692 Mon Sep 17 00:00:00 2001 From: Francesco Vadicamo Date: Sat, 25 Apr 2026 19:02:47 +0000 Subject: [PATCH 03/18] docs(v0.5.0): align reference and READMEs with widget + namespace config API reference (docs/reference/api.md): - New "Learn" section documenting POST /api/v1/learn/query (JSON + SSE, show_sources flag, document_name) and GET /api/v1/learn/conversations/{id}/turns (JWT scope, ERR-LEARN-005/006, learn_conversation_turns_read audit row) - Spell out the show_sources resolution chain in the namespaces PATCH section - Document the (archived) suffix on document_name for soft-deleted documents Configuration (docs/reference/configuration.md): - Add VEKTRA_PROMPT_GROUNDING_MODE (Query pipeline) - Add VEKTRA_LEARN_SHOW_SOURCES (E-learning) Error codes (docs/reference/error-codes.md): - Fix ERR-LEARN-004: 422 -> 409, message corrected to "Duplicate enrollment" (the documented text never matched the actual handler in vektra-learn/api.py) Getting started (docs/getting-started/first-query.md): - Sample response includes document_name; field added to the description table Component READMEs: - vektra-learn: full widget integration guide (data-* attrs, sessionStorage persistence, endpoint table) - vektra-admin: list namespace config endpoints - vektra-core: document_name join logic and (archived) fallback CHANGELOG.md: - Widget data-* attribute list in v0.5.0 entry was missing data-powered-by-text and data-powered-by-url (both shipped in PR #66) Scope: HIGH + MEDIUM drift from the v0.5.0 doc audit. LOW items (component README polish, dedicated widget guide, helper docs in vektra-shared README, configuration variable count reconciliation) deferred to a follow-up. --- CHANGELOG.md | 2 +- docs/getting-started/first-query.md | 7 +- docs/reference/api.md | 108 +++++++++++++++++++++++++++- docs/reference/configuration.md | 8 ++- docs/reference/error-codes.md | 2 +- vektra-admin/README.md | 10 +++ vektra-core/README.md | 6 ++ vektra-learn/README.md | 77 +++++++++++++++++++- 8 files changed, 211 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4744ba..62e3acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ Widget production-ready + instructor configuration. - **vektra-learn**: `GET /api/v1/learn/conversations/{id}/turns` JWT-scoped endpoint so the widget can restore a conversation after a page reload. Returns decrypted question/answer + created_at; admin-only metadata is not exposed. 403 on namespace mismatch, 404 on missing. - **vektra-core**: `document_name` field on every source citation, joined from `source_documents.filename` so the widget renders `[1] lecture-07.pdf` instead of chunk UUIDs. Propagates through both `SimpleQueryPipeline` and `AdvancedQueryPipeline`, JSON and SSE paths. Soft-deleted source documents (REQ-057) keep their citation with an `(archived)` suffix so answers stay traceable. - **vektra-learn**: content-access audit entry (`learn_conversation_turns_read`) written on every successful turns fetch via the shared `vektra_shared.audit` interface (NFR-007). -- **widget**: white-label `data-*` attributes — `data-title`, `data-primary-color`, `data-icon` (emoji or URL), `data-welcome-message`, `data-powered-by`. All rendered via `textContent` / safe color whitelist to avoid XSS. +- **widget**: white-label `data-*` attributes — `data-title`, `data-primary-color`, `data-icon` (emoji or URL), `data-welcome-message`, `data-powered-by`, `data-powered-by-text`, `data-powered-by-url`. All rendered via `textContent` / safe color whitelist to avoid XSS. - **widget**: tab-scoped conversation persistence via `sessionStorage` keyed by `course_id` (24h cutoff); history replay on load via the new turns endpoint; explicit "New chat" button in the header. - **errors**: new codes `ERR-ADMIN-005/006/007` (namespace config) and `ERR-LEARN-005/006` (conversation turns). diff --git a/docs/getting-started/first-query.md b/docs/getting-started/first-query.md index a51f5e8..bd4d4d4 100644 --- a/docs/getting-started/first-query.md +++ b/docs/getting-started/first-query.md @@ -79,7 +79,8 @@ scripts/query.sh "Summarize the main findings" "score": 0.912, "snippet": "Finding 1: The study reveals that...", "citation_id": "e5f6a7-...", - "document_version": 1 + "document_version": 1, + "document_name": "your-document.pdf" }, { "doc_id": "a1b2c3d4-...", @@ -87,7 +88,8 @@ scripts/query.sh "Summarize the main findings" "score": 0.847, "snippet": "Finding 2: In contrast to previous work...", "citation_id": "b8c9d0-...", - "document_version": 1 + "document_version": 1, + "document_name": "your-document.pdf" } ], "conversation_id": null, @@ -105,6 +107,7 @@ scripts/query.sh "Summarize the main findings" | `sources[].chunk_id` | Unique identifier of the retrieved chunk | | `sources[].citation_id` | UUID citation reference for traceability | | `sources[].document_version` | Index version of the chunk | +| `sources[].document_name` | Filename of the source document. Soft-deleted documents keep their citation with an `(archived)` suffix so traceability is preserved. | | `sources[].score` | Cosine similarity score (0.0 - 1.0, higher is more relevant) | | `sources[].snippet` | Text excerpt from the chunk | | `conversation_id` | Echoed back if provided in the request (see below) | diff --git a/docs/reference/api.md b/docs/reference/api.md index 493fe12..7b6af63 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -193,7 +193,7 @@ Request body (flat dict, one entry per config key): | Field | Type | Allowed values | Description | |-------|------|----------------|-------------| | `grounding_mode` | string or null | `"strict"`, `"hybrid"`, `null` | RAG grounding policy. `null` removes the key and falls back to `VEKTRA_PROMPT_GROUNDING_MODE`. | -| `show_sources` | bool or null | `true`, `false`, `null` | Widget citation visibility (FEAT-014). `null` removes the key and falls back to `VEKTRA_LEARN_SHOW_SOURCES`. The API always returns the full sources list; the flag only instructs the widget whether to render them. | +| `show_sources` | bool or null | `true`, `false`, `null` | Widget citation visibility (FEAT-014). `null` removes the key and falls back to `VEKTRA_LEARN_SHOW_SOURCES`. The API always returns the full sources list; the flag only instructs the widget whether to render them. Resolution chain: client `data-show-sources` attr > `namespaces.config.show_sources` > `VEKTRA_LEARN_SHOW_SOURCES` env > hardcoded `true`. | Behavior: - **Partial update**: keys not present in the body are preserved. @@ -339,6 +339,7 @@ Response: | `response_id` | Unique response identifier | | `answer` | LLM-generated answer grounded in sources | | `sources` | Ranked list of source chunks | +| `sources[].document_name` | Filename of the source document (e.g. `lecture-07.pdf`). Soft-deleted documents (REQ-057) keep their citation with an `(archived)` suffix so traceability is preserved. | | `conversation_id` | Echoed back if provided in request | | `context_only` | `true` if LLM failed and raw sources returned | | `no_relevant_context` | `true` if no chunks exceeded relevance threshold | @@ -368,6 +369,111 @@ curl -s \ http://localhost:8000/api/v1/providers | python3 -m json.tool ``` +## Learn + +The e-learning vertical (`vektra-learn`) exposes course-scoped endpoints authenticated via short-lived JWT dashboard tokens issued by `POST /api/v1/learn/tokens`. The `course_id` claim drives namespace isolation; the LMS-side integration (e.g. the Moodle plugin) is responsible for upstream auth before requesting a token. + +### POST /api/v1/learn/query + +Course-scoped RAG query. Authenticated via JWT (not API key). The namespace is derived from the token's `namespace` (or `course_id` fallback) claim. + +```bash +curl -s \ + -H "Authorization: Bearer $LEARN_JWT" \ + -H "Content-Type: application/json" \ + -d '{"question":"What did we cover in lecture 7?"}' \ + http://localhost:8000/api/v1/learn/query | python3 -m json.tool +``` + +Request body: + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `question` | string | (required) | Query text | +| `conversation_id` | UUID | - | Continue an existing conversation | +| `top_k` | int | `5` | Number of chunks to retrieve | +| `stream` | bool | `false` | Enable Server-Sent Events streaming | + +Response (HTTP 200, JSON): + +```json +{ + "response_id": "9f8e7d6c-...", + "answer": "Lecture 7 covered ...", + "sources": [ + { + "doc_id": "550e8400-...", + "chunk_id": "c1a2b3...", + "score": 0.912, + "snippet": "...", + "document_name": "lecture-07.pdf" + } + ], + "conversation_id": "550e8400-...", + "no_relevant_context": false, + "show_sources": true +} +``` + +| Field | Description | +|-------|-------------| +| `show_sources` | Server-resolved citation-visibility hint for the widget (FEAT-014). The full `sources` list is always returned regardless; the widget uses the flag to decide whether to render the citations block. See the resolution chain in [Namespaces PATCH](#patch-apiv1adminnamespacesnamespace_idconfig). | +| `sources[].document_name` | Filename of the source document, with `(archived)` suffix for soft-deleted documents. | + +#### Streaming + +Set `stream: true` or send `Accept: text/event-stream`: + +```bash +curl -s -N \ + -H "Authorization: Bearer $LEARN_JWT" \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{"question":"What did we cover in lecture 7?","stream":true}' \ + http://localhost:8000/api/v1/learn/query +``` + +The `sources` SSE event payload carries the same `show_sources` flag alongside the sources list. + +### GET /api/v1/learn/conversations/{conversation_id}/turns + +Return the decrypted turns of a conversation belonging to the token's course. Used by the widget to restore history after a page reload (FEAT-004). + +**Auth**: JWT dashboard token (same as `/learn/query`). The endpoint enforces `conversation.namespace_id == jwt.namespace`; a mismatch returns 403 (so the widget can distinguish "wrong course" from "deleted conversation" and reset its local state). + +```bash +curl -s \ + -H "Authorization: Bearer $LEARN_JWT" \ + http://localhost:8000/api/v1/learn/conversations/550e8400-.../turns | python3 -m json.tool +``` + +Response (HTTP 200): + +```json +{ + "conversation_id": "550e8400-...", + "namespace": "course-101", + "turns": [ + { + "turn_number": 1, + "question": "What is RAG?", + "answer": "RAG is ...", + "created_at": "2026-04-25T14:00:00Z", + "sources": [] + } + ] +} +``` + +The `turns[].sources` array is empty in v0.5.0 (admin-only metadata such as model, prompt tokens, and source enrichment from `query_traces` is intentionally not exposed to students). + +Errors: +- `404 ERR-LEARN-005` if the conversation does not exist or has been deleted. +- `403 ERR-LEARN-006` if the conversation belongs to a different course/namespace. +- `503 ERR-LEARN-001` if the conversation store is not yet initialized or does not support decryption (e.g. in-memory store; production deployments must set `VEKTRA_CONVERSATION_KEY`). + +**Audit (NFR-007)**: every successful read writes a `learn_conversation_turns_read` audit row carrying `namespace`, `conversation_id`, `turn_count`, `student_id`, and `course_id`. The audit row is generated even if the upstream middleware did not set a request id. + ## Index ### POST /api/v1/search diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index e16cdbd..950dd1a 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -84,6 +84,7 @@ The model name must match the vLLM `--model` path exactly (e.g., `/models/qwen35 | `VEKTRA_RESPONSE_TOKEN_RESERVE` | int | `2048` | Tokens reserved for LLM response generation | | `VEKTRA_CONTEXT_CHUNK_RATIO` | float | `0.6` | Fraction of context window allocated to retrieved chunks (0.0-1.0) | | `VEKTRA_PROMPT_TEMPLATES_DIR` | str | - | Directory for custom Jinja2 prompt templates (`system.j2`, `context.j2`, `conversation.j2`). Uses built-in defaults if unset. | +| `VEKTRA_PROMPT_GROUNDING_MODE` | str | `strict` | Default RAG grounding policy. `strict` answers from retrieved context + history only; `hybrid` falls back to model knowledge when confident. Per-namespace override via `PATCH /api/v1/admin/namespaces/{id}/config`. | ### Query rewriting @@ -138,6 +139,7 @@ The model name must match the vLLM `--model` path exactly (e.g., `/models/qwen35 |----------|------|---------|-------------| | `VEKTRA_LEARN_JWT_SECRET` | str | - | JWT signing secret for dashboard tokens. Required when vektra-learn is active. Min 32 characters. | | `VEKTRA_LEARN_REQUIRE_ENROLLMENT` | bool | `true` | Require enrollment record for learn queries. Set to false when LMS manages enrollment externally. | +| `VEKTRA_LEARN_SHOW_SOURCES` | bool | `true` | Default visibility of the source-citations section in the widget (FEAT-014). Per-namespace override via `PATCH /api/v1/admin/namespaces/{id}/config` > `show_sources`. Per-widget override via the `data-show-sources` script-tag attribute. The API always returns the full sources list; the flag only instructs the widget whether to render them. | ## Observability @@ -173,9 +175,9 @@ The model name must match the vLLM `--model` path exactly (e.g., `/models/qwen35 | Category | Count | |----------|-------| -| VEKTRA_* variables (across VektraSettings + sub-configs) | 53 | +| VEKTRA_* variables (across VektraSettings + sub-configs) | 55 | | External API keys (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) | 2 | | Infrastructure (`POSTGRES_PASSWORD`, `CMD_TARGET`) | 2 | -| **Total documented** | **57** | +| **Total documented** | **59** | -The 53 VEKTRA_* variables are declared across `VektraSettings` (flat aggregation) and sub-configs (`RewriteConfig`, `RerankConfig`, `WebhookConfig`, `IngestConfig` extensions). Sub-configs are validated independently at startup, not aggregated into VektraSettings. Infrastructure variables (`POSTGRES_PASSWORD`, `CMD_TARGET`) are used by Docker Compose or the entrypoint script. +The 55 VEKTRA_* variables are declared across `VektraSettings` (flat aggregation) and sub-configs (`RewriteConfig`, `RerankConfig`, `WebhookConfig`, `IngestConfig` extensions). Sub-configs are validated independently at startup, not aggregated into VektraSettings. Infrastructure variables (`POSTGRES_PASSWORD`, `CMD_TARGET`) are used by Docker Compose or the entrypoint script. diff --git a/docs/reference/error-codes.md b/docs/reference/error-codes.md index 9bb9cd3..804677d 100644 --- a/docs/reference/error-codes.md +++ b/docs/reference/error-codes.md @@ -89,7 +89,7 @@ These codes are used by specific components and are not part of the REQ-011 regi | ERR-LEARN-001 | vektra-learn | 503 | Learn service unavailable or pipeline not configured | | ERR-LEARN-002 | vektra-learn | 404 | No enrollment found for student in course | | ERR-LEARN-003 | vektra-learn | 401 | Dashboard token missing required course_id claim | -| ERR-LEARN-004 | vektra-learn | 422 | Invalid learn request (missing fields, bad format) | +| ERR-LEARN-004 | vektra-learn | 409 | Duplicate enrollment (student already enrolled in course) | | ERR-LEARN-005 | vektra-learn | 404 | Conversation not found (GET conversations/turns) | | ERR-LEARN-006 | vektra-learn | 403 | Conversation belongs to a different course/namespace | diff --git a/vektra-admin/README.md b/vektra-admin/README.md index 0eb9ae2..4cf9d59 100644 --- a/vektra-admin/README.md +++ b/vektra-admin/README.md @@ -4,4 +4,14 @@ System administration interface for the Vektra platform. Phase 1: minimal admin endpoints included in vektra-core (namespace management, API key management, system health). Phase 2: full admin SPA with separate deployment. +## Endpoints + +See [API reference](../docs/reference/api.md) for full request/response shapes: + +- `POST/GET/DELETE /api/v1/api-keys` — API key lifecycle (`admin` scope) +- `GET /api/v1/admin/namespaces/{id}/config` — read namespace behavioral config (stored JSONB + resolved view) +- `PATCH /api/v1/admin/namespaces/{id}/config` — partial update of namespace behavioral config (current whitelist: `grounding_mode`, `show_sources`) + +The PATCH endpoint backs upstream LMS plugins (e.g. the Moodle block edit form) so professors can toggle per-course behavior — strict/hybrid RAG, citation visibility — without touching environment variables or invoking platform admins. + See [architecture.md](../.s2s/architecture.md) for the component specification. diff --git a/vektra-core/README.md b/vektra-core/README.md index e0318dd..8cb8f68 100644 --- a/vektra-core/README.md +++ b/vektra-core/README.md @@ -4,4 +4,10 @@ RAG engine, LLM abstraction, conversation management, and API gateway. This is the primary deployable component of the Vektra platform. It provides the FastAPI HTTP API, implements `QueryPipeline`, manages API key authentication, and coordinates with `vektra-ingest` and `vektra-index` for document ingestion and retrieval. +## Source citations + +Every source in a query response carries a `document_name` field, joined from `source_documents.filename`, so consumers (the chatbot widget, analytics, manual inspection) display human-readable labels instead of chunk UUIDs. Soft-deleted documents (REQ-057) keep their citation with an `(archived)` suffix so traceability is preserved when an answer references content that has since been removed. + +The field propagates through both `SimpleQueryPipeline` and `AdvancedQueryPipeline`, in the JSON and SSE response paths. + See [architecture.md](../.s2s/architecture.md) for the component specification. diff --git a/vektra-learn/README.md b/vektra-learn/README.md index 18ff278..35b450e 100644 --- a/vektra-learn/README.md +++ b/vektra-learn/README.md @@ -4,7 +4,7 @@ E-learning vertical for the Vektra platform. Provides LMS-agnostic REST APIs for ## Components -- **Python backend** (`src/vektra_learn/`): FastAPI router with enrollment, content ingestion, token, and query endpoints +- **Python backend** (`src/vektra_learn/`): FastAPI router with enrollment, content ingestion, token, conversation, and query endpoints - **Chatbot widget** (`widget/`): Vanilla JS chat interface built with esbuild ## Widget build @@ -14,3 +14,78 @@ cd widget && npm install && npm run build ``` Output: `static/vektra-chat.js` + +## Widget integration + +The widget is loaded via a ` +``` + +### Required attributes + +| Attribute | Description | +|-----------|-------------| +| `data-api-url` | Vektra base URL the widget calls (e.g. `https://vektra.example.com`). | +| `data-course-id` | Stable course identifier; also used as the `sessionStorage` key for conversation persistence. | +| `data-token` | JWT dashboard token issued by `POST /api/v1/learn/tokens` (server-side, by the LMS). | + +### Optional white-label attributes (FEAT-016) + +| Attribute | Default | Effect | +|-----------|---------|--------| +| `data-title` | "Course Assistant" / i18n | Chat panel header text and button aria-label. | +| `data-primary-color` | `#2563eb` | Bound to CSS var `--vektra-primary`. Drives the floating button, accents, and links. Hover states use `filter: brightness()` so any custom color stays interactive. | +| `data-icon` | speech-bubble emoji | Floating button icon. Accepts an emoji (rendered as text) or a URL (rendered as image). | +| `data-welcome-message` | (none) | First assistant message shown when the widget opens. | +| `data-powered-by` | `true` | When `false` (case-insensitive), hide the attribution footer. | +| `data-powered-by-text` | "Powered by Vektra" | Override the footer text (plain text only). | +| `data-powered-by-url` | `https://vektralabs.github.io` | Override the footer link target. | +| `data-show-sources` | (server-resolved) | Force or suppress the source-citations block (FEAT-014). Absent = defer to the server-resolved value. Resolution chain: this attr > `namespaces.config.show_sources` > `VEKTRA_LEARN_SHOW_SOURCES` env > hardcoded `true`. | + +### Other attributes + +| Attribute | Default | Effect | +|-----------|---------|--------| +| `data-theme` | `light` | Color theme: `light` or `dark`. | +| `data-language` | `en` | UI strings language: `en`, `it`. | +| `data-token-refresh-url` | (none) | Endpoint the widget calls when the JWT is about to expire (FEAT-009). | + +All `data-*` values are rendered via `textContent` (never `innerHTML`), and `data-primary-color` is validated against a safe whitelist, so XSS via attribute injection is neutralized. + +## Conversation persistence (FEAT-004) + +The widget keeps the active `conversation_id` in `sessionStorage` keyed by `course_id`, so reloading the page restores the chat history without leaking conversations across students on shared university computers (sessionStorage is tab-scoped, wiped on tab close — `localStorage` is intentionally not used). + +On widget init, if a stored conversation is present and not stale (24h cutoff), the widget calls `GET /api/v1/learn/conversations/{id}/turns` and replays the history before enabling input. The header "New chat" button resets the storage entry, the DOM, and the in-memory `conversation_id`. A `404` response from the turns endpoint (conversation deleted server-side) silently clears the entry; a `403` (different course/namespace) does the same. + +## Endpoints + +See [API reference > Learn](../docs/reference/api.md#learn) for full request/response shapes: + +| Method | Path | Auth | Purpose | +|--------|------|------|---------| +| `POST` | `/api/v1/learn/tokens` | API key (`admin`) | Issue a JWT dashboard token (called server-side by the LMS). | +| `POST` | `/api/v1/learn/enrollments` | API key (`admin`) | Register a student in a course (optional when `VEKTRA_LEARN_REQUIRE_ENROLLMENT=false`). | +| `GET` | `/api/v1/learn/enrollments` | API key (`admin`) | List enrollments. | +| `DELETE` | `/api/v1/learn/enrollments/{id}` | API key (`admin`) | Remove an enrollment. | +| `POST` | `/api/v1/learn/content/ingest` | API key (`ingest`/`admin`) | Trigger course-scoped ingestion. | +| `POST` | `/api/v1/learn/query` | JWT | Course-scoped RAG query. Returns `show_sources` flag + `document_name` per source. | +| `GET` | `/api/v1/learn/conversations/{id}/turns` | JWT | Replay decrypted conversation history (namespace-scoped; emits `learn_conversation_turns_read` audit row per NFR-007). | From 43315c0f1239a7b09e16e54cd0ebc3adc776bf33 Mon Sep 17 00:00:00 2001 From: Francesco Vadicamo Date: Sat, 25 Apr 2026 19:14:17 +0000 Subject: [PATCH 04/18] docs(v0.5.0): fix widget URL and clarify nullability - vektra-learn/README.md: widget script src corrected to /static/learn/vektra-chat.js (FastAPI mount, vektra-app/main.py:656). The previous /static/vektra-chat.js example would have 404'd for every integrator that copy-pasted it. - docs/reference/api.md, docs/getting-started/first-query.md: note that document_name can be null when the document join returns no row. - docs/reference/api.md: note that turns[].answer is null for an in-flight turn (question recorded before the LLM completes). Found during self-review of the v0.5.0 doc-alignment commit. --- docs/getting-started/first-query.md | 2 +- docs/reference/api.md | 6 +++--- vektra-learn/README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/getting-started/first-query.md b/docs/getting-started/first-query.md index bd4d4d4..26ce1f6 100644 --- a/docs/getting-started/first-query.md +++ b/docs/getting-started/first-query.md @@ -107,7 +107,7 @@ scripts/query.sh "Summarize the main findings" | `sources[].chunk_id` | Unique identifier of the retrieved chunk | | `sources[].citation_id` | UUID citation reference for traceability | | `sources[].document_version` | Index version of the chunk | -| `sources[].document_name` | Filename of the source document. Soft-deleted documents keep their citation with an `(archived)` suffix so traceability is preserved. | +| `sources[].document_name` | Filename of the source document, or `null` when the document join returns no row. Soft-deleted documents keep their citation with an `(archived)` suffix so traceability is preserved. | | `sources[].score` | Cosine similarity score (0.0 - 1.0, higher is more relevant) | | `sources[].snippet` | Text excerpt from the chunk | | `conversation_id` | Echoed back if provided in the request (see below) | diff --git a/docs/reference/api.md b/docs/reference/api.md index 7b6af63..24690e4 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -339,7 +339,7 @@ Response: | `response_id` | Unique response identifier | | `answer` | LLM-generated answer grounded in sources | | `sources` | Ranked list of source chunks | -| `sources[].document_name` | Filename of the source document (e.g. `lecture-07.pdf`). Soft-deleted documents (REQ-057) keep their citation with an `(archived)` suffix so traceability is preserved. | +| `sources[].document_name` | Filename of the source document (e.g. `lecture-07.pdf`), or `null` when the document join returns no row. Soft-deleted documents (REQ-057) keep their citation with an `(archived)` suffix so traceability is preserved. | | `conversation_id` | Echoed back if provided in request | | `context_only` | `true` if LLM failed and raw sources returned | | `no_relevant_context` | `true` if no chunks exceeded relevance threshold | @@ -418,7 +418,7 @@ Response (HTTP 200, JSON): | Field | Description | |-------|-------------| | `show_sources` | Server-resolved citation-visibility hint for the widget (FEAT-014). The full `sources` list is always returned regardless; the widget uses the flag to decide whether to render the citations block. See the resolution chain in [Namespaces PATCH](#patch-apiv1adminnamespacesnamespace_idconfig). | -| `sources[].document_name` | Filename of the source document, with `(archived)` suffix for soft-deleted documents. | +| `sources[].document_name` | Filename of the source document, with `(archived)` suffix for soft-deleted documents. May be `null` when the document join returns no row. | #### Streaming @@ -465,7 +465,7 @@ Response (HTTP 200): } ``` -The `turns[].sources` array is empty in v0.5.0 (admin-only metadata such as model, prompt tokens, and source enrichment from `query_traces` is intentionally not exposed to students). +The `turns[].sources` array is empty in v0.5.0 (admin-only metadata such as model, prompt tokens, and source enrichment from `query_traces` is intentionally not exposed to students). The `turns[].answer` field may be `null` for a turn that is still in-flight (the question is recorded immediately, the answer is filled in when the LLM completes). Errors: - `404 ERR-LEARN-005` if the conversation does not exist or has been deleted. diff --git a/vektra-learn/README.md b/vektra-learn/README.md index 35b450e..5f974df 100644 --- a/vektra-learn/README.md +++ b/vektra-learn/README.md @@ -21,7 +21,7 @@ The widget is loaded via a ` +``` + +That's the minimum. The floating chat button appears in the bottom-right of the page; clicking it opens the chat panel. + +## Configuration: `data-*` attributes + +All customization lives on the ` +``` + +The endpoint must return JSON `{"token": ""}`. Any non-2xx status, malformed shape, or network error aborts the refresh and surfaces the original `401` to the user. + +The refresh endpoint validates the LMS user's session (cookie auth), then internally calls `POST /api/v1/learn/tokens` with the LMS's admin API key. The API key never reaches the browser. + +### Pattern B — `onTokenExpired` callback + +For programmatic embeds (not data-attribute driven), the widget exposes an `onTokenExpired` callback. See `vektra-learn/widget/src/index.js` for the entry-point shape. + +## Source citations (FEAT-014) + +The widget renders source citations under each assistant answer when sources are present. Visibility is controlled by a 4-level resolution chain (highest priority first): + +1. **`data-show-sources` attribute** — client-side override. The trimmed lowercase value is compared to `"false"`: only that exact string hides the section. Any other value (including `"true"`) forces it visible. +2. **`namespaces.config.show_sources`** — per-course server config, set via `PATCH /api/v1/admin/namespaces/{id}/config`. +3. **`VEKTRA_LEARN_SHOW_SOURCES`** environment variable — global default for the deployment. +4. **Hardcoded `true`** — final fallback. + +The API always returns the full sources list regardless of visibility, so analytics and `QueryTrace` keep complete data. Visibility is purely a UI concern. + +When sources are hidden, the answer still displays normally. When shown, sources are collapsible (per the accessibility refactor in v0.3.0) with `aria-controls` / `aria-expanded`. + +## Error states + +The widget surfaces explicit feedback for non-happy-path scenarios: + +| State | When | UI | +|---|---|---| +| `unavailable` | Initial health check fails or repeated network errors. | Banner "The assistant is currently unavailable. Please try again later." Send button disabled. | +| `reconnecting` | Background re-check after `unavailable`. | Banner "Reconnecting...". | +| `sessionExpired` | `401` after refresh attempt fails (or no refresh URL configured). | Banner "Your session has expired. Please reload the page." Input disabled. | +| `noRelevantContext` | Backend reports no chunks above the relevance threshold. | Inline message "I couldn't find relevant information in the course materials for this question." | +| Generic error | Any other failure during a query. | Inline red message, last assistant message preserved. | + +All strings are i18n-localized (`en`, `it`). Add a language by extending `I18N` in `vektra-learn/widget/src/chat-ui.js` and rebuilding the bundle. + +## End-to-end example: PHP (Moodle-style) + +```php + $course->idnumber, +])); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +$response = json_decode(curl_exec($ch), true); +$token = $response['token']; +?> + + + +``` + +The Moodle plugin ([vektra-moodle](https://github.com/vektralabs/vektra-moodle)) automates this flow as a course block. + +## End-to-end example: Python (FastAPI) + +```python +import httpx +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse + +app = FastAPI() +VEKTRA_HOST = "https://vektra.example.com" +ADMIN_KEY = "..." + +@app.get("/course/{course_id}", response_class=HTMLResponse) +async def course_page(course_id: str, request: Request): + # Validate user's session here (omitted) + async with httpx.AsyncClient() as client: + r = await client.post( + f"{VEKTRA_HOST}/api/v1/learn/tokens", + headers={"X-API-Key": ADMIN_KEY}, + json={"namespace": course_id}, + ) + token = r.json()["token"] + return f""" + + +

Course {course_id}

+ + + + """ +``` + +## Security notes + +- All `data-*` values are rendered via `textContent`. No XSS via attribute injection. +- `data-primary-color` is whitelist-validated against safe CSS color formats (hex, `rgb[a]`, `hsl[a]`, named colors). Anything else is silently ignored. +- `data-powered-by-url` and `data-token-refresh-url` accept only `http://`, `https://`, or same-origin paths. `javascript:`, `data:`, and other schemes are rejected. +- The JWT is embedded in HTML and visible to the student. This is by design: the JWT is course-scoped and short-lived; the API key stays server-side. +- `sessionStorage` is preferred over `localStorage` to avoid cross-student conversation leakage on shared computers. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| Floating button doesn't appear | Missing required `data-*` attribute | Browser console will log `[vektra-chat] Missing required attributes: data-api-url, data-course-id, data-token`. | +| "Session expired" appears | JWT expired and no refresh path configured | Configure `data-token-refresh-url` or `onTokenExpired`. | +| "Unavailable" banner | Health check failing | Verify `data-api-url` is reachable from the student's browser; check CORS on the Vektra host. | +| Sources never appear | `data-show-sources="false"` or namespace config | Inspect the resolution chain. `GET /api/v1/admin/namespaces/{id}/config` returns the `resolved.show_sources` value. | +| Custom color ignored | `data-primary-color` failed whitelist | Use a hex, `rgb()`, `hsl()`, or named color. No quotes, semicolons, or whitespace. | + +## Related documentation + +- [API reference > Learn](../reference/api.md#learn) — endpoint specs +- [Configuration](../reference/configuration.md) — `VEKTRA_LEARN_*` environment variables +- [Error codes](../reference/error-codes.md) — `ERR-LEARN-*` and `ERR-ADMIN-*` series +- [vektra-learn component README](../../vektra-learn/README.md) — internal architecture diff --git a/vektra-learn/README.md b/vektra-learn/README.md index 39d4a68..bf6ab31 100644 --- a/vektra-learn/README.md +++ b/vektra-learn/README.md @@ -56,7 +56,7 @@ The widget is loaded via a ` From 6c2e1a72f5a4080cc4c5807ef498f0e299a03f39 Mon Sep 17 00:00:00 2001 From: Francesco Vadicamo Date: Sun, 26 Apr 2026 17:46:50 +0000 Subject: [PATCH 18/18] docs(guides): fix anchor links to FEAT-suffixed section IDs CodeRabbit comment 3143802559 (LOW/Minor): two in-page anchor links pointed at fragments that don't match the rendered section IDs. Section headings include the FEAT-XXX suffix in parentheses, so GitHub/Jekyll auto-anchors include it too: - "## Token refresh (FEAT-009)" -> #token-refresh-feat-009 - "## Source citations (FEAT-014)" -> #source-citations-feat-014 Updated the two table rows on lines 85 and 93 to use the correct fragment IDs. --- docs/guides/widget-integration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/widget-integration.md b/docs/guides/widget-integration.md index e59ab1c..1110b52 100644 --- a/docs/guides/widget-integration.md +++ b/docs/guides/widget-integration.md @@ -82,7 +82,7 @@ All customization lives on the `